From 2966cc3277b5196b3e357b6a1443fcc5e5d0eb9e Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Wed, 30 Oct 2024 15:40:47 -0400 Subject: [PATCH] Csse pyd2 506e (#456) * program harnesses to v2 * mostly fixed up * fix nwc * qcer * more fix * fix qcel to fix qchem * docs * docs2 * docs3 * docs4 * docs again * why the error * release a few pins * next rnd * docs * suppress some warnings * rest of test lanes --- .github/workflows/CI.yml | 10 +++- devtools/conda-envs/docs-cf.yaml | 2 +- docs/source/changelog.rst | 2 +- docs/source/conf.py | 2 +- docs/source/single_compute.rst | 5 ++ qcengine/cli.py | 3 +- qcengine/compute.py | 16 ++++--- qcengine/config.py | 13 ++--- qcengine/exceptions.py | 4 +- qcengine/procedures/berny.py | 2 +- qcengine/procedures/geometric.py | 2 +- qcengine/procedures/model.py | 6 +-- qcengine/procedures/nwchem_opt/__init__.py | 2 +- qcengine/procedures/nwchem_opt/harvester.py | 2 +- qcengine/procedures/optking.py | 3 +- qcengine/procedures/torsiondrive.py | 15 +++--- qcengine/programs/adcc.py | 8 ++-- qcengine/programs/aimnet2.py | 4 +- qcengine/programs/cfour/harvester.py | 2 +- qcengine/programs/cfour/runner.py | 4 +- qcengine/programs/dftd3.py | 5 +- qcengine/programs/dftd_ng.py | 10 ++-- qcengine/programs/gamess/harvester.py | 2 +- qcengine/programs/gamess/runner.py | 4 +- qcengine/programs/gcp.py | 6 ++- qcengine/programs/mace.py | 6 +-- qcengine/programs/model.py | 10 ++-- qcengine/programs/molpro.py | 4 +- qcengine/programs/mopac.py | 3 +- qcengine/programs/mp2d.py | 3 +- qcengine/programs/mrchem.py | 5 +- qcengine/programs/nwchem/errors.py | 2 +- qcengine/programs/nwchem/harvester.py | 11 ++--- qcengine/programs/nwchem/runner.py | 6 +-- qcengine/programs/openmm.py | 5 +- qcengine/programs/psi4.py | 8 +++- qcengine/programs/qchem.py | 6 ++- qcengine/programs/qcore.py | 8 +++- .../programs/qcvar_identities_resources.py | 2 +- qcengine/programs/rdkit.py | 5 +- qcengine/programs/terachem.py | 3 +- qcengine/programs/terachem_frontend.py | 1 + qcengine/programs/terachem_pbs.py | 9 ++-- qcengine/programs/tests/test_molpro.py | 6 ++- qcengine/programs/tests/test_nwchem.py | 1 + qcengine/programs/tests/test_qchem.py | 18 ++++--- qcengine/programs/tests/test_terachem.py | 8 ++-- qcengine/programs/torchani.py | 5 +- qcengine/programs/turbomole/runner.py | 3 +- qcengine/programs/util/ao_reordering.py | 2 +- qcengine/programs/xtb.py | 4 +- qcengine/stock_mols.py | 6 +-- qcengine/testing.py | 19 +++++--- qcengine/tests/test_cli.py | 37 +++++++++----- qcengine/tests/test_procedures.py | 48 ++++++++----------- qcengine/tests/test_program_utils.py | 2 +- qcengine/util.py | 15 ++++-- 57 files changed, 241 insertions(+), 164 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 9815c41d5..75664742b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -158,7 +158,7 @@ jobs: #if: false run: | conda remove qcelemental --force - python -m pip install 'git+https://github.com/loriab/QCElemental.git@csse_pyd2_converterclasses' --no-deps + python -m pip install 'git+https://github.com/loriab/QCElemental.git@csse_pyd2_warnings' --no-deps # note: conda remove --force, not mamba remove --force b/c https://github.com/mamba-org/mamba/issues/412 # alt. is micromamba but not yet ready for setup-miniconda https://github.com/conda-incubator/setup-miniconda/issues/75 @@ -236,6 +236,12 @@ jobs: add-pip-as-python-dependency: true channels: conda-forge + - name: Special Config - QCElemental Dep + #if: false + run: | + conda remove qcelemental --force + python -m pip install 'git+https://github.com/loriab/QCElemental.git@csse_pyd2_warnings' --no-deps + - name: Environment Information run: | conda info @@ -244,6 +250,8 @@ jobs: - name: Build Documentation run: | python -m pip install . --no-deps + python -c "import qcelemental as q;print(q.__file__, q.__version__)" + python -c "import qcengine as q;print(q.__file__, q.__version__)" cd docs make html diff --git a/devtools/conda-envs/docs-cf.yaml b/devtools/conda-envs/docs-cf.yaml index ccc2f4683..9a22eab63 100644 --- a/devtools/conda-envs/docs-cf.yaml +++ b/devtools/conda-envs/docs-cf.yaml @@ -5,7 +5,7 @@ channels: dependencies: - python - networkx - - pydantic=2 + - pydantic=2.5 - pydantic-settings - numpy - pint diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 61c4022ba..da0040758 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -29,7 +29,7 @@ Changelog vX.Y.0 / 2024-MM-DD (Unreleased) --------------------- +-------------------------------- Breaking Changes ++++++++++++++++ diff --git a/docs/source/conf.py b/docs/source/conf.py index 1d210f1ff..77f95e8f3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -117,7 +117,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] #'_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. diff --git a/docs/source/single_compute.rst b/docs/source/single_compute.rst index 6fb940efa..08b69ef44 100644 --- a/docs/source/single_compute.rst +++ b/docs/source/single_compute.rst @@ -52,6 +52,11 @@ By default the job is given resources relating to the compute environment it is >>> ret = qcng.compute(inp, "psi4", local_options={"memory": 2, "ncores": 3}) +By default, the job returns a QCSchema Result of the same ``schema_version`` as the Input (v1 if Input version can't be determined). To request a specific version back, use the ``return_version`` keyword: + +.. code:: python + + >>> ret = qcng.compute(inp, "psi4", return_version=2) Results diff --git a/qcengine/cli.py b/qcengine/cli.py index 70e0e39f3..8daad9155 100644 --- a/qcengine/cli.py +++ b/qcengine/cli.py @@ -77,7 +77,8 @@ def parse_args(): run_procedure = subparsers.add_parser( "run-procedure", parents=[parent_parser], - help="Run a procedure on a given task. Output is printed as a JSON blob.", + help="Run a procedure on a given task. Output is printed as a JSON blob. " + "Since v0.50.0, favor 'run', even for procedures.", ) run_procedure.add_argument("procedure", type=str, help="The procedure to run.") run_procedure.add_argument( diff --git a/qcengine/compute.py b/qcengine/compute.py index ab3139f69..9bc47ce92 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union import qcelemental -from qcelemental.models import AtomicInput, AtomicResult, FailedOperation, OptimizationResult # TODO from .config import get_config from .exceptions import InputError, RandomError @@ -15,14 +14,13 @@ if TYPE_CHECKING: from pydantic.main import BaseModel - from qcelemental.models import AtomicResult __all__ = ["compute", "compute_procedure"] def _process_failure_and_return(model, return_dict, raise_error): - if isinstance(model, FailedOperation): + if isinstance(model, (qcelemental.models.v1.FailedOperation, qcelemental.models.v2.FailedOperation)): if raise_error: raise InputError(model.error.error_message) elif return_dict: @@ -71,6 +69,12 @@ def compute( result AtomicResult, OptimizationResult, FailedOperation, etc., or Dict representation of any object type A QCSchema representation of the requested output, type depends on return_dict key. + + .. versionadded:: 0.50.0 + input_data can newly be QCSchema v2 as well as longstanding v1. + The compute_procedure is newly incorporated into compute. + The *return_version* parameter was added. + """ try: @@ -123,11 +127,11 @@ def compute( def compute_procedure(*args, **kwargs): - vchanges = qcelemental.models.common_models._qcsk_v2_default_v1_importpathschange + from qcelemental.models.common_models import _qcsk_v2_default_v1_importpathschange warnings.warn( - f"Using the `compute_procedure` function is deprecated in favor of using `compute`, " - "and as soon as version {vchanges} it will stop working.", + "Using the `compute_procedure` function is deprecated in favor of using `compute`, " + f"and as soon as version {_qcsk_v2_default_v1_importpathschange} it will stop working.", category=FutureWarning, stacklevel=2, ) diff --git a/qcengine/config.py b/qcengine/config.py index 33550fdbd..bc06581e8 100644 --- a/qcengine/config.py +++ b/qcengine/config.py @@ -93,15 +93,16 @@ class NodeDescriptor(BaseModel): # Cluster options is_batch_node: bool = Field( False, - description="""Whether the node running QCEngine is a batch node - + description=r"""Whether the node running QCEngine is a batch node + Some clusters are configured such that tasks are launched from a special "batch" or "MOM" onto the compute nodes. The compute nodes on such clusters often have a different CPU architecture than the batch nodes and often are unable to launch MPI tasks, which has two implications: - 1) QCEngine must make *all* calls to an executable via ``mpirun`` because the executables might not - be able to run on the batch node. - 2) QCEngine must run on the batch node to be able to launch tasks on the more than one compute nodes - + + 1. QCEngine must make *all* calls to an executable via ``mpirun`` because the executables might not + be able to run on the batch node. + 2. QCEngine must run on the batch node to be able to launch tasks on the more than one compute nodes + ``is_batch_node`` is used when creating the task configuration as a means of determining whether ``mpiexec_command`` must always be used even for serial jobs (e.g., getting the version number) """, diff --git a/qcengine/exceptions.py b/qcengine/exceptions.py index 1ac56b098..0fa7a394d 100644 --- a/qcengine/exceptions.py +++ b/qcengine/exceptions.py @@ -1,8 +1,6 @@ import traceback from typing import Any, Dict, Optional -from qcelemental.models import AtomicInput - class QCEngineException(Exception): """ @@ -106,7 +104,7 @@ def detect_error(cls, outputs: Dict[str, str]): """ raise NotImplementedError() - def create_keyword_update(self, input_data: AtomicInput) -> Dict[str, Any]: + def create_keyword_update(self, input_data: "AtomicInput") -> Dict[str, Any]: """Create an keyword used to the update the dictionary given observed error Parameters diff --git a/qcengine/procedures/berny.py b/qcengine/procedures/berny.py index 65dffacbd..465fba5ce 100644 --- a/qcengine/procedures/berny.py +++ b/qcengine/procedures/berny.py @@ -5,7 +5,7 @@ from typing import Any, ClassVar, Dict, Union import numpy as np -from qcelemental.models import FailedOperation, OptimizationInput, OptimizationResult +from qcelemental.models.v2 import FailedOperation, OptimizationInput, OptimizationResult from qcelemental.util import which_import import qcengine diff --git a/qcengine/procedures/geometric.py b/qcengine/procedures/geometric.py index f5c75d7a2..e8f49bc9e 100644 --- a/qcengine/procedures/geometric.py +++ b/qcengine/procedures/geometric.py @@ -1,6 +1,6 @@ from typing import Any, ClassVar, Dict, Union -from qcelemental.models import OptimizationInput, OptimizationResult +from qcelemental.models.v2 import OptimizationInput, OptimizationResult from qcelemental.util import safe_version, which_import from .model import ProcedureHarness diff --git a/qcengine/procedures/model.py b/qcengine/procedures/model.py index 0b25588f9..64f7714c6 100644 --- a/qcengine/procedures/model.py +++ b/qcengine/procedures/model.py @@ -73,13 +73,13 @@ def _build_model( # for now, the two dictionaries look the same, so cast to the one we want # note that this prevents correctly identifying the user schema version when dict passed in, so either as_v1/None or as_v2 will fail - mdl = model_wrapper(data, v1_model) # TODO v2 + mdl = model_wrapper(data, v1_model) input_schema_version = mdl.schema_version if return_input_schema_version: - return mdl.convert_v(1), input_schema_version # non-psi4 return_dict=False fail w/o this + return mdl.convert_v(2), input_schema_version else: - return mdl.convert_v(1) + return mdl.convert_v(2) def get_version(self) -> str: """Finds procedure, extracts version, returns normalized version string. diff --git a/qcengine/procedures/nwchem_opt/__init__.py b/qcengine/procedures/nwchem_opt/__init__.py index 3b066c173..592f26555 100644 --- a/qcengine/procedures/nwchem_opt/__init__.py +++ b/qcengine/procedures/nwchem_opt/__init__.py @@ -1,6 +1,6 @@ from typing import Any, ClassVar, Dict, Union -from qcelemental.models import AtomicInput, OptimizationInput, OptimizationResult, Provenance +from qcelemental.models.v2 import AtomicInput, OptimizationInput, OptimizationResult, Provenance from qcengine.config import TaskConfig from qcengine.exceptions import InputError, UnknownError diff --git a/qcengine/procedures/nwchem_opt/harvester.py b/qcengine/procedures/nwchem_opt/harvester.py index 76ba2abb2..dad838b81 100644 --- a/qcengine/procedures/nwchem_opt/harvester.py +++ b/qcengine/procedures/nwchem_opt/harvester.py @@ -2,7 +2,7 @@ from decimal import Decimal from typing import List, Tuple -from qcelemental.models import AtomicResult, Molecule, OptimizationInput, Provenance +from qcelemental.models.v2 import AtomicResult, Molecule, OptimizationInput, Provenance from qcelemental.util import unnp from qcengine.programs.nwchem.harvester import harvest_outfile_pass diff --git a/qcengine/procedures/optking.py b/qcengine/procedures/optking.py index 472f44775..a7bb81e77 100644 --- a/qcengine/procedures/optking.py +++ b/qcengine/procedures/optking.py @@ -1,6 +1,6 @@ from typing import Any, ClassVar, Dict, Union -from qcelemental.models import OptimizationInput, OptimizationResult +from qcelemental.models.v2 import OptimizationInput, OptimizationResult from qcelemental.util import safe_version, which_import from .model import ProcedureHarness @@ -40,6 +40,7 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op if self.found(raise_error=True): import optking + input_data = input_model.convert_v(1) input_data = input_model.dict() # Set retries to two if zero while respecting local_config diff --git a/qcengine/procedures/torsiondrive.py b/qcengine/procedures/torsiondrive.py index 5bcd64066..1b7d7baef 100644 --- a/qcengine/procedures/torsiondrive.py +++ b/qcengine/procedures/torsiondrive.py @@ -4,8 +4,13 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Tuple, Union import numpy as np -from qcelemental.models import FailedOperation, Molecule -from qcelemental.models.procedures import OptimizationInput, OptimizationResult, TorsionDriveInput, TorsionDriveResult +from qcelemental.models.v2 import FailedOperation, Molecule +from qcelemental.models.v2.procedures import ( + OptimizationInput, + OptimizationResult, + TorsionDriveInput, + TorsionDriveResult, +) from qcelemental.util import which_import from .model import ProcedureHarness @@ -173,7 +178,7 @@ def _spawn_optimization( object. """ - from qcengine import compute_procedure + from qcengine import compute input_molecule = input_model.initial_molecule[0].copy(deep=True).dict() input_molecule["geometry"] = np.array(job).reshape(len(input_molecule["symbols"]), 3) @@ -204,9 +209,7 @@ def _spawn_optimization( initial_molecule=input_molecule, ) - return compute_procedure( - input_data, procedure=input_model.optimization_spec.procedure, task_config=config.dict() - ) + return compute(input_data, program=input_model.optimization_spec.procedure, task_config=config.dict()) @staticmethod def _find_final_results( diff --git a/qcengine/programs/adcc.py b/qcengine/programs/adcc.py index 619e4fc75..560329f97 100644 --- a/qcengine/programs/adcc.py +++ b/qcengine/programs/adcc.py @@ -3,7 +3,7 @@ """ from typing import TYPE_CHECKING, Any, ClassVar, Dict -from qcelemental.models import AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which_import from ..exceptions import InputError, UnknownError @@ -11,7 +11,7 @@ from .qcvar_identities_resources import build_atomicproperties if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -19,6 +19,8 @@ class AdccHarness(ProgramHarness): + """Interface for adcc project.""" + _defaults: ClassVar[Dict[str, Any]] = { "name": "adcc", "scratch": False, @@ -113,7 +115,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe except Exception as e: raise UnknownError(str(e)) - input_data = input_model.dict(encoding="json") + input_data = input_model.model_dump(encoding="json") output_data = input_data.copy() output_data["success"] = compute_success diff --git a/qcengine/programs/aimnet2.py b/qcengine/programs/aimnet2.py index 0509fb105..4c577cb3d 100644 --- a/qcengine/programs/aimnet2.py +++ b/qcengine/programs/aimnet2.py @@ -1,13 +1,13 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict -from qcelemental.models import AtomicResult, Provenance +from qcelemental.models.v2 import AtomicResult, Provenance from qcelemental.util import safe_version, which_import from qcengine.exceptions import InputError from qcengine.programs.model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from qcengine.config import TaskConfig diff --git a/qcengine/programs/cfour/harvester.py b/qcengine/programs/cfour/harvester.py index 773d6d3d3..686d1ef0d 100644 --- a/qcengine/programs/cfour/harvester.py +++ b/qcengine/programs/cfour/harvester.py @@ -4,7 +4,7 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import Molecule +from qcelemental.models.v2 import Molecule from qcelemental.molparse import regex from ..util import PreservingDict, load_hessian diff --git a/qcengine/programs/cfour/runner.py b/qcengine/programs/cfour/runner.py index b3bb7f654..e522ecfac 100644 --- a/qcengine/programs/cfour/runner.py +++ b/qcengine/programs/cfour/runner.py @@ -7,7 +7,7 @@ from typing import Any, ClassVar, Dict, Optional, Tuple import numpy as np -from qcelemental.models import AtomicInput, AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicInput, AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which from ...exceptions import InputError, UnknownError @@ -23,7 +23,7 @@ class CFOURHarness(ProgramHarness): - """ + """Interface for CFOUR project. Notes ----- diff --git a/qcengine/programs/dftd3.py b/qcengine/programs/dftd3.py index 4aff9a1be..d3a11fa76 100644 --- a/qcengine/programs/dftd3.py +++ b/qcengine/programs/dftd3.py @@ -11,7 +11,7 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import AtomicResult, FailedOperation, Provenance +from qcelemental.models.v2 import AtomicResult, FailedOperation, Provenance from qcelemental.util import safe_version, which from ..exceptions import InputError, ResourceError, UnknownError @@ -20,7 +20,7 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -29,6 +29,7 @@ class DFTD3Harness(ProgramHarness): + """Interface for DFTD3 executable project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "DFTD3", diff --git a/qcengine/programs/dftd_ng.py b/qcengine/programs/dftd_ng.py index 870491827..d1b398469 100644 --- a/qcengine/programs/dftd_ng.py +++ b/qcengine/programs/dftd_ng.py @@ -9,7 +9,8 @@ from typing import Any, ClassVar, Dict -from qcelemental.models import AtomicInput, AtomicResult +import qcelemental +from qcelemental.models.v2 import AtomicInput, AtomicResult from qcelemental.util import parse_version, safe_version, which_import from ..config import TaskConfig @@ -103,10 +104,12 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult: level_hint = "d4" input_data["keywords"]["level_hint"] = level_hint - input_model = AtomicInput(**input_data) + # dftd4 speaks qcsk.v1 + input_model = qcelemental.models.v1.AtomicInput(**input_data) # Run the Harness output = run_qcschema(input_model) + output = output.convert_v(2) if "info" in output.extras: qcvkey = output.extras["info"]["fctldash"].upper() @@ -271,10 +274,11 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult: input_data["keywords"]["params_tweaks"] = {**planinfo["dashparams"], "s9": 0.0} input_data["keywords"]["level_hint"] = level_hint - input_model = AtomicInput(**input_data) + input_model = qcelemental.models.v1.AtomicInput(**input_data) # Run the Harness output = run_qcschema(input_model) + output = output.convert_v(2) if "info" in output.extras: qcvkey = output.extras["info"]["fctldash"].upper() diff --git a/qcengine/programs/gamess/harvester.py b/qcengine/programs/gamess/harvester.py index 1597dbd05..cc7bbb7f6 100644 --- a/qcengine/programs/gamess/harvester.py +++ b/qcengine/programs/gamess/harvester.py @@ -8,7 +8,7 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import Molecule +from qcelemental.models.v2 import Molecule from qcelemental.molparse import regex from ..util import PreservingDict, load_hessian diff --git a/qcengine/programs/gamess/runner.py b/qcengine/programs/gamess/runner.py index 8913b7463..f0dc6a10b 100644 --- a/qcengine/programs/gamess/runner.py +++ b/qcengine/programs/gamess/runner.py @@ -5,7 +5,7 @@ from decimal import Decimal from typing import Any, ClassVar, Dict, Optional, Tuple -from qcelemental.models import AtomicInput, AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicInput, AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which from ...exceptions import InputError, UnknownError @@ -21,7 +21,7 @@ class GAMESSHarness(ProgramHarness): - """ + """Interface for GAMESS-US project. Notes ----- diff --git a/qcengine/programs/gcp.py b/qcengine/programs/gcp.py index 8712ae716..5182752e2 100644 --- a/qcengine/programs/gcp.py +++ b/qcengine/programs/gcp.py @@ -11,7 +11,7 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import AtomicResult, FailedOperation, Provenance +from qcelemental.models.v2 import AtomicResult, FailedOperation, Provenance from qcelemental.util import safe_version, which from ..exceptions import InputError, UnknownError @@ -19,7 +19,7 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -28,6 +28,7 @@ class GCPHarness(ProgramHarness): + """Interface for the old gcp executable project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "GCP", @@ -279,6 +280,7 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> class MCTCGCPHarness(GCPHarness): + """Interface for the gcp project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "MCTC-GCP", diff --git a/qcengine/programs/mace.py b/qcengine/programs/mace.py index 23ba51541..ab81fd0a2 100644 --- a/qcengine/programs/mace.py +++ b/qcengine/programs/mace.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Union -from qcelemental.models import AtomicResult, FailedOperation, Provenance +from qcelemental.models.v2 import AtomicResult, FailedOperation, Provenance from qcelemental.util import safe_version, which_import from qcengine.exceptions import InputError @@ -8,7 +8,7 @@ from qcengine.units import ureg if TYPE_CHECKING: - from qcelemental.models import AtomicInput, FailedOperation + from qcelemental.models.v2 import AtomicInput, FailedOperation from qcengine.config import TaskConfig @@ -122,7 +122,7 @@ def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> Union["Ato ret_data["properties"] = {"return_energy": mace_data["energy"] * ureg.conversion_factor("eV", "hartree")} if input_data.driver == "energy": - ret_data["return_result"] = ret_data["properties"]["return_energy"] + ret_data["return_result"] = ret_data["properties"]["return_energy"].detach().numpy().item() elif input_data.driver == "gradient": ret_data["return_result"] = ( np.asarray(-1.0 * mace_data["forces"] * ureg.conversion_factor("eV / angstrom", "hartree / bohr")) diff --git a/qcengine/programs/model.py b/qcengine/programs/model.py index ad2e32502..63fb735b4 100644 --- a/qcengine/programs/model.py +++ b/qcengine/programs/model.py @@ -14,6 +14,7 @@ class ProgramHarness(BaseModel, abc.ABC): + """Base class for analytic single-geometry capable harnesses.""" _defaults: ClassVar[Dict[str, Any]] = {} name: str @@ -66,8 +67,6 @@ def found(raise_error: bool = False) -> bool: ## Utility - # def _build_model - def build_input_model( self, data: Dict[str, Any], *, return_input_schema_version: bool = False ) -> Union["AtomicInput", Tuple["AtomicInput", int]]: @@ -93,9 +92,9 @@ def build_input_model( input_schema_version = mdl.schema_version if return_input_schema_version: - return mdl.convert_v(1), input_schema_version # non-psi4 return_dict=False fail w/o this + return mdl.convert_v(2), input_schema_version else: - return mdl.convert_v(1) + return mdl.convert_v(2) def get_version(self) -> str: """Finds program, extracts version, returns normalized version string. @@ -184,8 +183,7 @@ def compute(self, input_data: "AtomicInput", config: TaskConfig) -> "AtomicResul keyword_updates = e.create_keyword_update(local_input_data) new_keywords = local_input_data.keywords.copy() new_keywords.update(keyword_updates) - # TODO v2 - local_input_data = qcelemental.models.v1.AtomicInput( + local_input_data = input_data.__class__( **local_input_data.dict(exclude={"keywords"}), keywords=new_keywords ) diff --git a/qcengine/programs/molpro.py b/qcengine/programs/molpro.py index 09e14a369..88c946f29 100644 --- a/qcengine/programs/molpro.py +++ b/qcengine/programs/molpro.py @@ -6,7 +6,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple from xml.etree import ElementTree as ET -from qcelemental.models import AtomicResult +from qcelemental.models.v2 import AtomicResult from qcelemental.util import parse_version, safe_version, which from ..exceptions import InputError, UnknownError @@ -15,6 +15,8 @@ class MolproHarness(ProgramHarness): + """Interface for Molpro project.""" + _defaults: ClassVar[Dict[str, Any]] = { "name": "Molpro", "scratch": True, diff --git a/qcengine/programs/mopac.py b/qcengine/programs/mopac.py index 06e9bb040..dd5248a16 100644 --- a/qcengine/programs/mopac.py +++ b/qcengine/programs/mopac.py @@ -4,7 +4,7 @@ import os from typing import Any, ClassVar, Dict, List, Optional, Tuple -from qcelemental.models import AtomicResult +from qcelemental.models.v2 import AtomicResult from qcelemental.util import which from ..exceptions import InputError, UnknownError @@ -13,6 +13,7 @@ class MopacHarness(ProgramHarness): + """Interface for MOPAC project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "MOPAC", diff --git a/qcengine/programs/mp2d.py b/qcengine/programs/mp2d.py index e68578962..df646fa0f 100644 --- a/qcengine/programs/mp2d.py +++ b/qcengine/programs/mp2d.py @@ -8,7 +8,7 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import AtomicResult, Provenance +from qcelemental.models.v2 import AtomicResult, Provenance from qcelemental.util import safe_version, which from ..exceptions import InputError, ResourceError, UnknownError @@ -20,6 +20,7 @@ class MP2DHarness(ProgramHarness): + """Interface for MP2D executable project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "MP2D", diff --git a/qcengine/programs/mrchem.py b/qcengine/programs/mrchem.py index 67ea582f6..4251759ee 100644 --- a/qcengine/programs/mrchem.py +++ b/qcengine/programs/mrchem.py @@ -11,7 +11,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple -from qcelemental.models import AtomicResult +from qcelemental.models.v2 import AtomicResult from qcelemental.util import safe_version, which from ..exceptions import InputError, RandomError, UnknownError @@ -19,7 +19,7 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -28,6 +28,7 @@ class MRChemHarness(ProgramHarness): + """Interface for MRChem project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "MRChem", diff --git a/qcengine/programs/nwchem/errors.py b/qcengine/programs/nwchem/errors.py index e1c214d14..08cdacf05 100644 --- a/qcengine/programs/nwchem/errors.py +++ b/qcengine/programs/nwchem/errors.py @@ -2,7 +2,7 @@ import logging from typing import Any, Dict -from qcelemental.models import AtomicInput +from qcelemental.models.v2 import AtomicInput from qcengine.exceptions import SimpleKnownErrorException from qcengine.programs.nwchem.germinate import xc_functionals diff --git a/qcengine/programs/nwchem/harvester.py b/qcengine/programs/nwchem/harvester.py index 94425ba9a..b83d81e33 100644 --- a/qcengine/programs/nwchem/harvester.py +++ b/qcengine/programs/nwchem/harvester.py @@ -6,7 +6,6 @@ import numpy as np import qcelemental as qcel -from qcelemental.models import Molecule from qcelemental.molparse import regex from ..util import PreservingDict @@ -14,7 +13,7 @@ logger = logging.getLogger(__name__) -def harvest_output(outtext: str) -> Tuple[PreservingDict, Molecule, list, str, str]: +def harvest_output(outtext: str) -> Tuple[PreservingDict, "Molecule", list, str, str]: """Function to read an entire NWChem output file. Reads all of the different "line search" segments of a file and returns @@ -842,7 +841,7 @@ def harvest_outfile_pass(outtext): molxyz += "%s %16s %16s %16s\n" % (lline[-5], lline[-3], lline[-2], lline[-1]) # Jiyoung was collecting charge (-4)? see if this is ok for ghosts # Tag , X, Y, Z - psivar_coord = Molecule( + psivar_coord = qcel.models.v2.Molecule( validate=False, **qcel.molparse.to_schema( qcel.molparse.from_string(molxyz, dtype="xyz+", fix_com=True, fix_orientation=True)["qm"], @@ -864,7 +863,7 @@ def harvest_outfile_pass(outtext): tag = f"@{tag}" molxyz += "%s %16s %16s %16s\n" % (tag, lline[-3], lline[-2], lline[-1]) # Tag , X, Y, Z - psivar_coord = Molecule( + psivar_coord = qcel.models.v2.Molecule( validate=False, **qcel.molparse.to_schema( qcel.molparse.from_string(molxyz, dtype="xyz+", fix_com=True, fix_orientation=True)["qm"], @@ -1154,8 +1153,8 @@ def harvest_hessian(hess: str) -> np.ndarray: def harvest( - in_mol: Molecule, method: str, nwout: str, **outfiles -) -> Tuple[PreservingDict, Optional[np.ndarray], list, Molecule, str, str]: + in_mol: "Molecule", method: str, nwout: str, **outfiles +) -> Tuple[PreservingDict, Optional[np.ndarray], list, "Molecule", str, str]: """Parses all the pieces of output from NWChem: the stdout in *nwout* Scratch files are not yet considered at this moment. diff --git a/qcengine/programs/nwchem/runner.py b/qcengine/programs/nwchem/runner.py index 0860640d1..8a45b9b50 100644 --- a/qcengine/programs/nwchem/runner.py +++ b/qcengine/programs/nwchem/runner.py @@ -10,7 +10,7 @@ from typing import Any, ClassVar, Dict, Optional, Tuple import numpy as np -from qcelemental.models import AtomicInput, AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicInput, AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which, which_import from qcengine.config import TaskConfig, get_config @@ -31,7 +31,7 @@ class NWChemHarness(ErrorCorrectionProgramHarness): - """ + """Interface for NWChem Classic project. Notes ----- @@ -343,4 +343,4 @@ def parse_output( k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } - return AtomicResult(**{**input_model.dict(), **output_data}) + return AtomicResult(**{**input_model.model_dump(), **output_data}) diff --git a/qcengine/programs/openmm.py b/qcengine/programs/openmm.py index bfdf8a9ab..f3f5f4c11 100644 --- a/qcengine/programs/openmm.py +++ b/qcengine/programs/openmm.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict import numpy as np -from qcelemental.models import AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which_import from ..exceptions import InputError @@ -18,12 +18,13 @@ from .rdkit import RDKitHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig class OpenMMHarness(ProgramHarness): + """Interface for OpenMM project.""" _CACHE: ClassVar[Dict] = {} _CACHE_MAX_SIZE: ClassVar[int] = 10 diff --git a/qcengine/programs/psi4.py b/qcengine/programs/psi4.py index 01600f4b9..10562b133 100644 --- a/qcengine/programs/psi4.py +++ b/qcengine/programs/psi4.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, Dict -from qcelemental.models import AtomicResult, BasisSet +from qcelemental.models.v2 import AtomicResult, BasisSet from qcelemental.util import deserialize, parse_version, safe_version, which, which_import from ..exceptions import InputError, RandomError, ResourceError, UnknownError @@ -15,12 +15,13 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig class Psi4Harness(ProgramHarness): + """Interface for Psi4 project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "Psi4", @@ -214,6 +215,9 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe output_data["stdout"] = output_data.pop("raw_output", None) else: + # psi4 QCSchema interface only speaks qcsk.v1 + # * note that only psiapi=True calcs need this until rearrangement affects all + input_model = input_model.convert_v(1) if input_model.extras.get("psiapi", False): import psi4 diff --git a/qcengine/programs/qchem.py b/qcengine/programs/qchem.py index 9945679e5..de8d3daf2 100644 --- a/qcengine/programs/qchem.py +++ b/qcengine/programs/qchem.py @@ -11,7 +11,7 @@ import numpy as np from qcelemental import constants -from qcelemental.models import AtomicInput, AtomicResult, Molecule, Provenance +from qcelemental.models.v2 import AtomicInput, AtomicResult, Molecule, Provenance from qcelemental.molparse import regex from qcelemental.util import parse_version, safe_version, which @@ -25,6 +25,8 @@ class QChemHarness(ProgramHarness): + """Interface for Q-Chem project.""" + _defaults: ClassVar[Dict[str, Any]] = { "name": "QChem", "scratch": True, @@ -305,7 +307,7 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> output_data["stdout"] = outfiles["dispatch.out"] output_data["success"] = True - merged_data = {**input_model.dict(), **output_data} + merged_data = {**input_model.convert_v(2).model_dump(), **output_data} merged_data["extras"]["qcvars"] = qcvars return AtomicResult(**merged_data) diff --git a/qcengine/programs/qcore.py b/qcengine/programs/qcore.py index b8a51ebdb..241079e0d 100644 --- a/qcengine/programs/qcore.py +++ b/qcengine/programs/qcore.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Set import numpy as np -from qcelemental.models import AtomicResult, BasisSet +from qcelemental.models.v2 import AtomicResult, BasisSet from qcelemental.util import parse_version, safe_version, which_import from ..exceptions import InputError, UnknownError @@ -18,7 +18,7 @@ ) if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -31,6 +31,8 @@ def qcore_ao_order_spherical(max_angular_momentum: int) -> Dict[int, List[int]]: class QcoreHarness(ProgramHarness): + """Interface for Qcore/Entos project.""" + _defaults: ClassVar[Dict[str, Any]] = { "name": "Qcore", "scratch": False, @@ -247,6 +249,8 @@ def parse_output(self, output: Dict[str, Any], input_model: "AtomicInput") -> "A class EntosHarness(QcoreHarness): + """Interface for Qcore/Entos project.""" + _defaults: ClassVar[Dict[str, Any]] = { "name": "Entos", "scratch": True, diff --git a/qcengine/programs/qcvar_identities_resources.py b/qcengine/programs/qcvar_identities_resources.py index 24e0257c8..ceb3111bb 100644 --- a/qcengine/programs/qcvar_identities_resources.py +++ b/qcengine/programs/qcvar_identities_resources.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List import numpy as np -from qcelemental.models import AtomicResultProperties +from qcelemental.models.v2 import AtomicResultProperties from .util import PreservingDict diff --git a/qcengine/programs/rdkit.py b/qcengine/programs/rdkit.py index a78c66137..d971aed8e 100644 --- a/qcengine/programs/rdkit.py +++ b/qcengine/programs/rdkit.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict -from qcelemental.models import AtomicResult, Provenance +from qcelemental.models.v2 import AtomicResult, Provenance from qcelemental.util import safe_version, which_import from ..exceptions import InputError @@ -12,12 +12,13 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig class RDKitHarness(ProgramHarness): + """Interface for RDKit project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "RDKit", diff --git a/qcengine/programs/terachem.py b/qcengine/programs/terachem.py index 05f6ed727..31dc9242f 100644 --- a/qcengine/programs/terachem.py +++ b/qcengine/programs/terachem.py @@ -5,7 +5,7 @@ import re from typing import Any, ClassVar, Dict, Optional -from qcelemental.models import AtomicResult, FailedOperation +from qcelemental.models.v2 import AtomicResult, FailedOperation from qcelemental.molparse.regex import DECIMAL, NUMBER from qcelemental.util import parse_version, safe_version, which @@ -17,6 +17,7 @@ class TeraChemHarness(ProgramHarness): + """Interface for TeraChem project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "TeraChem", diff --git a/qcengine/programs/terachem_frontend.py b/qcengine/programs/terachem_frontend.py index 19aa33924..abb86352c 100644 --- a/qcengine/programs/terachem_frontend.py +++ b/qcengine/programs/terachem_frontend.py @@ -15,6 +15,7 @@ class TeraChemFrontEndHarness(TeraChemPBSHarness): """QCEngine Harness for interfacing with the TeraChem Frontend (Protocol Buffer Server + file server)""" + # ?? TODO need _pbs_defaults or inherited? _defaults: ClassVar[Dict[str, Any]] = {**_pbs_defaults, **_fe_defaults} _tcpb_min_version = "0.9.0" _tcpb_client = "TCFrontEndClient" diff --git a/qcengine/programs/terachem_pbs.py b/qcengine/programs/terachem_pbs.py index 1ca944e7e..fdbbb7f4a 100644 --- a/qcengine/programs/terachem_pbs.py +++ b/qcengine/programs/terachem_pbs.py @@ -6,13 +6,13 @@ from os import getenv from typing import TYPE_CHECKING, Any, ClassVar, Dict, Union -from qcelemental.models import AtomicResult, FailedOperation +from qcelemental.models.v2 import AtomicResult, FailedOperation from qcelemental.util import which_import from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig @@ -102,4 +102,7 @@ def compute( tcpb = import_module(self._tcpb_package) with getattr(tcpb, self._tcpb_client)(**self._env_vars) as client: - return client.compute(input_model) + # presumably terachem speaks qcsk.v1 + input_model = input_model.convert_v(1) + return_model = client.compute(input_model) + return return_model.convert_v(2) diff --git a/qcengine/programs/tests/test_molpro.py b/qcengine/programs/tests/test_molpro.py index a2b535330..3873ab5c9 100644 --- a/qcengine/programs/tests/test_molpro.py +++ b/qcengine/programs/tests/test_molpro.py @@ -13,13 +13,15 @@ def test_molpro_output_parser(test_case): # Get output file data data = molpro_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) output = qcng.get_program("molpro", check=False).parse_output(data, inp).dict() output.pop("provenance", None) + output.pop("schema_version", None) - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict() + output_ref = qcel.models.v1.AtomicResult.parse_raw(data["output.json"]).dict() output_ref.pop("provenance", None) + output_ref.pop("schema_version", None) # TODO add `skip` to compare_recursive check = compare_recursive(output_ref, output, forgive={"stdout"}) diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 745a4048b..f53cbf254 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -373,6 +373,7 @@ def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema } resi = checkver_and_convert(resi, request.node.name, "pre") + print("AA2") result = qcng.compute(resi, "nwchem", return_version=retver, raise_error=True) result = checkver_and_convert(result, request.node.name, "post") # , excuse_as_v2=True) diff --git a/qcengine/programs/tests/test_qchem.py b/qcengine/programs/tests/test_qchem.py index a7d1a0661..397bf3e76 100644 --- a/qcengine/programs/tests/test_qchem.py +++ b/qcengine/programs/tests/test_qchem.py @@ -17,6 +17,7 @@ "root.provenance.creator", "root.extras", "root.molecule.extras", + "root.schema_version", # TODO temp until rearrangement befuddles ] @@ -25,18 +26,21 @@ def test_qchem_output_parser(test_case): # Get output file data data = qchem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) outfiles = qcel.util.deserialize(data["outfiles.msgpack"], "msgpack-ext") output = qcng.get_program("qchem", check=False).parse_output(outfiles, inp).dict() output.pop("provenance", None) - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict() + output_ref = qcel.models.v1.AtomicResult.parse_raw(data["output.json"]).dict() output_ref.pop("provenance", None) output_ref.pop("extras", None) output.pop("extras", None) - check, message = compare_recursive(output_ref, output, return_message=True) + # Modify ref to trim down total data as a molecule is now sparse + output_ref["molecule"] = {k: v for k, v in output_ref["molecule"].items() if k in output["molecule"]} + + check, message = compare_recursive(output_ref, output, return_message=True, forgive=qchem_forgive) assert check, message @@ -45,7 +49,7 @@ def test_qchem_input_formatter(test_case): # Get input file data data = qchem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) # TODO add actual comparison of generated input file input_file = qcng.get_program("qchem", check=False).build_input(inp, qcng.get_config()) @@ -57,7 +61,7 @@ def test_qchem_input_formatter_template(test_case): # Get input file data data = qchem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) # TODO add actual comparison of generated input file input_file = qcng.get_program("qchem", check=False).build_input(inp, qcng.get_config(), template="Test template") @@ -128,7 +132,7 @@ def test_qchem_logfile_parser(test_case): output = qcng.get_program("qchem", check=False).parse_logfile(outfiles).dict() output["stdout"] = None - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict() + output_ref = qcel.models.v1.AtomicResult.parse_raw(data["output.json"]).dict() for key in list(output["provenance"].keys()): if key not in output_ref["provenance"]: output["provenance"].pop(key) @@ -151,7 +155,7 @@ def test_qchem_logfile_parser_qcscr(test_case): output = qcng.get_program("qchem", check=False).parse_logfile(outfiles).dict() output["stdout"] = None - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict() + output_ref = qcel.models.v1.AtomicResult.parse_raw(data["output.json"]).dict() for key in list(output["provenance"].keys()): if key not in output_ref["provenance"]: output["provenance"].pop(key) diff --git a/qcengine/programs/tests/test_terachem.py b/qcengine/programs/tests/test_terachem.py index a778744c7..8558b301e 100644 --- a/qcengine/programs/tests/test_terachem.py +++ b/qcengine/programs/tests/test_terachem.py @@ -13,20 +13,20 @@ def test_terachem_output_parser(test_case): # Get output file data data = terachem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) output = qcng.get_program("terachem", check=False).parse_output(data, inp).dict() - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]).dict() + output_ref = qcel.models.v1.AtomicResult.parse_raw(data["output.json"]).dict() # Forgiving molecule since it is now sparse - assert compare_recursive(output_ref, output, forgive={"stdout", "provenance", "molecule"}) + assert compare_recursive(output_ref, output, forgive={"stdout", "provenance", "molecule", "schema_version"}) @pytest.mark.parametrize("test_case", terachem_info.list_test_cases()) def test_terachem_input_formatter(test_case): # Get input file data data = terachem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = qcel.models.v1.AtomicInput.parse_raw(data["input.json"]) # TODO add actual comparison of generated input file input_file = qcng.get_program("terachem", check=False).build_input(inp, qcng.get_config()) diff --git a/qcengine/programs/torchani.py b/qcengine/programs/torchani.py index c8ea19b0a..920c8b7df 100644 --- a/qcengine/programs/torchani.py +++ b/qcengine/programs/torchani.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict -from qcelemental.models import AtomicResult, Provenance +from qcelemental.models.v2 import AtomicResult, Provenance from qcelemental.util import parse_version, safe_version, which_import from ..exceptions import InputError, ResourceError @@ -12,12 +12,13 @@ from .model import ProgramHarness if TYPE_CHECKING: - from qcelemental.models import AtomicInput + from qcelemental.models.v2 import AtomicInput from ..config import TaskConfig class TorchANIHarness(ProgramHarness): + """Interface for TorchANI project.""" _CACHE = {} diff --git a/qcengine/programs/turbomole/runner.py b/qcengine/programs/turbomole/runner.py index d7b7b1d53..ecda4b9c6 100644 --- a/qcengine/programs/turbomole/runner.py +++ b/qcengine/programs/turbomole/runner.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any, ClassVar, Dict, Optional, Tuple -from qcelemental.models import AtomicResult, BasisSet, Provenance +from qcelemental.models.v2 import AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which from ...exceptions import InputError @@ -20,6 +20,7 @@ class TurbomoleHarness(ProgramHarness): + """Interface for Turbomole project.""" _defaults: ClassVar[Dict[str, Any]] = { "name": "Turbomole", diff --git a/qcengine/programs/util/ao_reordering.py b/qcengine/programs/util/ao_reordering.py index a86ad0567..65ac38ba6 100644 --- a/qcengine/programs/util/ao_reordering.py +++ b/qcengine/programs/util/ao_reordering.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List import numpy as np -from qcelemental.models import BasisSet +from qcelemental.models.v2 import BasisSet def cca_ao_order_spherical(max_angular_momentum: int) -> Dict[int, List[int]]: diff --git a/qcengine/programs/xtb.py b/qcengine/programs/xtb.py index 7cf6b6196..1caf0e1fd 100644 --- a/qcengine/programs/xtb.py +++ b/qcengine/programs/xtb.py @@ -11,7 +11,7 @@ from typing import Any, ClassVar, Dict -from qcelemental.models import AtomicInput, AtomicResult +from qcelemental.models.v2 import AtomicInput, AtomicResult from qcelemental.util import safe_version, which_import from ..config import TaskConfig @@ -66,7 +66,9 @@ def compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult: from xtb.qcschema.harness import run_qcschema # Run the Harness + input_data = input_data.convert_v(1) output = run_qcschema(input_data) + output = output.convert_v(2) # Make sure all keys from the initial input spec are sent along output.extras.update(input_data.extras) diff --git a/qcengine/stock_mols.py b/qcengine/stock_mols.py index 90592b041..b8250d40f 100644 --- a/qcengine/stock_mols.py +++ b/qcengine/stock_mols.py @@ -4,8 +4,6 @@ import copy -from qcelemental.models import Molecule - _test_mols = { "hydrogen": { "symbols": ["H", "H"], @@ -193,10 +191,12 @@ def get_molecule(name, *, return_dict: bool = False): """ Returns a QC JSON representation of a test molecule. """ + import qcelemental + if name not in _test_mols: raise KeyError("Molecule name '{}' not found".format(name)) if return_dict: return copy.deepcopy(_test_mols[name]) else: - return Molecule(**copy.deepcopy(_test_mols[name])) + return qcelemental.models.v1.Molecule(**copy.deepcopy(_test_mols[name])) diff --git a/qcengine/testing.py b/qcengine/testing.py index ccc09b608..5df7f89d2 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -221,18 +221,25 @@ def schema_versions(request): elif request.param == "to_v2": return qcel.models.v1, 2, qcel.models.v2 elif request.param == "as_v2": - return ( - qcel.models.v2, - 2, - qcel.models.v2, - ) # TODO with dict-in and dict-out and models indiscriminable and defaulting to v1 - # the as_v2 is often not reliable, so paper over it with 2 for now. return to -1 when fixed. + # TODO with dict-in and dict-out and models indiscriminable and defaulting to v1 + # the as_v2 is often not reliable, so paper over it with 2 for now. return to -1 when fixed. + return (qcel.models.v2, 2, qcel.models.v2) elif request.param == "to_v1": return qcel.models.v2, 1, qcel.models.v1 else: return qcel.models, -1, qcel.models +@pytest.fixture(scope="function", params=["as_v1", "as_v2"]) +def schema_versions2(request): + if request.param == "as_v1": + return qcel.models.v1, -1, qcel.models.v1 + elif request.param == "as_v2": + # TODO with dict-in and dict-out and models indiscriminable and defaulting to v1 + # the as_v2 is often not reliable, so paper over it with 2 for now. return to -1 when fixed. + return (qcel.models.v2, 2, qcel.models.v2) + + def checkver_and_convert(mdl, tnm, prepost, vercheck: bool = True, cast_dict_as=None): import json diff --git a/qcengine/tests/test_cli.py b/qcengine/tests/test_cli.py index 60d35a912..2161c9cc6 100644 --- a/qcengine/tests/test_cli.py +++ b/qcengine/tests/test_cli.py @@ -7,7 +7,7 @@ import qcelemental from qcengine import cli, get_molecule, util -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions2, using def run_qcengine_cli(args: List[str], stdin: str = None) -> str: @@ -56,14 +56,14 @@ def test_info(): assert output in default_output -# TODO add schema_versions when psi4 can handle v2 @using("psi4") -def test_run_psi4(tmp_path): +def test_run_psi4(tmp_path, schema_versions2, request): """Tests qcengine run with psi4 and JSON input""" - models = qcelemental.models.v1 + models, _, _ = schema_versions2 def check_result(stdout): output = json.loads(stdout) + output = checkver_and_convert(output, request.node.name, "post") assert output["provenance"]["creator"].lower() == "psi4" assert output["success"] is True @@ -73,25 +73,31 @@ def check_result(stdout): model={"method": "hf", "basis": "6-31G"}, ) - args = ["run", "psi4", inp.json()] + inp = checkver_and_convert(inp, request.node.name, "pre") + args = ["run", "psi4", inp.model_dump_json()] check_result(run_qcengine_cli(args)) args = ["run", "psi4", os.path.join(tmp_path, "input.json")] - with util.disk_files({"input.json": inp.json()}, {}, cwd=tmp_path): + with util.disk_files({"input.json": inp.model_dump_json()}, {}, cwd=tmp_path): check_result(run_qcengine_cli(args)) args = ["run", "psi4", "-"] - check_result(run_qcengine_cli(args, stdin=inp.json())) + # model_dump_json() works on v1 or v2 (see above). below tests that json() still works on v1. + if "as_v1" in request.node.name: + check_result(run_qcengine_cli(args, stdin=inp.json())) + else: + check_result(run_qcengine_cli(args, stdin=inp.model_dump_json())) @using("geometric") @using("psi4") -def test_run_procedure(tmp_path): +def test_run_procedure(tmp_path, schema_versions2, request): """Tests qcengine run-procedure with geometric, psi4, and JSON input""" - models = qcelemental.models.v1 + models, _, _ = schema_versions2 def check_result(stdout): output = json.loads(stdout) + output = checkver_and_convert(output, request.node.name, "post", cast_dict_as="OptimizationResult") assert output["provenance"]["creator"].lower() == "geometric" assert output["success"] is True @@ -102,12 +108,17 @@ def check_result(stdout): } inp = models.OptimizationInput(**inp) - args = ["run-procedure", "geometric", inp.json()] + inp = checkver_and_convert(inp, request.node.name, "pre") + args = ["run-procedure", "geometric", inp.model_dump_json()] check_result(run_qcengine_cli(args)) args = ["run-procedure", "geometric", os.path.join(tmp_path, "input.json")] - with util.disk_files({"input.json": inp.json()}, {}, cwd=tmp_path): + with util.disk_files({"input.json": inp.model_dump_json()}, {}, cwd=tmp_path): check_result(run_qcengine_cli(args)) - args = ["run-procedure", "geometric", inp.json()] - check_result(run_qcengine_cli(args, stdin=inp.json())) + args = ["run-procedure", "geometric", inp.model_dump_json()] + check_result(run_qcengine_cli(args, stdin=inp.model_dump_json())) + + # try unified route + args = ["run", "geometric", inp.model_dump_json()] + check_result(run_qcengine_cli(args, stdin=inp.model_dump_json())) diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index 6e3f56b0c..f042ebfd6 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -2,6 +2,8 @@ Tests the DQM compute dispatch module """ +import warnings + import pytest import qcelemental as qcel @@ -43,9 +45,11 @@ def test_geometric_psi4(input_data, optimizer, ncores, schema_versions, request) } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure( - input_data, optimizer, raise_error=True, task_config=task_config, return_version=retver - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FutureWarning) + ret = qcng.compute_procedure( + input_data, optimizer, raise_error=True, task_config=task_config, return_version=retver + ) ret = checkver_and_convert(ret, request.node.name, "post") assert 10 > len(ret.trajectory) > 1 @@ -79,9 +83,7 @@ def test_geometric_local_options(input_data, schema_versions, request): # Set some extremely large number to test input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure( - input_data, "geometric", raise_error=True, task_config={"memory": "5000"}, return_version=retver - ) + ret = qcng.compute(input_data, "geometric", raise_error=True, task_config={"memory": "5000"}, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert pytest.approx(ret.trajectory[0].provenance.memory, 1) == 4900 @@ -103,7 +105,7 @@ def test_geometric_stdout(input_data, schema_versions, request): input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "geometric", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -122,7 +124,7 @@ def test_berny_stdout(input_data, schema_versions, request): input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "berny", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "berny", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -142,7 +144,7 @@ def test_berny_failed_gradient_computation(input_data, schema_versions, request) input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "berny", raise_error=False, return_version=retver) + ret = qcng.compute(input_data, "berny", raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert isinstance(ret, (qcel.models.v1.FailedOperation, qcel.models.v2.FailedOperation)) @@ -164,7 +166,7 @@ def test_geometric_rdkit_error(input_data, schema_versions, request): input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "geometric", return_version=retver) + ret = qcng.compute(input_data, "geometric", return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.success is False @@ -184,7 +186,7 @@ def test_optimization_protocols(input_data, schema_versions, request): input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "geometric", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success, ret.error.error_message @@ -211,9 +213,7 @@ def test_geometric_retries(failure_engine, input_data, schema_versions, request) input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure( - input_data, "geometric", task_config={"ncores": 13}, raise_error=True, return_version=retver - ) + ret = qcng.compute(input_data, "geometric", task_config={"ncores": 13}, raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -226,9 +226,7 @@ def test_geometric_retries(failure_engine, input_data, schema_versions, request) # Ensure we still fail failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"] # Iter 1 # Iter 2 - ret = qcng.compute_procedure( - input_data, "geometric", task_config={"ncores": 13, "retries": 1}, return_version=retver - ) + ret = qcng.compute(input_data, "geometric", task_config={"ncores": 13, "retries": 1}, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.success is False @@ -304,7 +302,7 @@ def test_geometric_generic(input_data, program, model, bench, schema_versions, r } input_data = checkver_and_convert(input_data, request.node.name, "pre", cast_dict_as="OptimizationInput") - ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "geometric", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -338,7 +336,7 @@ def test_nwchem_relax(linopt, schema_versions, request): # Run the relaxation input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "nwchemdriver", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "nwchemdriver", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert 10 > len(ret.trajectory) > 1 @@ -365,16 +363,12 @@ def test_nwchem_restart(tmpdir, schema_versions, request): local_opts = {"scratch_messy": True, "scratch_directory": str(tmpdir)} input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure( - input_data, "nwchemdriver", task_config=local_opts, raise_error=False, return_version=retver - ) + ret = qcng.compute(input_data, "nwchemdriver", task_config=local_opts, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert not ret.success # Run it again, which should converge - new_ret = qcng.compute_procedure( - input_data, "nwchemdriver", task_config=local_opts, raise_error=True, return_version=retver - ) + new_ret = qcng.compute(input_data, "nwchemdriver", task_config=local_opts, raise_error=True, return_version=retver) new_ret = checkver_and_convert(new_ret, request.node.name, "post") assert new_ret.success @@ -406,7 +400,7 @@ def test_torsiondrive_generic(schema_versions, request): ) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, "torsiondrive", raise_error=True, return_version=retver) + ret = qcng.compute(input_data, "torsiondrive", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.error is None @@ -452,7 +446,7 @@ def test_optimization_mrchem(input_data, optimizer, schema_versions, request): input_data = models.OptimizationInput(**input_data) input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute_procedure(input_data, optimizer, raise_error=True, return_version=retver) + ret = qcng.compute(input_data, optimizer, raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert 10 > len(ret.trajectory) > 1 diff --git a/qcengine/tests/test_program_utils.py b/qcengine/tests/test_program_utils.py index 573e0747a..8e4d0cc3e 100644 --- a/qcengine/tests/test_program_utils.py +++ b/qcengine/tests/test_program_utils.py @@ -52,6 +52,6 @@ def test_check_procedure_avail(procedure): def test_procedure_avail_bounce(): with pytest.raises(qcng.exceptions.InputError) as exc: - qcng.compute_procedure({}, "bad_program", raise_error=True) + qcng.compute({}, "bad_program", raise_error=True) assert "not registered" in str(exc.value) diff --git a/qcengine/util.py b/qcengine/util.py index 3b1048653..75ac94b2c 100644 --- a/qcengine/util.py +++ b/qcengine/util.py @@ -19,7 +19,7 @@ from typing import Any, BinaryIO, Dict, List, Optional, TextIO, Tuple, Union import pydantic -from qcelemental.models import AtomicResult, FailedOperation, OptimizationResult +import qcelemental from qcengine.config import TaskConfig @@ -219,17 +219,22 @@ def handle_output_metadata( ret = output_data.__class__(**output_fusion) else: # Should only be reachable on failures - ret = FailedOperation( + model = { + -1: qcelemental.models.v1.FailedOperation, + 1: qcelemental.models.v1.FailedOperation, + 2: qcelemental.models.v2.FailedOperation, + }[convert_version] + ret = model( success=output_fusion.pop("success", False), error=output_fusion.pop("error"), input_data=output_fusion ) if convert_version > 0: ret = ret.convert_v(convert_version) + returned_version = getattr(ret, "schema_version", "not a model") if return_dict: - return json.loads( - ret.model_dump_json() - ) # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals + # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals + return json.loads(ret.model_dump_json()) else: return ret