Skip to content

Add QCManyBody and GenOptKing harnesses #448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
32 changes: 32 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,38 @@ Changelog
.. - UNSOLVED (:issue:`397`) extras failed


v0.31.0 / 2024-MM-DD (Unreleased)
--------------------

Breaking Changes
++++++++++++++++

New Features
++++++++++++
- UNMERGED (:pr:`448`) QCManyBody - new procedure for computing interaction energies or truncation or full
many-body expansions. @loriab
- UNMERGED (:pr:`448`) GeomeTRIC, OptKing - new derived procedures running `GeneralizedOptimizationInput`/
`Results` that allow using a `ManyBodySpecification` instead of `QCSpecification` to drive counterpoise-
corrected or partial-body optimizations. May require explicit use of `schema_name` field. Call with
`qcengine.compute_procedure(qcsk, "gengeometric")` or `genoptking`. This is temporary until QCSchema v2. @loriab

Enhancements
++++++++++++

Bug Fixes
+++++++++
- UNMERGED (:pr:`448`) CFour - fix error collecting molecule when it's a single atom with two-letter symbol. @loriab

Misc.
+++++

MUST (Unmerged)
+++++++++++++++

WIP (Unmerged)
++++++++++++++


v0.30.0 / 2024-06-25
--------------------

Expand Down
8 changes: 6 additions & 2 deletions qcengine/procedures/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

from ..exceptions import InputError, ResourceError
from .berny import BernyProcedure
from .geometric import GeometricProcedure
from .geometric import GeometricProcedure, GenGeometricProcedure
from .qcmanybody import QCManyBodyProcedure
from .nwchem_opt import NWChemDriverProcedure
from .optking import OptKingProcedure
from .optking import OptKingProcedure, GenOptKingProcedure
from .torsiondrive import TorsionDriveProcedure
from .model import ProcedureHarness

Expand Down Expand Up @@ -67,7 +68,10 @@ def list_available_procedures() -> Set[str]:


register_procedure(GeometricProcedure())
register_procedure(GenGeometricProcedure())
register_procedure(OptKingProcedure())
register_procedure(GenOptKingProcedure())
register_procedure(BernyProcedure())
register_procedure(QCManyBodyProcedure())
register_procedure(NWChemDriverProcedure())
register_procedure(TorsionDriveProcedure())
65 changes: 65 additions & 0 deletions qcengine/procedures/geometric.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,68 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op
output_data = OptimizationResult(**output_data)

return output_data


class GenGeometricProcedure(GeometricProcedure):

# note that "procedure" value below not used
_defaults = {"name": "GenGeometric", "procedure": "genoptimization"}

version_cache: Dict[str, str] = {}

def found(self, raise_error: bool = False) -> bool:
qc = which_import(
"geometric",
return_bool=True,
raise_error=raise_error,
raise_msg="Please install via `conda install geometric -c conda-forge`.",
)
dep = which_import(
"qcmanybody",
return_bool=True,
raise_error=raise_error,
raise_msg="For GenGeometric harness, please install via `conda install qcmanybody -c conda-forge`.",
)

return qc and dep

def build_input_model(
self, data: Union[Dict[str, Any], "GeneralizedOptimizationInput"]
) -> "GeneralizedOptimizationInput":
from qcmanybody.models.generalized_optimization import GeneralizedOptimizationInput

return self._build_model(data, GeneralizedOptimizationInput)

def compute(
self, input_model: "GeneralizedOptimizationInput", config: "TaskConfig"
) -> "GeneralizedOptimizationResult":
self.found(raise_error=True)

import geometric
from qcmanybody.models.generalized_optimization import GeneralizedOptimizationResult

input_data = input_model.dict()

# Temporary patch for geomeTRIC
input_data["initial_molecule"]["symbols"] = list(input_data["initial_molecule"]["symbols"])

# Set retries to two if zero while respecting local_config
local_config = config.dict()
local_config["retries"] = local_config.get("retries", 2) or 2
input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config

# Run the program
output_data = geometric.run_json.geometric_run_json(input_data)

output_data["provenance"] = {
"creator": "geomeTRIC",
"routine": "geometric.run_json.geometric_run_json",
"version": geometric.__version__,
}

