-
Notifications
You must be signed in to change notification settings - Fork 0
/
opt.py
101 lines (78 loc) · 3.33 KB
/
opt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# SPDX-FileCopyrightText: 2024 Matthew J. Milner <[email protected]>
# SPDX-License-Identifier: BSD-3-Clause
import argparse
import json
import logging
import sys
from support import easyxtb
logger = logging.getLogger(__name__)
def cleanup_after_opt(cjson: dict) -> dict:
"""Returns a copy of a cjson dict minus any data that is no longer meaningful after
a geometry change."""
output = cjson.copy()
# Frequencies and orbitals
for field in ["vibrations", "basisSet", "orbitals", "cube"]:
if field in output:
del output[field]
# Atomic charges
if "formalCharges" in output["atoms"]:
del output["atoms"]["formalCharges"]
if "partialCharges" in output["atoms"]:
del output["atoms"]["partialCharges"]
return output
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
parser.add_argument("--print-options", action="store_true")
parser.add_argument("--run-command", action="store_true")
parser.add_argument("--display-name", action="store_true")
parser.add_argument("--lang", nargs="?", default="en")
parser.add_argument("--menu-path", action="store_true")
args = parser.parse_args()
# Disable if xtb missing
if easyxtb.XTB_BIN is None:
quit()
if args.print_options:
options = {"inputMoleculeFormat": "xyz"}
print(json.dumps(options))
if args.display_name:
print("Optimize")
if args.menu_path:
print("Extensions|Semi-empirical (xtb){880}")
if args.run_command:
# Read input from Avogadro
avo_input = json.loads(sys.stdin.read())
# Extract the coords
geom = easyxtb.Geometry.from_xyz(avo_input["xyz"].split("\n"))
# Run calculation; returns optimized geometry as well as Calculation object
logger.debug("avo_xtb is requesting a geometry optimization")
opt_geom, calc = easyxtb.calc.optimize(
geom,
charge=avo_input["charge"],
multiplicity=avo_input["spin"],
solvation=easyxtb.config["solvent"],
method=easyxtb.config["method"],
level=easyxtb.config["opt_lvl"],
return_calc=True,
)
# Convert geometry to cjson
geom_cjson = opt_geom.to_cjson()
# Check for convergence
# TODO
# Will need to look for "FAILED TO CONVERGE"
# Get energy for Avogadro
energies = easyxtb.convert.convert_energy(calc.energy, "hartree")
# Format everything appropriately for Avogadro
# Start from the original cjson
output = {"moleculeFormat": "cjson", "cjson": avo_input["cjson"].copy()}
# Remove anything that is now unphysical after the optimization
output["cjson"] = cleanup_after_opt(output["cjson"])
# Add data from calculation
output["cjson"]["atoms"]["coords"] = geom_cjson["atoms"]["coords"]
output["cjson"]["properties"]["totalEnergy"] = round(energies["eV"], 7)
# Save result
with open(easyxtb.TEMP_DIR / "result.cjson", "w", encoding="utf-8") as save_file:
json.dump(output["cjson"], save_file, indent=2)
# Pass back to Avogadro
print(json.dumps(output))
logger.debug(f"The following dictionary was passed back to Avogadro: {output}")