Skip to content

Commit

Permalink
Allow Geometry objects as arguments for Calculation command, move con…
Browse files Browse the repository at this point in the history
…formers/CREST options, and add solvate command (#50)

* Add support for passing Geometry objects as arguments for Calculation, and add a solvate() calculation type

* Use deepcopy on empty_cjson

* Tidy up conformers user options and move crest configuration to general config

* Don't put empty layer array in CJSON template as not valid for Avogadro

* Use config value for GFN-xTB method version

* Add solvate command to plugin

* Read cluster geom from file

* Keep solute bonding information when solvating

* Extract version numbers using regex

* Warn when using broken versions of CREST or incompatible CREST/xtb combos
  • Loading branch information
matterhorn103 authored Dec 4, 2024
1 parent f5fd831 commit 97a42d7
Show file tree
Hide file tree
Showing 11 changed files with 664 additions and 167 deletions.
49 changes: 33 additions & 16 deletions avo_xtb/about.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import argparse
import json
import logging
import re
import subprocess
import sys

Expand All @@ -13,6 +14,30 @@
logger = logging.getLogger(__name__)


# Get xtb and crest versions
# Regex to match version numbers (e.g., 6.7.1, 2.12, 6.4.0)
pattern = r"\bversion\s+(\d+\.\d+\.\d+|\d+\.\d+)\b"

if easyxtb.XTB_BIN:
xtb_version_info = subprocess.run(
[str(easyxtb.XTB_BIN), "--version"],
encoding="utf-8",
capture_output=True,
).stdout
XTB_VERSION = re.findall(pattern, xtb_version_info, re.IGNORECASE)[0]
else:
XTB_VERSION = None
if easyxtb.CREST_BIN:
crest_version_info = subprocess.run(
[str(easyxtb.CREST_BIN), "--version"],
encoding="utf-8",
capture_output=True,
).stdout
CREST_VERSION = re.findall(pattern, crest_version_info, re.IGNORECASE)[0]
else:
CREST_VERSION = None


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--debug", action="store_true")
Expand All @@ -37,30 +62,22 @@
avo_input = json.loads(sys.stdin.read())
output = avo_input.copy()

if easyxtb.XTB_BIN:
xtb_version = subprocess.run(
[str(easyxtb.XTB_BIN), "--version"],
encoding="utf-8",
capture_output=True,
).stdout.splitlines()[-2].strip()
if XTB_VERSION:
xtb_version_msg = XTB_VERSION
else:
xtb_version = "No xtb binary found"
if easyxtb.CREST_BIN:
crest_version = subprocess.run(
[str(easyxtb.CREST_BIN), "--version"],
encoding="utf-8",
capture_output=True,
).stdout.splitlines()[-4].strip()
xtb_version_msg = "No xtb binary found"
if CREST_VERSION:
crest_version_msg = CREST_VERSION
else:
crest_version = "No CREST binary found"
crest_version_msg = "No CREST binary found"

# Do nothing to data other than add message with version and path info
output["message"] = (
"avo_xtb plugin\n"
+ f"easyxtb version: {easyxtb.configuration.easyxtb_VERSION}\n"
+ f"xtb version: {xtb_version}\n"
+ f"xtb version: {xtb_version_msg}\n"
+ f"xtb path: {easyxtb.XTB_BIN}\n"
+ f"CREST version: {crest_version}\n"
+ f"CREST version: {crest_version_msg}\n"
+ f"CREST path: {easyxtb.CREST_BIN}"
)

Expand Down
40 changes: 26 additions & 14 deletions avo_xtb/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,31 @@
"default": str(easyxtb.XTB_BIN),
"order": 1.0,
},
"crest_bin": {
"type": "string",
"label": "Location of the CREST binary",
"default": str(easyxtb.CREST_BIN),
"order": 2.0,
},
"user_dir": {
"type": "string",
"label": "Run calculations (in subfolder) in",
"default": str(easyxtb.CALC_DIR),
"order": 2.0,
"order": 3.0,
},
"n_proc": {
"type": "integer",
"label": "Parallel processes to run",
"minimum": 1,
"default": 1,
"order": 3.0
"order": 4.0
},
"energy_units": {
"type": "stringList",
"label": "Preferred energy units",
"values": ["kJ/mol", "kcal/mol"],
"default": 0,
"order": 4.0,
"order": 5.0,
},
"solvent": {
"type": "stringList",
Expand Down Expand Up @@ -84,14 +90,14 @@
"water",
],
"default": 0,
"order": 5.0,
"order": 6.0,
},
"method": {
"type": "stringList",
"label": "Method (xtb only)",
"label": "Method",
"values": methods,
"default": methods[-1],
"order": 6.0,
"order": 7.0,
},
"opt_lvl": {
"type": "stringList",
Expand All @@ -107,7 +113,7 @@
"extreme",
],
"default": 4,
"order": 7.0,
"order": 8.0,
},
"warning": {
"type": "text",
Expand Down Expand Up @@ -146,24 +152,30 @@
easyxtb.config["calc_dir"] = str(easyxtb.CALC_DIR)

# Save change to xtb_bin if there has been one
if avo_input["xtb_bin"] != str(easyxtb.XTB_BIN):
if avo_input["xtb_bin"] in ["none", ""]:
pass
elif avo_input["xtb_bin"] != str(easyxtb.XTB_BIN):
easyxtb.XTB_BIN = Path(avo_input["xtb_bin"])
easyxtb.config["xtb_bin"] = str(easyxtb.XTB_BIN)

# Save change to crest_bin if there has been one
if avo_input["crest_bin"] in ["none", ""]:
pass
elif Path(avo_input["crest_bin"]) != easyxtb.CREST_BIN:
easyxtb.CREST_BIN = Path(avo_input["crest_bin"])
easyxtb.config["crest_bin"] = str(easyxtb.CREST_BIN)

# Update number of threads
easyxtb.config["n_proc"] = avo_input["n_proc"]

# Update energy units
easyxtb.config["energy_units"] = avo_input["energy_units"]

# Convert "none" string to Python None
# Update solvent
if avo_input["solvent"] == "none":
solvent_selected = None
easyxtb.config["solvent"] = None
else:
solvent_selected = avo_input["solvent"]

# Update solvent
easyxtb.config["solvent"] = solvent_selected
easyxtb.config["solvent"] = avo_input["solvent"]

# Update method
easyxtb.config["method"] = methods.index(avo_input["method"])
Expand Down
100 changes: 8 additions & 92 deletions avo_xtb/conformers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,74 +31,7 @@

if args.print_options:
options = {
"inputMoleculeFormat": "xyz",
"userOptions": {
"crest_bin": {
"type": "string",
"label": "Location of the CREST binary",
"default": str(easyxtb.CREST_BIN),
"order": 1.0,
},
"save_dir": {
"type": "string",
"label": "Save results in",
"default": str(easyxtb.CALC_DIR),
"order": 2.0,
},
# "Number of threads": {
# "type": "integer",
# #"label": "Number of cores",
# "minimum": 1,
# "default": 1,
# "order": 3.0
# },
# "Memory per core": {
# "type": "integer",
# #"label" "Memory per core",
# "minimum": 1,
# "default": 1,
# "suffix": " GB",
# "order": 4.0
# },
"help": {
"type": "text",
"label": "For help see",
"default": "https://crest-lab.github.io/crest-docs/",
"order": 9.0,
},
"solvent": {
"type": "stringList",
"label": "Solvation",
"values": [
"none",
"acetone",
"acetonitrile",
"aniline",
"benzaldehyde",
"benzene",
"ch2cl2",
"chcl3",
"cs2",
"dioxane",
"dmf",
"dmso",
"ether",
"ethylacetate",
"furane",
"hexandecane",
"hexane",
"methanol",
"nitromethane",
"octanol",
"woctanol",
"phenol",
"toluene",
"thf",
"water",
],
"default": 0,
"order": 6.0,
},
"ewin": {
"type": "integer",
"label": "Keep all conformers within",
Expand All @@ -114,15 +47,18 @@
"default": False,
"order": 8.0,
},
"help": {
"type": "text",
"label": "For help see",
"default": "https://crest-lab.github.io/crest-docs/",
"order": 9.0,
},
},
}
# Display energy in kcal if user has insisted on it
if easyxtb.config["energy_units"] == "kcal/mol":
options["userOptions"]["ewin"]["default"] = 6
options["userOptions"]["ewin"]["suffix"] = " kcal/mol"
# Make solvation default if found in user config
if easyxtb.config["solvent"] is not None:
options["userOptions"]["solvent"]["default"] = easyxtb.config["solvent"]
print(json.dumps(options))
if args.display_name:
print("Conformers…")
Expand All @@ -135,30 +71,17 @@
# Extract the coords
geom = easyxtb.Geometry.from_cjson(avo_input["cjson"])

# If provided crest path different to that stored, use it and save it
if Path(avo_input["crest_bin"]) != easyxtb.CREST_BIN:
crest_bin = Path(avo_input["crest_bin"])
easyxtb.config["crest_bin"] = str(crest_bin)
with open(easyxtb.config_file, "w", encoding="utf-8") as config_path:
json.dump(easyxtb.config, config_path)

# crest takes energies in kcal so convert if provided in kJ (default)
if easyxtb.config["energy_units"] == "kJ/mol":
ewin_kcal = avo_input["ewin"] / 4.184
else:
ewin_kcal = avo_input["ewin"]

# Convert "none" string to Python None
if avo_input["solvent"] == "none":
solvation = None
else:
solvation = avo_input["solvent"]

# Run calculation; returns set of conformers as well as Calculation object
conformers, calc = easyxtb.calculate.conformers(
geom,
solvation=solvation,
method=2,
solvation=easyxtb.config["solvent"],
method=easyxtb.config["method"],
ewin=ewin_kcal,
hess=avo_input["hess"],
return_calc=True,
Expand Down Expand Up @@ -190,13 +113,6 @@
with open(easyxtb.TEMP_DIR / "result.cjson", "w", encoding="utf-8") as save_file:
json.dump(output["cjson"], save_file, indent=2)

# If user specified a save location, copy calculation directory to there
if not (
avo_input["save_dir"] in ["", None]
or Path(avo_input["save_dir"]) == easyxtb.TEMP_DIR
):
copytree(easyxtb.TEMP_DIR, Path(avo_input["save_dir"]), dirs_exist_ok=True)

# Pass back to Avogadro
print(json.dumps(output))
logger.debug(f"The following dictionary was passed back to Avogadro: {output}")
2 changes: 1 addition & 1 deletion avo_xtb/deprotonate.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
tautomers, calc = easyxtb.calculate.deprotonate(
geom,
solvation=easyxtb.config["solvent"],
method=2,
method=easyxtb.config["method"],
return_calc=True,
)

Expand Down
3 changes: 2 additions & 1 deletion avo_xtb/protonate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import json
import logging
import sys
from copy import deepcopy

from support import easyxtb

Expand All @@ -19,7 +20,7 @@ def cleanup_after_taut(cjson: dict) -> dict:
Essentially gives an empty cjson, with only the total charge and spin retained.
"""

output = easyxtb.convert.empty_cjson.copy()
output = deepcopy(easyxtb.convert.empty_cjson)
output["properties"]["totalCharge"] = cjson["properties"]["totalCharge"]
output["properties"]["totalSpinMultiplicity"] = cjson["properties"]["totalSpinMultiplicity"]

Expand Down
3 changes: 2 additions & 1 deletion avo_xtb/run_xtb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import json
import logging
import sys
from copy import deepcopy
from pathlib import Path
from shutil import copytree

Expand Down Expand Up @@ -120,7 +121,7 @@
# Start by passing back an empty cjson, then add changes
output = {
"moleculeFormat": "cjson",
"cjson": easyxtb.convert.empty_cjson.copy(),
"cjson": deepcopy(easyxtb.convert.empty_cjson),
}

# TODO Catch errors in xtb execution
Expand Down
Loading

0 comments on commit 97a42d7

Please sign in to comment.