output_data["schema_name"] = "qcschema_generalizedoptimizationresult"
output_data["input_specification"]["extras"].pop("_qcengine_local_config", None)
if output_data["success"]:
output_data = GeneralizedOptimizationResult(**output_data)

return output_data
64 changes: 62 additions & 2 deletions qcengine/procedures/optking.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from typing import Any, Dict, Union
from typing import TYPE_CHECKING, Any, Dict, Union

from qcelemental.models import OptimizationInput, OptimizationResult
from qcelemental.util import safe_version, which_import

from .model import ProcedureHarness

if TYPE_CHECKING:
from qcengine.config import TaskConfig
from qcmanybody.models.generalized_optimization import GeneralizedOptimizationInput, GeneralizedOptimizationResult


class OptKingProcedure(ProcedureHarness):

Expand Down Expand Up @@ -37,7 +41,7 @@ def get_version(self) -> str:

return self.version_cache[which_prog]

def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Optimization":
def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "OptimizationResult":
if self.found(raise_error=True):
import optking

Expand All @@ -57,3 +61,59 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op
output_data = OptimizationResult(**output_data)

return output_data


class GenOptKingProcedure(OptKingProcedure):

# note that "procedure" value below not used
_defaults = {"name": "GenOptKing", "procedure": "genoptimization"}

version_cache: Dict[str, str] = {}

def found(self, raise_error: bool = False) -> bool:
qc = which_import(
"optking",
return_bool=True,
raise_error=raise_error,
raise_msg="Please install via `conda install optking -c conda-forge`.",
)
dep = which_import(
"qcmanybody",
return_bool=True,
raise_error=raise_error,
raise_msg="For GenOptKing harness, please install via `conda install qcmanybody -c conda-forge`.",
)

return qc and dep

def build_input_model(
self, data: Union[Dict[str, Any], "GeneralizedOptimizationInput"]
) -> "GeneralizedOptimizationInput":
from qcmanybody.models.generalized_optimization import GeneralizedOptimizationInput

return self._build_model(data, GeneralizedOptimizationInput)

def compute(
self, input_model: "GeneralizedOptimizationInput", config: "TaskConfig"
) -> "GeneralizedOptimizationResult":
self.found(raise_error=True)

import optking
from qcmanybody.models.generalized_optimization import GeneralizedOptimizationResult

input_data = input_model.dict()

# Set retries to two if zero while respecting local_config
local_config = config.dict()
local_config["retries"] = local_config.get("retries", 2) or 2
input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config

# Run the program
output_data = optking.optimize_qcengine(input_data)

output_data["schema_name"] = "qcschema_generalizedoptimizationresult"
output_data["input_specification"]["extras"].pop("_qcengine_local_config", None)
if output_data["success"]:
output_data = GeneralizedOptimizationResult(**output_data)

return output_data
51 changes: 51 additions & 0 deletions qcengine/procedures/qcmanybody.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import TYPE_CHECKING, Any, Dict, Union

from qcelemental.util import safe_version, which_import

from .model import ProcedureHarness

if TYPE_CHECKING:
from ..config import TaskConfig
from qcmanybody.models import ManyBodyInput, ManyBodyResult


class QCManyBodyProcedure(ProcedureHarness):

# v2: ClassVar[Dict[str, Any]]
_defaults: Dict[str, Any] = {"name": "QCManyBody", "procedure": "manybody"}

version_cache: Dict[str, str] = {}

class Config(ProcedureHarness.Config):
pass

def found(self, raise_error: bool = False) -> bool:
return which_import(
"qcmanybody",
return_bool=True,
raise_error=raise_error,
raise_msg="Please install via `conda install qcmanybody -c conda-forge`.",
)

def build_input_model(self, data: Union[Dict[str, Any], "ManyBodyInput"]) -> "ManyBodyInput":
from qcmanybody.models import ManyBodyInput

return self._build_model(data, ManyBodyInput)

def get_version(self) -> str:
self.found(raise_error=True)

which_prog = which_import("qcmanybody")
if which_prog not in self.version_cache:
import qcmanybody

self.version_cache[which_prog] = safe_version(qcmanybody.__version__)

return self.version_cache[which_prog]

def compute(self, input_model: "ManyBodyInput", config: "TaskConfig") -> "ManyBodyResult":
from qcmanybody import ManyBodyComputer

output_model = ManyBodyComputer.from_manybodyinput(input_model)

return output_model
2 changes: 1 addition & 1 deletion qcengine/programs/adcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def found(raise_error: bool = False) -> bool:
"psi4",
return_bool=True,
raise_error=raise_error,
raise_msg="Please install psi4 for adcc harness via `conda install psi4 -c conda-forge/label/libint_dev -c conda-forge`.",
raise_msg="Please install psi4 for adcc harness via `conda install psi4 -c conda-forge`.",
)
return found_adcc and found_psi4

Expand Down
4 changes: 3 additions & 1 deletion qcengine/programs/cfour/harvester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,9 @@ def harvest_outfile_pass(outtext):
# Process atom geometry
mobj = re.search(r"^\s+" + r"@GETXYZ-I, 1 atoms read from ZMAT." + r"\s*$", outtext, re.MULTILINE)
mobj2 = re.search(
r"^([A-Z]+)#1" + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s*$", outtext, re.MULTILINE
r"^\s*([A-Z]+)" + r"\s*" + r"#1" + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s+" + NUMBER + r"\s*$",
outtext,
re.MULTILINE,
)
if mobj and mobj2:
logger.debug("matched atom2") # unsavory for when atom never printed except for basis file
Expand Down
2 changes: 1 addition & 1 deletion qcengine/programs/psi4.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def found(raise_error: bool = False) -> bool:
"psi4",
return_bool=True,
raise_error=raise_error,
raise_msg="Please install via `conda install psi4 -c conda-forge/label/libint_dev -c conda-forge`. Check it's in your PATH with `which psi4`."
raise_msg="Please install via `conda install psi4 -c conda-forge`. Check it's in your PATH with `which psi4`."
+ error_msg,
)

Expand Down
14 changes: 11 additions & 3 deletions qcengine/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,14 @@ def is_program_new_enough(program, version_feature_introduced):
form is equal to or later than `version_feature_introduced`.

"""
if program not in qcng.list_available_programs():
return False
candidate_version = qcng.get_program(program).get_version()
if program in qcng.list_all_procedures():
if program not in qcng.list_available_procedures():
return False
candidate_version = qcng.get_procedure(program).get_version()
else:
if program not in qcng.list_available_programs():
return False
candidate_version = qcng.get_program(program).get_version()

return parse_version(candidate_version) >= parse_version(version_feature_introduced)

Expand Down Expand Up @@ -164,19 +169,22 @@ def get_job(self):
"mctc-gcp": is_program_new_enough("mctc-gcp", "2.3.0"),
"gcp": which("gcp", return_bool=True),
"geometric": which_import("geometric", return_bool=True),
"geometric_genopt": is_program_new_enough("gengeometric", "1.1.0"),
"berny": which_import("berny", return_bool=True),
"mdi": is_mdi_new_enough("1.2"),
"molpro": is_program_new_enough("molpro", "2018.1"),
"mopac": is_program_new_enough("mopac", "2016"),
"mp2d": which("mp2d", return_bool=True),
"nwchem": which("nwchem", return_bool=True),
"optking": which_import("optking", return_bool=True),
"optking_genopt": is_program_new_enough("genoptking", "0.3.0"),
"psi4": is_program_new_enough("psi4", "1.2"),
"psi4_runqcsk": is_program_new_enough("psi4", "1.4a2.dev160"),
"psi4_mp2qcsk": is_program_new_enough("psi4", "1.4a2.dev580"),
"psi4_derqcsk": is_program_new_enough("psi4", "1.5a1.dev117"),
"qcdb": which_import("qcdb", return_bool=True),
"qchem": is_program_new_enough("qchem", "5.1"),
"qcmanybody": which_import("qcmanybody", return_bool=True),
"rdkit": which_import("rdkit", return_bool=True),
"terachem": which("terachem", return_bool=True),
"terachem_pbs": is_program_new_enough("terachem_pbs", "0.7.2"),
Expand Down
Loading
Loading