From 1a2dfb26781f79ac3191dabb06a9cddf03478649 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Mon, 23 Sep 2024 16:16:28 -0400 Subject: [PATCH 1/7] start 5 modes testing more tests fix import and checks fix typo try xtb xtb and mrchem sdftd3 more tests few fixes? test_programs all but stdsuite stdsuite fix alignment tests docker and prop docker docker2 docker3 docker4 docker5 docker6 docker7 docker8 back and np fixed tidy up --- .github/workflows/CI.yml | 14 +- .pre-commit-config.yaml | 2 +- .../programs/tests/standard_suite_runner.py | 9 +- qcengine/programs/tests/test_adcc.py | 17 +- qcengine/programs/tests/test_alignment.py | 25 +- .../programs/tests/test_canonical_config.py | 48 ++- .../programs/tests/test_canonical_fields.py | 15 +- qcengine/programs/tests/test_dftd3_mp2d.py | 70 +++- qcengine/programs/tests/test_dftd4.py | 49 ++- qcengine/programs/tests/test_ghost.py | 34 +- qcengine/programs/tests/test_molpro.py | 10 +- qcengine/programs/tests/test_mrchem.py | 35 +- qcengine/programs/tests/test_nwchem.py | 160 +++++---- qcengine/programs/tests/test_programs.py | 319 ++++++++++++++---- qcengine/programs/tests/test_qchem.py | 25 +- qcengine/programs/tests/test_qcore.py | 21 +- qcengine/programs/tests/test_sdftd3.py | 58 ++-- .../programs/tests/test_standard_suite.py | 43 ++- .../tests/test_standard_suite_ccsd(t).py | 32 +- .../programs/tests/test_standard_suite_hf.py | 33 +- qcengine/programs/tests/test_terachem.py | 11 +- qcengine/programs/tests/test_turbomole.py | 53 ++- qcengine/programs/tests/test_xtb.py | 124 ++++--- qcengine/stock_mols.py | 7 +- qcengine/testing.py | 86 ++++- qcengine/tests/test_cli.py | 15 +- qcengine/tests/test_harness_canonical.py | 68 ++-- qcengine/tests/test_procedures.py | 152 ++++++--- qcengine/tests/test_utils.py | 7 +- qcengine/util.py | 2 + 30 files changed, 1122 insertions(+), 422 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b83fbf959..76c21e0c1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -63,7 +63,7 @@ jobs: # label: QCore # runs-on: ubuntu-latest # pytest: "" - # Note: removed Sep 2024 b/c too hard to reconcile w/pyd v2 + # Note: removed Sep 2024 b/c too hard to reconcile w/pyd v2. Manby approves. - conda-env: nwchem python-version: 3.8 @@ -114,6 +114,12 @@ jobs: runs-on: ubuntu-latest pytest: "" + #- conda-env: nwchem + # python-version: "3.10" + # label: TeraChem + # runs-on: ubuntu-20.04 + # pytest: "" + name: "🐍 ${{ matrix.cfg.python-version }} • ${{ matrix.cfg.label }} • ${{ matrix.cfg.runs-on }}" runs-on: ${{ matrix.cfg.runs-on }} @@ -144,11 +150,15 @@ jobs: run: | qcore --accept-license + +#docker.io/mtzgroup/terachem:latest +#Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] + - 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_shimclasses' --no-deps + python -m pip install 'git+https://github.com/loriab/QCElemental.git@csse_pyd2_converterclasses' --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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af7afd9c3..91603ea6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: black language_version: python3.12 args: [--line-length=120] - exclude: 'test_|versioneer.py|examples/.*|docs/.*|devtools/.*' + exclude: 'versioneer.py|examples/.*|docs/.*|devtools/.*' - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: diff --git a/qcengine/programs/tests/standard_suite_runner.py b/qcengine/programs/tests/standard_suite_runner.py index 95853e3f9..a6060d846 100644 --- a/qcengine/programs/tests/standard_suite_runner.py +++ b/qcengine/programs/tests/standard_suite_runner.py @@ -4,12 +4,12 @@ import numpy as np import pytest -from qcelemental.models import AtomicInput from qcelemental.molutil import compute_scramble from qcelemental.testing import compare, compare_values import qcengine as qcng from qcengine.programs.util import mill_qcvars +from qcengine.testing import checkver_and_convert from .standard_suite_contracts import * from .standard_suite_ref import answer_hash, std_suite @@ -17,7 +17,7 @@ pp = pprint.PrettyPrinter(width=120) -def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): +def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame, models): qcprog = inp["call"] qc_module_in = inp["qc_module"] # returns ""|"-" # input-specified routing @@ -124,7 +124,7 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): # <<< Prepare Calculation and Call API >>> - atin = AtomicInput( + atin = models.AtomicInput( **{ "molecule": subject, "driver": driver, @@ -142,13 +142,16 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame): if "error" in inp: errtype, errmatch, reason = inp["error"] with pytest.raises(errtype) as e: + atin = checkver_and_convert(atin, tnm, "pre") qcng.compute(atin, qcprog, raise_error=True, return_dict=True, task_config=local_options) assert re.search(errmatch, str(e.value)), f"Not found: {errtype} '{errmatch}' in {e.value}" # _recorder(qcprog, qc_module_in, driver, method, reference, fcae, scf_type, corl_type, "error", "nyi: " + reason) return + atin = checkver_and_convert(atin, tnm, "pre") wfn = qcng.compute(atin, qcprog, raise_error=True, task_config=local_options) + wfn = checkver_and_convert(wfn, tnm, "post") print("WFN") pp.pprint(wfn.dict()) diff --git a/qcengine/programs/tests/test_adcc.py b/qcengine/programs/tests/test_adcc.py index a9625bfa1..97236e704 100644 --- a/qcengine/programs/tests/test_adcc.py +++ b/qcengine/programs/tests/test_adcc.py @@ -5,26 +5,29 @@ from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def h2o(): - return qcel.models.Molecule.from_data( - """ +def h2o_data(): + return """ O 0.0 0.000 -0.129 H 0.0 -1.494 1.027 H 0.0 1.494 1.027 """ - ) @using("adcc") -def test_run(h2o): - inp = qcel.models.AtomicInput( +def test_run(h2o_data, schema_versions, request): + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + + inp = models.AtomicInput( molecule=h2o, driver="properties", model={"method": "adc2", "basis": "sto-3g"}, keywords={"n_singlets": 3} ) + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, "adcc", raise_error=True, local_options={"ncores": 1}, return_dict=True) + ret = checkver_and_convert(ret, request.node.name, "post") ref_excitations = np.array([0.0693704245883876, 0.09773854881340478, 0.21481589246935925]) ref_hf_energy = -74.45975898670224 diff --git a/qcengine/programs/tests/test_alignment.py b/qcengine/programs/tests/test_alignment.py index 8870d5241..f3f7f29dc 100644 --- a/qcengine/programs/tests/test_alignment.py +++ b/qcengine/programs/tests/test_alignment.py @@ -3,21 +3,22 @@ import qcelemental as qcel from qcengine.programs.tests.standard_suite_ref import std_molecules, std_refs -from qcengine.testing import using +from qcengine.testing import schema_versions, using from .standard_suite_runner import runner_asserter from .test_standard_suite import _processor @pytest.fixture -def clsd_open_pmols(): +def clsd_open_pmols(schema_versions): + models, _ = schema_versions frame_not_important = { - name[:-4]: qcel.models.Molecule.from_data(smol, name=name[:-4]) + name[:-4]: models.Molecule.from_data(smol, name=name[:-4]) for name, smol in std_molecules.items() if name.endswith("-xyz") } frame_part_of_spec = { - name[:-4] + "-fixed": qcel.models.Molecule.from_data(smol + "\nno_com\nno_reorient\n", name=name[:-4]) + name[:-4] + "-fixed": models.Molecule.from_data(smol + "\nno_com\nno_reorient\n", name=name[:-4]) for name, smol in std_molecules.items() if name.endswith("-xyz") } @@ -92,7 +93,19 @@ def clsd_open_pmols(): # yapf: enable ], ) -def test_hf_alignment(inp, scramble, frame, driver, basis, subjects, clsd_open_pmols, request): +def test_hf_alignment(inp, scramble, frame, driver, basis, subjects, clsd_open_pmols, request, schema_versions): runner_asserter( - *_processor(inp, "", basis, subjects, clsd_open_pmols, request, driver, "hf", scramble=scramble, frame=frame) + *_processor( + inp, + "", + basis, + subjects, + clsd_open_pmols, + request, + schema_versions[0], + driver, + "hf", + scramble=scramble, + frame=frame, + ) ) diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index 72023850a..9b8237af3 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -8,10 +8,9 @@ from pathlib import Path import pytest -from qcelemental.models import AtomicInput import qcengine as qcng -from qcengine.testing import has_program, using +from qcengine.testing import checkver_and_convert, has_program, schema_versions, using _canonical_methods = [ # needs attn ("adcc", {"method": "adc2", "basis": "6-31G"}, {"n_triplets": 3}), @@ -38,14 +37,15 @@ ] -def _get_molecule(program, method): +def _get_molecule(program, method, molcls): if program in ["openmm", "terachem_pbs"]: - return qcng.get_molecule("water") + dmol = qcng.get_molecule("water", return_dict=True) elif program == "gamess" and method == "ccsd(t)": - return qcng.get_molecule("water") + dmol = qcng.get_molecule("water", return_dict=True) else: - return qcng.get_molecule("hydrogen") + dmol = qcng.get_molecule("hydrogen", return_dict=True) + return molcls(**dmol) @pytest.mark.parametrize( "memory_trickery", @@ -64,7 +64,7 @@ def _get_molecule(program, method): ], ) @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_local_options_memory_gib(program, model, keywords, memory_trickery, request): +def test_local_options_memory_gib(program, model, keywords, memory_trickery, schema_versions, request): """Ensure memory handling implemented in harness (if applicable). For available harnesses, run minimal calc at specific total node memory, both through runtime @@ -80,11 +80,13 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req * If this test doesn't work, implement or adjust ``config.memory`` in your harness. """ + models, _ = schema_versions + if not has_program(program): pytest.skip(f"Program '{program}' not found.") harness = qcng.get_program(program) - molecule = _get_molecule(program, model["method"]) + molecule = _get_molecule(program, model["method"], models.Molecule) addl_keywords = memory_trickery.get(program, memory_trickery) use_keywords = {**keywords, **addl_keywords} @@ -102,8 +104,12 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req # << Run - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=use_keywords) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=use_keywords) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = checkver_and_convert(ret, request.node.name, "post") + pprint.pprint(ret.dict(), width=200) assert ret.success is True @@ -126,7 +132,7 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_local_options_scratch(program, model, keywords): +def test_local_options_scratch(program, model, keywords, schema_versions, request): """Ensure scratch handling implemented in harness (if applicable). For available harnesses, run minimal calc at specific scratch directory name (randomly generated @@ -146,11 +152,13 @@ def test_local_options_scratch(program, model, keywords): ``config.scratch_messy`` in your harness. """ + models, _ = schema_versions + if not has_program(program): pytest.skip(f"Program '{program}' not found.") harness = qcng.get_program(program) - molecule = _get_molecule(program, model["method"]) + molecule = _get_molecule(program, model["method"], models.Molecule) # << Config @@ -166,8 +174,12 @@ def test_local_options_scratch(program, model, keywords): # << Run - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = checkver_and_convert(ret, request.node.name, "post") + pprint.pprint(ret.dict(), width=200) assert ret.success is True @@ -212,7 +224,7 @@ def test_local_options_scratch(program, model, keywords): @pytest.mark.parametrize("ncores", [1, 3]) @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_local_options_ncores(program, model, keywords, ncores): +def test_local_options_ncores(program, model, keywords, ncores, schema_versions, request): """Ensure multithreading implemented in harness (if applicable) or multithreaded runs don't break harness (if inapplicable). @@ -228,11 +240,13 @@ def test_local_options_ncores(program, model, keywords, ncores): * If this test doesn't work, implement or adjust ``config.ncores`` in your harness. """ + models, _ = schema_versions + if not has_program(program): pytest.skip(f"Program '{program}' not found.") harness = qcng.get_program(program) - molecule = _get_molecule(program, model["method"]) + molecule = _get_molecule(program, model["method"], models.Molecule) # << Config @@ -246,8 +260,12 @@ def test_local_options_ncores(program, model, keywords, ncores): # << Run - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = checkver_and_convert(ret, request.node.name, "post") + pprint.pprint(ret.dict(), width=200) assert ret.success is True diff --git a/qcengine/programs/tests/test_canonical_fields.py b/qcengine/programs/tests/test_canonical_fields.py index ba4f809ba..fd641ac2b 100644 --- a/qcengine/programs/tests/test_canonical_fields.py +++ b/qcengine/programs/tests/test_canonical_fields.py @@ -3,17 +3,16 @@ import pytest import qcelemental as qcel -from qcelemental.models import AtomicInput import qcengine as qcng -from qcengine.testing import has_program, using +from qcengine.testing import checkver_and_convert, has_program, schema_versions, using from .test_canonical_config import _canonical_methods, _get_molecule @pytest.mark.parametrize("native", ["none", "input", "all"]) @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_protocol_native(program, model, keywords, native): +def test_protocol_native(program, model, keywords, native, schema_versions, request): """Ensure native_files protocol implemented in harness. For harnesses, run minimal gradient calc with different protocol settings; check expected @@ -26,11 +25,13 @@ def test_protocol_native(program, model, keywords, native): * If this test doesn't work, implement or adjust ``native_files`` in your harness. """ + models, _ = schema_versions + if not has_program(program): pytest.skip(f"Program '{program}' not found.") harness = qcng.get_program(program) - molecule = _get_molecule(program, model["method"]) + molecule = _get_molecule(program, model["method"], models.Molecule) # << Config @@ -47,8 +48,12 @@ def test_protocol_native(program, model, keywords, native): # << Run - inp = AtomicInput(molecule=molecule, driver="gradient", model=model, keywords=keywords, protocols=protocols) + inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model, keywords=keywords, protocols=protocols) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True, local_options=config.dict()) + ret = checkver_and_convert(ret, request.node.name, "post") + pprint.pprint(ret.dict(), width=200) assert ret.success is True diff --git a/qcengine/programs/tests/test_dftd3_mp2d.py b/qcengine/programs/tests/test_dftd3_mp2d.py index ee8c2e782..fe83f67d7 100644 --- a/qcengine/programs/tests/test_dftd3_mp2d.py +++ b/qcengine/programs/tests/test_dftd3_mp2d.py @@ -4,20 +4,23 @@ import numpy as np import pytest import qcelemental as qcel -from qcelemental.models import AtomicInput from qcelemental.testing import compare, compare_recursive, compare_values, tnm import qcengine as qcng from qcengine.programs import empirical_dispersion_resources -from qcengine.testing import is_program_new_enough, using +from qcengine.testing import checkver_and_convert, is_program_new_enough, schema_versions, using @using("dftd3") @pytest.mark.parametrize("method", ["b3lyp-d3", "b3lyp-d3m", "b3lyp-d3bj", "b3lyp-d3mbj"]) -def test_dftd3_task(method): - json_data = {"molecule": qcng.get_molecule("eneyne"), "driver": "energy", "model": {"method": method}} +def test_dftd3_task(method, schema_versions, request): + models, _ = schema_versions + json_data = {"molecule": models.Molecule(**qcng.get_molecule("eneyne", return_dict=True)), "driver": "energy", "model": {"method": method}} + + json_data = checkver_and_convert(json_data, request.node.name, "pre") ret = qcng.compute(json_data, "dftd3", raise_error=True, return_dict=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret["driver"] == "energy" assert "provenance" in ret @@ -30,9 +33,11 @@ def test_dftd3_task(method): @using("dftd3") -def test_dftd3_error(): +def test_dftd3_error(schema_versions, request): + models, _ = schema_versions + json_data = { - "molecule": qcng.get_molecule("eneyne"), + "molecule": models.Molecule(**qcng.get_molecule("eneyne", return_dict=True)), "driver": "energy", "model": {"method": "b3lyp-d3(bj)"}, "keywords": {}, @@ -42,6 +47,8 @@ def test_dftd3_error(): with pytest.raises(qcng.exceptions.InputError) as exc: input_data = json_data.copy() input_data["driver"] = "properties" + + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "dftd3", raise_error=True) assert "Driver properties not implemented" in str(exc.value) @@ -50,6 +57,8 @@ def test_dftd3_error(): with pytest.raises(qcng.exceptions.InputError) as exc: input_data = json_data.copy() input_data["model"]["method"] = "b3lyp-quadD" + + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "dftd3", raise_error=True) assert "correction level" in str(exc.value) @@ -1514,7 +1523,9 @@ def test_dftd3__from_arrays__supplement(): @using("dftd3") -def test_3(): +def test_3(schema_versions, request): + models, _ = schema_versions + sys = qcel.molparse.from_string(seneyne)["qm"] resinp = { @@ -1525,7 +1536,10 @@ def test_3(): "model": {"method": "b3lyp"}, "keywords": {"level_hint": "d3bj"}, } + + resinp = checkver_and_convert(resinp, request.node.name, "pre") res = qcng.compute(resinp, "dftd3", raise_error=True) + res = checkver_and_convert(res, request.node.name, "post") res = res.dict() # res = dftd3.run_dftd3_from_arrays(molrec=sys, name_hint='b3lyp', level_hint='d3bj') @@ -1634,7 +1648,9 @@ def test_qcdb__energy_d3(): ({"parent": "ne", "name": "mp2d-mp2-dmp2", "subject": "atom", "lbl": "MP2-DMP2"}), ], ) -def test_mp2d__run_mp2d__2body(inp, subjects, request): +def test_mp2d__run_mp2d__2body(inp, subjects, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1652,7 +1668,9 @@ def test_mp2d__run_mp2d__2body(inp, subjects, request): "model": {"method": inp["name"]}, "keywords": {}, } + resinp = checkver_and_convert(resinp, request.node.name, "pre") jrec = qcng.compute(resinp, "mp2d", raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() # assert len(jrec['extras']['qcvars']) == 8 @@ -1726,7 +1744,9 @@ def test_mp2d__run_mp2d__2body(inp, subjects, request): ], # fmt: on ) -def test_dftd3__run_dftd3__2body(inp, program, subjects, request): +def test_dftd3__run_dftd3__2body(inp, program, subjects, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1736,12 +1756,14 @@ def test_dftd3__run_dftd3__2body(inp, program, subjects, request): else: mol = subject.to_schema(dtype=2) - atin = AtomicInput( + atin = models.AtomicInput( molecule=mol, driver="gradient", **inp["qcsk"], ) + atin = checkver_and_convert(atin, request.node.name, "pre") jrec = qcng.compute(atin, program, raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() pprint.pprint(jrec) @@ -1790,7 +1812,9 @@ def test_dftd3__run_dftd3__2body(inp, program, subjects, request): ], # fmt: on ) -def test_dftd3__run_dftd3__2body_error(inp, subjects, request): +def test_dftd3__run_dftd3__2body_error(inp, subjects, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1802,12 +1826,14 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, request): program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( + atin = models.AtomicInput( molecule=mol, driver="gradient", **inp["qcsk"], ) + atin = checkver_and_convert(atin, request.node.name, "pre") jrec = qcng.compute(atin, program, raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() with pytest.raises(AssertionError) as exc: @@ -1837,7 +1863,9 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, request): ({"parent": "ne", "name": "d3-atmgr", "subject": "atom", "lbl": "ATM"}), ], ) -def test_dftd3__run_dftd3__3body(inp, subjects, request): +def test_dftd3__run_dftd3__3body(inp, subjects, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1855,7 +1883,9 @@ def test_dftd3__run_dftd3__3body(inp, subjects, request): "model": {"method": inp["name"]}, "keywords": {}, } + resinp = checkver_and_convert(resinp, request.node.name, "pre") jrec = qcng.compute(resinp, "dftd3", raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() assert len(jrec["extras"]["qcvars"]) == 8 @@ -1901,7 +1931,9 @@ def test_dftd3__run_dftd3__3body(inp, subjects, request): ), ], ) -def test_sapt_pairwise(inp, program, extrakw, subjects, request): +def test_sapt_pairwise(inp, program, extrakw, subjects, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] expected_pairwise = pref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1911,7 +1943,7 @@ def test_sapt_pairwise(inp, program, extrakw, subjects, request): else: mol = subject.to_schema(dtype=2) - atin = AtomicInput( + atin = models.AtomicInput( molecule=mol, driver="energy", model={"method": inp["lbl"]}, @@ -1920,7 +1952,9 @@ def test_sapt_pairwise(inp, program, extrakw, subjects, request): **extrakw, }, ) + atin = checkver_and_convert(atin, request.node.name, "pre") jrec = qcng.compute(atin, program, raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() assert compare_values(expected, jrec["extras"]["qcvars"]["CURRENT ENERGY"], atol=1.0e-7) @@ -1961,7 +1995,9 @@ def test_sapt_pairwise(inp, program, extrakw, subjects, request): ({"parent": "ne", "name": "hf/minis", "subject": "atom", "lbl": "GCP"}), ], ) -def test_gcp(inp, subjects, program, request): +def test_gcp(inp, subjects, program, schema_versions, request): + models, _ = schema_versions + subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1979,7 +2015,9 @@ def test_gcp(inp, subjects, program, request): "model": {"method": inp["name"]}, "keywords": {}, } + resinp = checkver_and_convert(resinp, request.node.name, "pre") jrec = qcng.compute(resinp, program, raise_error=True) + jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() # assert len(jrec["extras"]["qcvars"]) == 8 diff --git a/qcengine/programs/tests/test_dftd4.py b/qcengine/programs/tests/test_dftd4.py index e3ac1f523..4a4fe3983 100644 --- a/qcengine/programs/tests/test_dftd4.py +++ b/qcengine/programs/tests/test_dftd4.py @@ -10,23 +10,26 @@ import qcelemental as qcel import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @using("dftd4") -def test_dftd4_task_b97m_m01(): +def test_dftd4_task_b97m_m01(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 return_result = -0.025024986301735823 - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-01", return_dict=True)), model={"method": "b97m"}, driver="energy", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) assert atomic_result.success @@ -34,7 +37,8 @@ def test_dftd4_task_b97m_m01(): @using("dftd4") -def test_dftd4_task_tpss_m02(): +def test_dftd4_task_tpss_m02(schema_versions, request): + models, _ = schema_versions thr = 2.0e-8 @@ -59,8 +63,8 @@ def test_dftd4_task_tpss_m02(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-02"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)), model={"method": ""}, keywords={ "params_tweaks": { @@ -72,14 +76,17 @@ def test_dftd4_task_tpss_m02(): driver="gradient", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("dftd4") -def test_dftd4_task_r2scan_m03(): +def test_dftd4_task_r2scan_m03(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -104,14 +111,16 @@ def test_dftd4_task_r2scan_m03(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-03"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)), keywords={"level_hint": "D4"}, driver="gradient", model={"method": "r2scan"}, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) assert atomic_result.success @@ -119,19 +128,22 @@ def test_dftd4_task_r2scan_m03(): @using("dftd4") -def test_dftd4_task_unknown_method(): +def test_dftd4_task_unknown_method(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), keywords={"level_hint": "D4"}, model={"method": "non-existent-method"}, driver="energy", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="input error", error_message="Functional 'non-existent-method' not known" ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) assert not atomic_result.success @@ -139,9 +151,10 @@ def test_dftd4_task_unknown_method(): @using("dftd4") -def test_dftd4_task_cold_fusion(): +def test_dftd4_task_cold_fusion(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( + atomic_input = models.AtomicInput( molecule={ "symbols": ["Li", "Li", "Li", "Li"], "geometry": [ @@ -156,12 +169,14 @@ def test_dftd4_task_cold_fusion(): model={"method": "pbe"}, driver="energy", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="input error", error_message="Too close interatomic distances found", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) assert not atomic_result.success diff --git a/qcengine/programs/tests/test_ghost.py b/qcengine/programs/tests/test_ghost.py index 06cee7d74..d9a43d3c2 100644 --- a/qcengine/programs/tests/test_ghost.py +++ b/qcengine/programs/tests/test_ghost.py @@ -8,19 +8,18 @@ import qcengine as qcng from qcengine.programs.tests.test_dftd3_mp2d import eneyne_ne_qcschemamols -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def hene(): - smol = """ +def hene_data(): + return """ 0 1 He 0 0 0 @Ne 2.5 0 0 nocom noreorient """ - return qcel.models.Molecule.from_data(smol) @pytest.mark.parametrize("driver", ["energy", "gradient"]) @@ -39,7 +38,10 @@ def hene(): pytest.param("psi4", "aug-cc-pvdz", {"scf_type": "direct"}, marks=using("psi4")), ], ) -def test_simple_ghost(driver, program, basis, keywords, hene): +def test_simple_ghost(driver, program, basis, keywords, hene_data, schema_versions, request): + models, models_out = schema_versions + hene = models.Molecule.from_data(hene_data) + resi = {"molecule": hene, "driver": driver, "model": {"method": "hf", "basis": basis}, "keywords": keywords} if program == "gamess": @@ -47,7 +49,9 @@ def test_simple_ghost(driver, program, basis, keywords, hene): qcng.compute(resi, program, raise_error=True, return_dict=True) pytest.xfail("no ghosts with gamess") + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == driver assert "provenance" in res @@ -166,7 +170,9 @@ def test_simple_ghost(driver, program, basis, keywords, hene): ), ], ) -def test_tricky_ghost(driver, qcprog, subject, basis, keywords): +def test_tricky_ghost(driver, qcprog, subject, basis, keywords, schema_versions, request): + models, models_out = schema_versions + dmol = eneyne_ne_qcschemamols()["eneyne"][subject] # Freeze the input orientation so that output arrays are aligned to input # and all programs match gradient. @@ -178,13 +184,13 @@ def test_tricky_ghost(driver, qcprog, subject, basis, keywords): # to the in_mol vs. calc_mol aligner. Gradients don't change much # w/symm geometry but enough that the symmetrizer must be defeated. keywords["geometry__autosym"] = "1d-4" - kmol = qcel.models.Molecule(**dmol) + kmol = models.Molecule(**dmol) ref = bimol_ref["eneyne"] assert len(kmol.symbols) == ref["natom"][subject] assert sum([int(at) for at in kmol.real]) == ref["nreal"][subject] - atin = qcel.models.AtomicInput( + atin = models.AtomicInput( **{"molecule": kmol, "model": {"method": "mp2", "basis": basis}, "driver": driver, "keywords": keywords} ) @@ -193,7 +199,9 @@ def test_tricky_ghost(driver, qcprog, subject, basis, keywords): res = qcng.compute(atin, qcprog, raise_error=True) pytest.xfail("no ghosts with gamess") + atin = checkver_and_convert(atin, request.node.name, "pre") atres = qcng.compute(atin, qcprog) + atres = checkver_and_convert(atres, request.node.name, "post") pprint.pprint(atres.dict(), width=200) assert compare_values( @@ -261,8 +269,10 @@ def test_tricky_ghost(driver, qcprog, subject, basis, keywords): ), ], ) -def test_atom_labels(qcprog, basis, keywords): - kmol = qcel.models.Molecule.from_data( +def test_atom_labels(qcprog, basis, keywords, schema_versions, request): + models, models_out = schema_versions + + kmol = models.Molecule.from_data( """ H 0 0 0 H5 5 0 0 @@ -275,11 +285,13 @@ def test_atom_labels(qcprog, basis, keywords): assert compare(["H", "H", "H", "H"], kmol.symbols, "elem") assert compare(["", "5", "_other", "_4sq"], kmol.atom_labels, "elbl") - atin = qcel.models.AtomicInput( + atin = models.AtomicInput( **{"molecule": kmol, "model": {"method": "mp2", "basis": basis}, "driver": "energy", "keywords": keywords} ) + atin = checkver_and_convert(atin, request.node.name, "pre") atres = qcng.compute(atin, qcprog) + atres = checkver_and_convert(atres, request.node.name, "post") pprint.pprint(atres.dict(), width=200) nre = 1.0828427 diff --git a/qcengine/programs/tests/test_molpro.py b/qcengine/programs/tests/test_molpro.py index 52b9e73da..b36459be5 100644 --- a/qcengine/programs/tests/test_molpro.py +++ b/qcengine/programs/tests/test_molpro.py @@ -3,7 +3,7 @@ from qcelemental.testing import compare_recursive import qcengine as qcng -from qcengine.testing import qcengine_records, using +from qcengine.testing import checkver_and_convert, qcengine_records, schema_versions, using molpro_info = qcengine_records("molpro") @@ -40,13 +40,17 @@ def test_molpro_input_formatter(test_case): @using("molpro") @pytest.mark.parametrize("test_case", molpro_info.list_test_cases()) -def test_molpro_executor(test_case): +def test_molpro_executor(test_case, schema_versions, request): + models, _ = schema_versions + # Get input file data data = molpro_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = models.AtomicInput.parse_raw(data["input.json"]) # Run Molpro + inp = checkver_and_convert(inp, request.node.name, "pre") result = qcng.compute(inp, "molpro", local_options={"ncores": 4}) + result = checkver_and_convert(result, request.node.name, "post") assert result.success is True # Get output file data diff --git a/qcengine/programs/tests/test_mrchem.py b/qcengine/programs/tests/test_mrchem.py index 8158b4af3..421b745da 100644 --- a/qcengine/programs/tests/test_mrchem.py +++ b/qcengine/programs/tests/test_mrchem.py @@ -5,12 +5,12 @@ from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def h2o(): - return qcel.models.Molecule( +def h2o(schema_versions): + return schema_versions[0].Molecule( geometry=[[0, 0, -0.1250], [-1.4375, 0, 1.0250], [1.4375, 0, 1.0250]], symbols=["O", "H", "H"], connectivity=[[0, 1, 1], [0, 2, 1]], @@ -18,8 +18,9 @@ def h2o(): @pytest.fixture -def fh(): - return qcel.models.Molecule( +def fh(schema_versions): + models, _ = schema_versions + return models.Molecule( geometry=[[0.000000000000, 0.000000000000, -1.642850273986], [0.000000000000, 0.000000000000, 0.087149726014]], symbols=["H", "F"], fix_com=True, @@ -29,14 +30,16 @@ def fh(): @using("mrchem") -def test_energy(h2o): +def test_energy(h2o, schema_versions, request): + models, _ = schema_versions + mr_kws = { "world_prec": 1.0e-3, "world_size": 6, "world_unit": "bohr", } - inp = qcel.models.AtomicInput( + inp = models.AtomicInput( molecule=h2o, driver="energy", model={ @@ -45,7 +48,9 @@ def test_energy(h2o): keywords=mr_kws, ) + inp = checkver_and_convert(inp, request.node.name, "pre") res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values(-76.4546307, res["return_result"], atol=1e-3) @@ -63,14 +68,16 @@ def test_energy(h2o): @using("mrchem") -def test_dipole(h2o): +def test_dipole(h2o, schema_versions, request): + models, _ = schema_versions + mr_kws = { "world_prec": 1.0e-3, "world_size": 6, "world_unit": "bohr", } - inp = qcel.models.AtomicInput( + inp = models.AtomicInput( molecule=h2o, driver="properties", model={ @@ -79,7 +86,9 @@ def test_dipole(h2o): keywords=mr_kws, ) + inp = checkver_and_convert(inp, request.node.name, "pre") res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values([-3.766420e-07, 0.0, 0.720473], res["return_result"]["dipole_moment"]["dip-1"], atol=1e-3) @@ -97,13 +106,15 @@ def test_dipole(h2o): @using("mrchem") -def test_gradient(fh): +def test_gradient(fh, schema_versions, request): + models, _ = schema_versions + mr_kws = { "world_prec": 1.0e-3, "world_size": 6, } - inp = qcel.models.AtomicInput( + inp = models.AtomicInput( molecule=fh, driver="gradient", model={ @@ -112,7 +123,9 @@ def test_gradient(fh): keywords=mr_kws, ) + inp = checkver_and_convert(inp, request.node.name, "pre") res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values( diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index fceb21d68..325eabfe1 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -5,7 +5,7 @@ from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using # Molecule where autoz fails _auto_z_problem = xyz = """C 15.204188380000 -3.519180270000 -10.798726560000 @@ -48,8 +48,8 @@ @pytest.fixture -def nh2(): - smol = """ +def nh2_data(): + return """ # R=1.008 #A=105.0 0 2 N 0.000000000000000 0.000000000000000 -0.145912918634892 @@ -58,14 +58,19 @@ def nh2(): units au symmetry c1 """ - return qcel.models.Molecule.from_data(smol) @using("nwchem") -def test_b3lyp(nh2): +def test_b3lyp(nh2_data, schema_versions, request): + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + # Run NH2 resi = {"molecule": nh2, "driver": "energy", "model": {"method": "b3lyp", "basis": "3-21g"}} + + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values(-55.554037, res["return_result"], atol=1e-3) @@ -86,9 +91,16 @@ def test_b3lyp(nh2): @using("nwchem") -def test_hess(nh2): +def test_hess(nh2_data, schema_versions, request): + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + resi = {"molecule": nh2, "driver": "hessian", "model": {"method": "b3lyp", "basis": "3-21g"}} + + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False) + res = checkver_and_convert(res, request.node.name, "post") + assert compare_values(-3.5980754370e-02, res.return_result[0, 0], atol=1e-3) assert compare_values(0, res.return_result[1, 0], atol=1e-3) assert compare_values(0.018208307756, res.return_result[3, 0], atol=1e-3) @@ -98,27 +110,40 @@ def test_hess(nh2): shifted_nh2, _ = nh2.scramble(do_shift=False, do_mirror=False, do_rotate=True, do_resort=False) resi["molecule"] = shifted_nh2 + + resi = checkver_and_convert(resi, request.node.name, "pre") res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False) + res_shifted = checkver_and_convert(res_shifted, request.node.name, "post") + assert not np.allclose(res.return_result, res_shifted.return_result, atol=1e-8) assert np.isclose(np.linalg.det(res.return_result), np.linalg.det(res_shifted.return_result)) @using("nwchem") -def test_gradient(nh2): +def test_gradient(nh2_data, schema_versions, request): + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + resi = { "molecule": nh2, "driver": "gradient", "model": {"method": "b3lyp", "basis": "3-21g"}, "keywords": {"dft__convergence__gradient": "1e-6"}, } + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") + assert compare_values(4.22418267e-2, res["return_result"][2], atol=1e-7) # Beyond accuracy of NWChem stdout # Rotate the molecule and verify that the gradient changes shifted_nh2, _ = nh2.scramble(do_shift=False, do_mirror=False, do_rotate=True, do_resort=False) resi["molecule"] = shifted_nh2 + + resi = checkver_and_convert(resi, request.node.name, "pre") res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert not compare_values(4.22418267e-2, res_shifted["return_result"][2], atol=1e-7) @@ -139,18 +164,20 @@ def test_gradient(nh2): @pytest.fixture -def h20(): - water = """ +def h20_data(): + return """ -1 2 O 0 0 0 H 0 0 1 H 0 1 0 """ - return qcel.models.Molecule.from_data(water) @using("nwchem") -def test_dipole(h20): +def test_dipole(h20_data, schema_versions, request): + models, _ = schema_versions + h20 = models.Molecule.from_data(h20_data) + # Run NH2 resi = { "molecule": h20, @@ -158,7 +185,10 @@ def test_dipole(h20): "model": {"method": "dft", "basis": "3-21g"}, "keywords": {"dft__xc": "b3lyp", "property__dipole": True}, } + + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values(-75.764944, res["return_result"], atol=1e-3) @@ -184,17 +214,19 @@ def test_dipole(h20): @pytest.fixture -def h20v2(): - water = """ +def h20v2_data(): + return """ O 0 0 0 H 0 0 1 H 0 1 0 """ - return qcel.models.Molecule.from_data(water) @using("nwchem") -def test_homo_lumo(h20v2): +def test_homo_lumo(h20v2_data, schema_versions, request): + models, _ = schema_versions + h20v2 = models.Molecule.from_data(h20v2_data) + # Run NH2 resi = { "molecule": h20v2, @@ -202,7 +234,10 @@ def test_homo_lumo(h20v2): "model": {"method": "dft", "basis": "3-21g"}, "keywords": {"dft__xc": "b3lyp"}, } + + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully assert compare_values(-75.968095, res["return_result"], atol=1e-3) @@ -226,8 +261,9 @@ def test_homo_lumo(h20v2): @using("nwchem") -def test_geometry_bug(): +def test_geometry_bug(schema_versions, request): """Make sure that the harvester does not crash if NWChem's autosym moves atoms too far""" + models, _ = schema_versions # Example molecule that has an RMSD of 2e-4 after NWChem symmetrizes the coordinates xyz = """6 @@ -238,65 +274,67 @@ def test_geometry_bug(): H -0.54657475 1.79916975 -0.87390126 H -0.52288871 1.72555240 0.89907326 H 0.44142019 -0.33354425 -0.77152059""" - mol = qcel.models.Molecule.from_data(xyz) - qcng.compute( - {"molecule": mol, "model": {"method": "b3lyp", "basis": "6-31g"}, "driver": "gradient"}, - "nwchem", - raise_error=True, - ) + mol = models.Molecule.from_data(xyz) + atin = {"molecule": mol, "model": {"method": "b3lyp", "basis": "6-31g"}, "driver": "gradient"} + + atin = checkver_and_convert(atin, request.node.name, "pre") + atres = qcng.compute(atin, "nwchem", raise_error=True) + atres = checkver_and_convert(atres, request.node.name, "post") @using("nwchem") -def test_autoz_error(): +def test_autoz_error(schema_versions, request): """Test ability to turn off autoz""" + models, _ = schema_versions + # Large molecule that leads to an AutoZ error - mol = qcel.models.Molecule.from_data(_auto_z_problem) - result = qcng.compute( - { + mol = models.Molecule.from_data(_auto_z_problem) + resi = { "molecule": mol, "model": {"method": "hf", "basis": "sto-3g"}, "driver": "energy", "protocols": {"error_correction": {"default_policy": False}}, - }, # Turn off error correction - "nwchem", - raise_error=False, - ) + } # Turn off error correction + + resi = checkver_and_convert(resi, request.node.name, "pre") + result = qcng.compute(resi, "nwchem", raise_error=False) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) assert not result.success assert "Error when generating redundant atomic coordinates" in result.error.error_message # Turn off autoz - result = qcng.compute( - { + resi = { "molecule": mol, "model": {"method": "hf", "basis": "sto-3g"}, "driver": "energy", "keywords": {"geometry__noautoz": True}, - }, - "nwchem", - raise_error=False, - ) + } + resi = checkver_and_convert(resi, request.node.name, "pre") + result = qcng.compute(resi, "nwchem", raise_error=False) + result = checkver_and_convert(result, request.node.name, "post",vercheck=False) # Ok if it crashes for other reasons assert "Error when generating redundant atomic coordinates" not in result.error.error_message @using("nwchem") -def test_autoz_error_correction(): +def test_autoz_error_correction(schema_versions, request): """See if error correction for autoz works""" + models, _ = schema_versions # Large molecule that leads to an AutoZ error - mol = qcel.models.Molecule.from_data(_auto_z_problem) - result = qcng.compute( - { + mol = models.Molecule.from_data(_auto_z_problem) + resi = { "molecule": mol, "model": {"method": "hf", "basis": "sto-3g"}, "driver": "energy", "keywords": {"scf__maxiter": 250, "scf__thresh": 1e-1}, - }, - "nwchem", - raise_error=True, - ) + } + + resi = checkver_and_convert(resi, request.node.name, "pre") + result = qcng.compute(resi, "nwchem", raise_error=True) + result = checkver_and_convert(result, request.node.name, "post") assert result.success assert "geom_binvr" in result.extras["observed_errors"] @@ -316,9 +354,11 @@ def test_autoz_error_correction(): ], ) @using("nwchem") -def test_conv_threshold(h20v2, method, keyword, init_iters, use_tce): - result = qcng.compute( - { +def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema_versions, request): + models, _ = schema_versions + h20v2 = models.Molecule.from_data(h20v2_data) + + resi = { "molecule": h20v2, "model": {"method": method, "basis": "sto-3g"}, "driver": "energy", @@ -327,10 +367,11 @@ def test_conv_threshold(h20v2, method, keyword, init_iters, use_tce): "qc_module": use_tce, "scf__uhf": True, }, # UHF needed for SCF test - }, - "nwchem", - raise_error=True, - ) + } + + resi = checkver_and_convert(resi, request.node.name, "pre") + result = qcng.compute(resi, "nwchem", raise_error=True) + result = checkver_and_convert(result, request.node.name, "post") assert result.success assert "convergence_failed" in result.extras["observed_errors"] @@ -338,9 +379,12 @@ def test_conv_threshold(h20v2, method, keyword, init_iters, use_tce): @using("nwchem") -def test_restart(nh2, tmpdir): +def test_restart(nh2_data, tmpdir, schema_versions, request): # Create a molecule that takes 5-8 steps for NWChem to relax it, # but only run the relaxation for 4 steps + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + resi = { "molecule": nh2, "driver": "gradient", @@ -352,12 +396,11 @@ def test_restart(nh2, tmpdir): # Run once: It should fail to converge local_options = {"scratch_messy": True, "scratch_directory": str(tmpdir)} - result = qcng.compute( - resi, - "nwchem", - local_options=local_options, - raise_error=False, - ) + + resi = checkver_and_convert(resi, request.node.name, "pre") + result = qcng.compute(resi, "nwchem", local_options=local_options, raise_error=False) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) + assert not result.success assert "computation failed to converge" in str(result.error) @@ -368,4 +411,5 @@ def test_restart(nh2, tmpdir): local_options=local_options, raise_error=False, ) + result = checkver_and_convert(result, request.node.name, "post") assert result.success diff --git a/qcengine/programs/tests/test_programs.py b/qcengine/programs/tests/test_programs.py index dae4dcd7d..39bff546e 100644 --- a/qcengine/programs/tests/test_programs.py +++ b/qcengine/programs/tests/test_programs.py @@ -5,33 +5,40 @@ import numpy as np import pytest -from qcelemental.models import AtomicInput, Molecule +from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import failure_engine, using +from qcengine.testing import checkver_and_convert, failure_engine, schema_versions, using -def test_missing_key(): +def test_missing_key(schema_versions, request): + ret = qcng.compute({"hello": "hi"}, "bleh") + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.success is False assert "hello" in ret.input_data -def test_missing_key_raises(): +def test_missing_key_raises(schema_versions, request): with pytest.raises(qcng.exceptions.InputError): ret = qcng.compute({"hello": "hi"}, "bleh", raise_error=True) @using("psi4") -def test_psi4_task(): +def test_psi4_task(schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": {"method": "SCF", "basis": "sto-3g"}, "keywords": {"scf_type": "df"}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.driver == "energy" assert "Final Energy" in ret.stdout @@ -45,31 +52,38 @@ def test_psi4_task(): @using("psi4") @using("gcp") -def test_psi4_hf3c_task(): +def test_psi4_hf3c_task(schema_versions, request): + models, _ = schema_versions input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": {"method": "HF3c"}, "keywords": {"scf_type": "df"}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True assert ret.model.basis is None @using("psi4_runqcsk") -def test_psi4_interactive_task(): +def test_psi4_interactive_task(schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": {"method": "SCF", "basis": "sto-3g"}, "keywords": {"scf_type": "df"}, "extras": {"psiapi": True}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert "Final Energy" in ret.stdout assert ret.success @@ -78,23 +92,30 @@ def test_psi4_interactive_task(): @using("psi4_runqcsk") -def test_psi4_wavefunction_task(): +def test_psi4_wavefunction_task(schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": {"method": "SCF", "basis": "sto-3g"}, "keywords": {"scf_type": "df"}, "protocols": {"wavefunction": "orbitals_and_eigenvalues"}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success, ret.error.error_message assert ret.wavefunction.scf_orbitals_a.shape == (7, 7) @using("psi4") -def test_psi4_internal_failure(): - mol = Molecule.from_data( +def test_psi4_internal_failure(schema_versions, request): + models, _ = schema_versions + + mol = models.Molecule.from_data( """0 3 O 0.000000000000 0.000000000000 -0.068516245955 """ @@ -107,14 +128,17 @@ def test_psi4_internal_failure(): "keywords": {"reference": "rhf"}, } with pytest.raises(qcng.exceptions.InputError) as exc: + psi4_task = checkver_and_convert(psi4_task, request.node.name, "pre") ret = qcng.compute(psi4_task, "psi4", raise_error=True) assert "reference is only" in str(exc.value) @using("psi4") -def test_psi4_ref_switch(): - inp = AtomicInput( +def test_psi4_ref_switch(schema_versions, request): + models, _ = schema_versions + + inp = models.AtomicInput( **{ "molecule": {"symbols": ["Li"], "geometry": [0, 0, 0], "molecular_multiplicity": 2}, "driver": "energy", @@ -123,7 +147,9 @@ def test_psi4_ref_switch(): } ) + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, "psi4", raise_error=True, return_dict=False) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True assert ret.properties.calcinfo_nalpha == 2 @@ -132,30 +158,39 @@ def test_psi4_ref_switch(): @using("rdkit") @pytest.mark.parametrize("method", ["UFF", "MMFF94", "MMFF94s"]) -def test_rdkit_task(method): +def test_rdkit_task(method, schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "gradient", "model": {"method": method}, "keywords": {}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "rdkit", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @using("rdkit") -def test_rdkit_connectivity_error(): +def test_rdkit_connectivity_error(schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water").dict(), + "molecule": qcng.get_molecule("water", return_dict=True), "driver": "energy", "model": {"method": "UFF", "basis": ""}, "keywords": {}, } del input_data["molecule"]["connectivity"] + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "rdkit") + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.success is False assert "connectivity" in ret.error.error_message @@ -164,74 +199,96 @@ def test_rdkit_connectivity_error(): @using("torchani") -def test_torchani_task(): +def test_torchani_task(schema_versions, request): + models, _ = schema_versions + input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "gradient", "model": {"method": "ANI1x", "basis": None}, "keywords": {}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "torchani", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True assert ret.driver == "gradient" @using("mopac") -def test_mopac_task(): +def test_mopac_task(schema_versions, request): input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": qcng.get_molecule("water", return_dict=True), "driver": "gradient", "model": {"method": "PM6", "basis": None}, "keywords": {"pulay": False}, } - ret = qcng.compute(input_data, "mopac", raise_error=True) + input_data1 = checkver_and_convert(input_data.copy(), request.node.name, "pre") + ret = qcng.compute(input_data1, "mopac", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.extras.keys() >= {"heat_of_formation", "dip_vec"} energy = pytest.approx(-0.08474117913025125, rel=1.0e-5) # Check gradient - ret = qcng.compute(input_data, "mopac", raise_error=True) + input_data2 = checkver_and_convert(input_data.copy(), request.node.name, "pre") + ret = qcng.compute(input_data2, "mopac", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.extras.keys() >= {"heat_of_formation", "dip_vec"} assert np.linalg.norm(ret.return_result) == pytest.approx(0.03543560156912385, rel=3.0e-4) assert ret.properties.return_energy == energy # Check energy - input_data["driver"] = "energy" - ret = qcng.compute(input_data, "mopac", raise_error=True) + input_data3 = input_data.copy() + input_data3["driver"] = "energy" + ret = qcng.compute(input_data3, "mopac", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.return_result == energy assert "== MOPAC DONE ==" in ret.stdout -def test_random_failure_no_retries(failure_engine): +def test_random_failure_no_retries(failure_engine, schema_versions, request): failure_engine.iter_modes = ["input_error"] ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.error.error_type == "input_error" assert "retries" not in ret.input_data["provenance"].keys() failure_engine.iter_modes = ["random_error"] ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.error.error_type == "random_error" assert "retries" not in ret.input_data["provenance"].keys() -def test_random_failure_with_retries(failure_engine): +def test_random_failure_with_retries(failure_engine, schema_versions, request): failure_engine.iter_modes = ["random_error", "random_error", "random_error"] ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 2}) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.input_data["provenance"]["retries"] == 2 assert ret.error.error_type == "random_error" failure_engine.iter_modes = ["random_error", "input_error"] ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 4}) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.input_data["provenance"]["retries"] == 1 assert ret.error.error_type == "input_error" -def test_random_failure_with_success(failure_engine): +def test_random_failure_with_success(failure_engine, schema_versions, request): failure_engine.iter_modes = ["random_error", "pass"] failure_engine.ncalls = 0 ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 1}) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success, ret.error.error_message assert ret.provenance.retries == 1 @@ -239,17 +296,21 @@ def test_random_failure_with_success(failure_engine): @using("openmm") -def test_openmm_task_smirnoff(): +def test_openmm_task_smirnoff(schema_versions, request): + models, _ = schema_versions + from qcengine.programs.openmm import OpenMMHarness input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": {"method": "openff-1.0.0", "basis": "smirnoff"}, "keywords": {}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") cachelength = len(OpenMMHarness._CACHE) @@ -257,6 +318,7 @@ def test_openmm_task_smirnoff(): assert ret.success is True ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") # ensure cache has not grown assert len(OpenMMHarness._CACHE) == cachelength @@ -265,11 +327,13 @@ def test_openmm_task_smirnoff(): @pytest.mark.skip("`basis` must be explicitly specified at this time") @using("openmm") -def test_openmm_task_url_basis(): +def test_openmm_task_url_basis(schema_versions, request): + models, _ = schema_versions + from qcengine.programs.openmm import OpenMMHarness input_data = { - "molecule": qcng.get_molecule("water"), + "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", "model": { "method": "openmm", @@ -279,7 +343,9 @@ def test_openmm_task_url_basis(): "keywords": {}, } + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") cachelength = len(OpenMMHarness._CACHE) @@ -287,6 +353,7 @@ def test_openmm_task_url_basis(): assert ret.success is True ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") # ensure cache has not grown assert len(OpenMMHarness._CACHE) == cachelength @@ -294,30 +361,37 @@ def test_openmm_task_url_basis(): @using("openmm") -def test_openmm_cmiles_gradient(): +def test_openmm_cmiles_gradient(schema_versions, request): + models, _ = schema_versions + program = "openmm" - water = qcng.get_molecule("water") + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) water_dict = water.dict() # add water cmiles to the molecule water_dict["extras"] = {"cmiles": {"canonical_isomeric_explicit_hydrogen_mapped_smiles": "[H:2][O:1][H:3]"}} - molecule = Molecule.from_data(water_dict) + molecule = models.Molecule.from_data(water_dict) model = {"method": "openff-1.0.0", "basis": "smirnoff"} - inp = AtomicInput(molecule=molecule, driver="gradient", model=model) + inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=False) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @using("openmm") -def test_openmm_cmiles_gradient_nomatch(): +def test_openmm_cmiles_gradient_nomatch(schema_versions, request): + models, _ = schema_versions + program = "openmm" - water = qcng.get_molecule("water") + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) water_dict = water.dict() # add ethane cmiles to the molecule @@ -327,12 +401,14 @@ def test_openmm_cmiles_gradient_nomatch(): } } - molecule = Molecule.from_data(water_dict) + molecule = models.Molecule.from_data(water_dict) model = {"method": "openff-1.0.0", "basis": "smirnoff"} + inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model) - inp = AtomicInput(molecule=molecule, driver="gradient", model=model) + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=False) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) # if we correctly find the cmiles this should fail as the molecule and cmiles are different assert ret.success is False @@ -357,49 +433,61 @@ def test_openmm_cmiles_gradient_nomatch(): pytest.param(({"nonbondedMethod": "badmethod"}, ValueError, 0), id="incorrect nonbondedmethod"), ], ) -def test_openmm_gaff_keywords(gaff_settings): +def test_openmm_gaff_keywords(gaff_settings, schema_versions, request): """ Test the different running settings with gaff. """ + models, _ = schema_versions + program = "openmm" - water = qcng.get_molecule("water") + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) water_dict = water.dict() # add water cmiles to the molecule water_dict["extras"] = {"cmiles": {"canonical_isomeric_explicit_hydrogen_mapped_smiles": "[H:2][O:1][H:3]"}} - molecule = Molecule.from_data(water_dict) + molecule = models.Molecule.from_data(water_dict) keywords, error, expected_result = gaff_settings model = {"method": "gaff-2.1", "basis": "antechamber"} - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) if error is not None: with pytest.raises(error): + inp = checkver_and_convert(inp, request.node.name, "pre") _ = qcng.compute(inp, program, raise_error=True) else: + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=False) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert ret.return_result == pytest.approx(expected_result, rel=1e-6) @using("mace") -def test_mace_energy(): +def test_mace_energy(schema_versions, request): """ Test calculating the energy with mace """ - water = qcng.get_molecule("water") - atomic_input = AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="energy") + models, _ = schema_versions + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) + atomic_input = models.AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="energy") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") result = qcng.compute(atomic_input, "mace") + result = checkver_and_convert(result, request.node.name, "post") + assert result.success assert pytest.approx(result.return_result) == -76.47683956098838 @using("mace") -def test_mace_gradient(): +def test_mace_gradient(schema_versions, request): """ Test calculating the gradient with mace """ - water = qcng.get_molecule("water") + models, _ = schema_versions + + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) expected_result = np.array( [ [0.0, -2.1590400539385646e-18, -0.04178551770271103], @@ -408,9 +496,12 @@ def test_mace_gradient(): ] ) - atomic_input = AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="gradient") + atomic_input = models.AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="gradient") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") result = qcng.compute(atomic_input, "mace") + result = checkver_and_convert(result, request.node.name, "post") + assert result.success assert pytest.approx(result.return_result) == expected_result @@ -423,13 +514,17 @@ def test_mace_gradient(): pytest.param("wb97m-d3", -76.47412023758551, id="wb97m-d3"), ], ) -def test_aimnet2_energy(model, expected_energy): +def test_aimnet2_energy(model, expected_energy, schema_versions, request): """Test computing the energies of water with two aimnet2 models.""" + models, _ = schema_versions - water = qcng.get_molecule("water") - atomic_input = AtomicInput(molecule=water, model={"method": model, "basis": None}, driver="energy") + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) + atomic_input = models.AtomicInput(molecule=water, model={"method": model, "basis": None}, driver="energy") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") result = qcng.compute(atomic_input, "aimnet2") + result = checkver_and_convert(result, request.node.name, "post") + assert result.success assert pytest.approx(result.return_result) == expected_energy assert "charges" in result.extras["aimnet2"] @@ -438,13 +533,17 @@ def test_aimnet2_energy(model, expected_energy): @using("aimnet2") -def test_aimnet2_gradient(): +def test_aimnet2_gradient(schema_versions, request): """Test computing the gradient of water using one aimnet2 model.""" + models, _ = schema_versions - water = qcng.get_molecule("water") - atomic_input = AtomicInput(molecule=water, model={"method": "wb97m-d3", "basis": None}, driver="gradient") + water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) + atomic_input = models.AtomicInput(molecule=water, model={"method": "wb97m-d3", "basis": None}, driver="gradient") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") result = qcng.compute(atomic_input, "aimnet2") + result = checkver_and_convert(result, request.node.name, "post") + assert result.success # make sure the gradient is now the return result assert np.allclose( @@ -460,3 +559,105 @@ def test_aimnet2_gradient(): assert pytest.approx(result.properties.return_energy) == -76.47412023758551 # make sure the other properties were also saved assert "charges" in result.extras["aimnet2"] + + +@using("psi4") +def test_psi4_properties_driver(schema_versions, request): + models, _ = schema_versions + + import numpy as np + + json_data = { + "schema_name": "qcschema_input", + "schema_version": 1, + "molecule": { + "geometry": [ + 0.0, + 0.0, + -0.1294769411935893, + 0.0, + -1.494187339479985, + 1.0274465079245698, + 0.0, + 1.494187339479985, + 1.0274465079245698, + ], + "symbols": ["O", "H", "H"], + "fix_com": True, + "fix_orientation": True, + }, + "driver": "properties", + "model": { + "method": "HF", + "basis": "6-31G", + "properties": [ + "dipole", + "quadrupole", + "mulliken_charges", + "lowdin_charges", + "wiberg_lowdin_indices", + "mayer_indices", + ], + }, + "keywords": {"scf_type": "df", "mp2_type": "df", "e_convergence": 9}, + } + + # Write expected output (dipole & quadrupole in au) + expected_return_result = { + "dipole": np.array([0.0, 0.0, 1.04037263345]).reshape((3,)), + "quadrupole": np.array([-5.42788218076, 0.0, 0.0, 0.0, -3.07521129293, 0.0, 0.0, 0.0, -4.36605314966]).reshape( + (3, 3) + ), + "mulliken_charges": [-0.7967275695997689, 0.3983637847998823, 0.3983637847998822], + "lowdin_charges": [-0.5945105406840803, 0.29725527034203636, 0.29725527034203636], + "wiberg_lowdin_indices": np.array( + [ + 0.0, + 0.9237385044125344, + 0.9237385044125329, + 0.9237385044125344, + 0.0, + 0.006992650019441531, + 0.9237385044125329, + 0.006992650019441531, + 0.0, + ] + ).reshape((3, 3)), + "mayer_indices": np.array( + [ + 0.0, + 0.802064044935596, + 0.8020640449355959, + 0.802064044935596, + 0.0, + 0.003020025778524219, + 0.8020640449355959, + 0.003020025778524219, + 0.0, + ] + ).reshape((3, 3)), + } + + expected_properties = { + "calcinfo_nbasis": 13, + "calcinfo_nmo": 13, + "calcinfo_nalpha": 5, + "calcinfo_nbeta": 5, + "calcinfo_natom": 3, + "scf_one_electron_energy": -122.27509111304202, + "scf_two_electron_energy": 37.49348718008625, + "nuclear_repulsion_energy": 8.80146206062943, + "scf_total_energy": -75.98014187232634, + "return_energy": -75.98014187232634, + } + + json_data = checkver_and_convert(json_data, request.node.name, "pre") + json_ret = qcng.compute(json_data, "psi4") + json_ret = checkver_and_convert(json_ret, request.node.name, "post") + + assert json_ret.success + assert "qcschema_output" == json_ret.schema_name + for k in expected_return_result.keys(): + assert compare_values(expected_return_result[k], json_ret.return_result[k], atol=1.0e-5) + for k in expected_properties.keys(): + assert compare_values(expected_properties[k], getattr(json_ret.properties, k), atol=1.0e-5) diff --git a/qcengine/programs/tests/test_qchem.py b/qcengine/programs/tests/test_qchem.py index a76302ff3..7ff555548 100644 --- a/qcengine/programs/tests/test_qchem.py +++ b/qcengine/programs/tests/test_qchem.py @@ -4,7 +4,7 @@ from qcelemental.testing import compare_recursive, compare_values import qcengine as qcng -from qcengine.testing import qcengine_records, using +from qcengine.testing import checkver_and_convert, qcengine_records, schema_versions, using qchem_info = qcengine_records("qchem") qchem_logonly_info = qcengine_records("qchem_logonly") @@ -66,26 +66,32 @@ def test_qchem_input_formatter_template(test_case): @using("qchem") @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_executor(test_case): +def test_qchem_executor(test_case, schema_versions, request): + models, _ = schema_versions + # Get input file data data = qchem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = models.AtomicInput.parse_raw(data["input.json"]) # Run qchem + inp = checkver_and_convert(inp, request.node.name, "pre") result = qcng.compute(inp, "qchem") + result = checkver_and_convert(result, request.node.name, "post") + assert result.success is True # Get output file data - output_ref = qcel.models.AtomicResult.parse_raw(data["output.json"]) + output_ref = models.AtomicResult.parse_raw(data["output.json"]) atol = 1e-6 assert compare_recursive(output_ref.return_result, result.return_result, atol=atol) @using("qchem") -def test_qchem_orientation(): +def test_qchem_orientation(schema_versions, request): + models, _ = schema_versions - mol = qcel.models.Molecule.from_data( + mol = models.Molecule.from_data( """ He 0.0 0.7 0.7 He 0.0 -0.7 -0.7 @@ -94,13 +100,20 @@ def test_qchem_orientation(): # Compare with rotation inp = {"molecule": mol, "driver": "gradient", "model": {"method": "HF", "basis": "6-31g"}} + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, "qchem", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert compare_values(np.linalg.norm(ret.return_result, axis=0), [0, 0, 0.00791539]) # Compare without rotations mol_noorient = mol.copy(update={"fix_orientation": True}) inp = {"molecule": mol_noorient, "driver": "gradient", "model": {"method": "HF", "basis": "6-31g"}} + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, "qchem", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert compare_values(np.linalg.norm(ret.return_result, axis=0), [0, 0.00559696541, 0.00559696541]) diff --git a/qcengine/programs/tests/test_qcore.py b/qcengine/programs/tests/test_qcore.py index c4ff4e9fa..ca891a207 100644 --- a/qcengine/programs/tests/test_qcore.py +++ b/qcengine/programs/tests/test_qcore.py @@ -4,7 +4,7 @@ from qcelemental.testing import compare_recursive, compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @using("qcore") @@ -16,15 +16,18 @@ ({"method": "hf", "basis": "6-31g"}, -75.98014477585764, 0.08866491), ], ) -def test_qcore_methods(method, energy, gradient_norm): +def test_qcore_methods(method, energy, gradient_norm, schema_versions, request): + models, _ = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), model=method, driver="gradient", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "qcore") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success, atomic_result.error.error_message assert compare_values(atomic_result.properties.return_energy, energy) @@ -33,16 +36,20 @@ def test_qcore_methods(method, energy, gradient_norm): @using("qcore") -def test_qcore_wavefunction(): +def test_qcore_wavefunction(schema_versions, request): + models, _ = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), model={"method": "wb97xd3", "basis": "6-31g"}, driver="gradient", protocols={"wavefunction": "all"}, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "qcore") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") + assert atomic_result.success, atomic_result.error.error_message assert atomic_result.wavefunction is not None assert atomic_result.wavefunction.scf_orbitals_a is not None diff --git a/qcengine/programs/tests/test_sdftd3.py b/qcengine/programs/tests/test_sdftd3.py index e2a211424..24477355e 100644 --- a/qcengine/programs/tests/test_sdftd3.py +++ b/qcengine/programs/tests/test_sdftd3.py @@ -10,23 +10,26 @@ import qcelemental as qcel import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @using("s-dftd3") -def test_dftd3_task_b97m_m01(): +def test_dftd3_task_b97m_m01(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 return_result = -0.05879001214961249 - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-01", return_dict=True)), model={"method": "b97m"}, driver="energy", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) assert atomic_result.success @@ -45,28 +48,32 @@ def test_dftd3_task_b97m_m01(): ], ids=["d3bj", "d3zero", "d3mbj", "d3mzero", "d3op"], ) -def test_dftd3_task_pbe_m02(inp): +def test_dftd3_task_pbe_m02(inp, schema_versions, request): + models, _ = schema_versions # return to 1.0e-8 after https://github.com/MolSSI/QCEngine/issues/370 thr = 1.0e-7 return_result = inp["return_result"] - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-02"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)), model={"method": "pbe"}, keywords={"level_hint": inp["level_hint"]}, driver="energy", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("s-dftd3") -def test_dftd3_task_tpss_m02(): +def test_dftd3_task_tpss_m02(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -91,8 +98,8 @@ def test_dftd3_task_tpss_m02(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-02"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)), model={"method": ""}, keywords={ "level_hint": "d3mbj", @@ -105,14 +112,17 @@ def test_dftd3_task_tpss_m02(): driver="gradient", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("s-dftd3") -def test_dftd3_task_r2scan_m03(): +def test_dftd3_task_r2scan_m03(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -137,14 +147,16 @@ def test_dftd3_task_r2scan_m03(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-03"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)), keywords={"level_hint": "d3bj"}, driver="gradient", model={"method": "r2scan"}, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(return_result, abs=thr) == atomic_result.return_result @@ -152,19 +164,22 @@ def test_dftd3_task_r2scan_m03(): @using("s-dftd3") -def test_dftd3_task_unknown_method(): +def test_dftd3_task_unknown_method(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), keywords={"level_hint": "d3zero"}, model={"method": "non-existent-method"}, driver="energy", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="input error", error_message="No entry for 'non-existent-method' present" ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False, cast_dict_as="FailedOperation") print(atomic_result.error) assert not atomic_result.success @@ -172,9 +187,10 @@ def test_dftd3_task_unknown_method(): @using("s-dftd3") -def test_dftd3_task_cold_fusion(): +def test_dftd3_task_cold_fusion(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( + atomic_input = models.AtomicInput( molecule={ "symbols": ["Li", "Li", "Li", "Li"], "geometry": [ @@ -189,12 +205,14 @@ def test_dftd3_task_cold_fusion(): model={"method": "pbe"}, driver="energy", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="input error", error_message="Too close interatomic distances found", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) assert not atomic_result.success diff --git a/qcengine/programs/tests/test_standard_suite.py b/qcengine/programs/tests/test_standard_suite.py index a0df4db93..5280f6179 100644 --- a/qcengine/programs/tests/test_standard_suite.py +++ b/qcengine/programs/tests/test_standard_suite.py @@ -38,7 +38,7 @@ import qcengine as qcng from qcengine.programs.tests.standard_suite_ref import std_molecules, std_refs -from qcengine.testing import using +from qcengine.testing import schema_versions, using from .standard_suite_runner import runner_asserter @@ -46,9 +46,10 @@ @pytest.fixture -def clsd_open_pmols(): +def clsd_open_pmols(schema_versions): + models, _ = schema_versions return { - name[:-4]: qcel.models.Molecule.from_data(smol, name=name[:-4]) + name[:-4]: models.Molecule.from_data(smol, name=name[:-4]) for name, smol in std_molecules.items() if name.endswith("-xyz") } @@ -158,8 +159,10 @@ def _trans_key(qc, bas, key): # yapf: enable ], ) -def test_hf_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request): - runner_asserter(*_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, "energy", "hf")) +def test_hf_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + runner_asserter( + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "hf") + ) # @@ -210,8 +213,10 @@ def test_hf_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reques # yapf: enable ], ) -def test_hf_gradient_module(inp, dertype, basis, subjects, clsd_open_pmols, request): - runner_asserter(*_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, "gradient", "hf")) +def test_hf_gradient_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + runner_asserter( + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "gradient", "hf") + ) # @@ -261,8 +266,10 @@ def test_hf_gradient_module(inp, dertype, basis, subjects, clsd_open_pmols, requ # yapf: enable ], ) -def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, request): - runner_asserter(*_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, "hessian", "hf")) +def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + runner_asserter( + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "hessian", "hf") + ) # @@ -338,8 +345,10 @@ def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, reque # yapf: enable ], ) -def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request): - runner_asserter(*_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, "energy", "mp2")) +def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + runner_asserter( + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "mp2") + ) # @@ -424,8 +433,10 @@ def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reque # yapf: enable ], ) -def test_ccsd_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request): - runner_asserter(*_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, "energy", "ccsd")) +def test_ccsd_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + runner_asserter( + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "ccsd") + ) ################### @@ -474,7 +485,9 @@ def test_ccsd_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, requ # atol = 2.e-5 -def _processor(inp, dertype, basis, subjects, clsd_open_pmols, request, driver, method, *, scramble=None, frame=""): +def _processor( + inp, dertype, basis, subjects, clsd_open_pmols, request, models, driver, method, *, scramble=None, frame="" +): method = method qcprog = inp["call"] tnm = request.node.name @@ -506,4 +519,4 @@ def _processor(inp, dertype, basis, subjects, clsd_open_pmols, request, driver, ] ).strip("-") - return inpcopy, subject, method, basis, tnm, scramble, frame + return inpcopy, subject, method, basis, tnm, scramble, frame, models diff --git a/qcengine/programs/tests/test_standard_suite_ccsd(t).py b/qcengine/programs/tests/test_standard_suite_ccsd(t).py index a0b453b03..bc2a12686 100644 --- a/qcengine/programs/tests/test_standard_suite_ccsd(t).py +++ b/qcengine/programs/tests/test_standard_suite_ccsd(t).py @@ -3,24 +3,23 @@ from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def h2o(): - smol = """ +def h2o_data(): + return """ # R=0.958 A=104.5 H 0.000000000000 1.431430901356 0.984293362719 O 0.000000000000 0.000000000000 -0.124038860300 H 0.000000000000 -1.431430901356 0.984293362719 units au """ - return qcel.models.Molecule.from_data(smol) @pytest.fixture -def nh2(): - smol = """ +def nh2_data(): + return """ # R=1.008 #A=105.0 0 2 N 0.000000000000000 0.000000000000000 -0.145912918634892 @@ -28,7 +27,6 @@ def nh2(): H 0.000000000000000 1.511214298139000 1.013682596946108 units au """ - return qcel.models.Molecule.from_data(smol) @pytest.mark.parametrize( @@ -42,14 +40,19 @@ def nh2(): pytest.param("gamess", "accd", {"ccinp__ncore": 0, "contrl__ispher": 1}, marks=using("gamess")), ], ) -def test_sp_ccsd_t_rhf_full(program, basis, keywords, h2o): +def test_sp_ccsd_t_rhf_full(program, basis, keywords, h2o_data, schema_versions, request): """cfour/sp-rhf-ccsd/input.dat #! single point CCSD(T)/adz on water """ + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert "provenance" in res @@ -81,10 +84,14 @@ def test_sp_ccsd_t_rhf_full(program, basis, keywords, h2o): ), ], ) -def test_sp_ccsd_t_uhf_fc_error(program, basis, keywords, nh2, errmsg): +def test_sp_ccsd_t_uhf_fc_error(program, basis, keywords, nh2_data, errmsg, schema_versions, request): + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + resi = {"molecule": nh2, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} with pytest.raises(qcng.exceptions.InputError) as e: + resi = checkver_and_convert(resi, request.node.name, "pre") qcng.compute(resi, program, raise_error=True, return_dict=True) assert errmsg in str(e.value) @@ -104,10 +111,15 @@ def test_sp_ccsd_t_uhf_fc_error(program, basis, keywords, nh2, errmsg): pytest.param("psi4", "aug-cc-pvdz", {"reference": "rohf"}, marks=using("psi4")), ], ) -def test_sp_ccsd_t_rohf_full(program, basis, keywords, nh2): +def test_sp_ccsd_t_rohf_full(program, basis, keywords, nh2_data, schema_versions, request): + models, _ = schema_versions + nh2 = models.Molecule.from_data(nh2_data) + resi = {"molecule": nh2, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert "provenance" in res diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 8a6356acd..6dfacfee8 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -3,24 +3,23 @@ from qcelemental.testing import compare_values import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def h2o(): - smol = """ +def h2o_data(): + return """ # R=0.958 A=104.5 H 0.000000000000 1.431430901356 0.984293362719 O 0.000000000000 0.000000000000 -0.124038860300 H 0.000000000000 -1.431430901356 0.984293362719 units au """ - return qcel.models.Molecule.from_data(smol) @pytest.fixture -def nh2(): - smol = """ +def nh2_data(): + return """ # R=1.008 #A=105.0 0 2 N 0.000000000000000 0.000000000000000 -0.145912918634892 @@ -29,7 +28,6 @@ def nh2(): units au symmetry c1 """ - return qcel.models.Molecule.from_data(smol) @pytest.mark.parametrize( @@ -53,14 +51,19 @@ def nh2(): pytest.param("terachem_pbs", "aug-cc-pvdz", {}, marks=using("terachem_pbs")), ], ) -def test_sp_hf_rhf(program, basis, keywords, h2o): +def test_sp_hf_rhf(program, basis, keywords, h2o_data, schema_versions, request): """cfour/sp-rhf-hf/input.dat #! single point HF/adz on water """ + models, models_out = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert "provenance" in res @@ -103,10 +106,15 @@ def test_sp_hf_rhf(program, basis, keywords, h2o): pytest.param("turbomole", "aug-cc-pVDZ", {}, marks=using("turbomole")), ], ) -def test_sp_hf_uhf(program, basis, keywords, nh2): +def test_sp_hf_uhf(program, basis, keywords, nh2_data, schema_versions, request): + models, models_out = schema_versions + + nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert "provenance" in res @@ -142,10 +150,15 @@ def test_sp_hf_uhf(program, basis, keywords, nh2): pytest.param("qchem", "aug-cc-pvdz", {"UNRESTRICTED": False}, marks=using("qchem")), ], ) -def test_sp_hf_rohf(program, basis, keywords, nh2): +def test_sp_hf_rohf(program, basis, keywords, nh2_data, schema_versions, request): + models, models_out = schema_versions + + nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert "provenance" in res diff --git a/qcengine/programs/tests/test_terachem.py b/qcengine/programs/tests/test_terachem.py index 8a10c2b08..724fe68e4 100644 --- a/qcengine/programs/tests/test_terachem.py +++ b/qcengine/programs/tests/test_terachem.py @@ -3,7 +3,7 @@ from qcelemental.testing import compare_recursive import qcengine as qcng -from qcengine.testing import qcengine_records, using +from qcengine.testing import checkver_and_convert, qcengine_records, schema_versions, using # Prep globals terachem_info = qcengine_records("terachem") @@ -35,13 +35,18 @@ def test_terachem_input_formatter(test_case): @using("terachem") @pytest.mark.parametrize("test_case", terachem_info.list_test_cases()) -def test_terachem_executor(test_case): +def test_terachem_executor(test_case, schema_versions, request): + models, _ = schema_versions + # Get input file data data = terachem_info.get_test_data(test_case) - inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) + inp = models.AtomicInput.parse_raw(data["input.json"]) # Run Terachem + inp = checkver_and_convert(inp, request.node.name, "pre") result = qcng.compute(inp, "terachem") + result = checkver_and_convert(result, request.node.name, "post") + # result = qcng.get_program('terachem').compute(inp, qcng.get_config()) assert result.success is True diff --git a/qcengine/programs/tests/test_turbomole.py b/qcengine/programs/tests/test_turbomole.py index e372f2c76..fff4bf87d 100644 --- a/qcengine/programs/tests/test_turbomole.py +++ b/qcengine/programs/tests/test_turbomole.py @@ -5,35 +5,29 @@ import qcengine as qcng from qcengine.programs.turbomole.harvester import parse_hessian -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @pytest.fixture -def h2o(): - mol = qcelemental.models.Molecule.from_data( - """ +def h2o_data(): + return """ O 0.000000000000 0.000000000000 -0.068516245955 H 0.000000000000 -0.790689888800 0.543701278274 H 0.000000000000 0.790689888800 0.543701278274 """ - ) - return mol @pytest.fixture -def h2o_ricc2_def2svp(): +def h2o_ricc2_def2svp_data(): """NumForce calls only make sense for stationary points. So this geometry was optimized at the ricc2/def2-svp level of theory and can be used to run NumForce with ricc2.""" - mol = qcelemental.models.Molecule.from_data( - """ + return """ O 0.0000000 0.0000000 -0.0835835 H 0.7501772 0.0000000 0.5210589 H -0.7501772 0.0000000 0.5210589 """ - ) - return mol @pytest.mark.parametrize( @@ -51,10 +45,15 @@ def h2o_ricc2_def2svp(): ), ], ) -def test_turbomole_energy(method, keywords, ref_energy, h2o): +def test_turbomole_energy(method, keywords, ref_energy, h2o_data, schema_versions, request): + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = {"molecule": h2o, "driver": "energy", "model": {"method": method, "basis": "def2-SVP"}, "keywords": keywords} + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "turbomole", raise_error=True, return_dict=True) + res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" assert res["success"] is True @@ -71,7 +70,10 @@ def test_turbomole_energy(method, keywords, ref_energy, h2o): pytest.param("rimp2", {}, 0.061576, marks=using("turbomole")), ], ) -def test_turbomole_gradient(method, keywords, ref_norm, h2o): +def test_turbomole_gradient(method, keywords, ref_norm, h2o_data, schema_versions, request): + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = { "molecule": h2o, "driver": "gradient", @@ -79,7 +81,9 @@ def test_turbomole_gradient(method, keywords, ref_norm, h2o): "keywords": keywords, } + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "turbomole", raise_error=True) + res = checkver_and_convert(res, request.node.name, "post") assert res.driver == "gradient" assert res.success is True @@ -91,7 +95,10 @@ def test_turbomole_gradient(method, keywords, ref_norm, h2o): @using("turbomole") -def test_turbomole_ri_dsp(h2o): +def test_turbomole_ri_dsp(h2o_data, schema_versions, request): + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = { "molecule": h2o, "driver": "energy", @@ -99,7 +106,9 @@ def test_turbomole_ri_dsp(h2o): "keywords": {"ri": True, "d3bj": True}, } + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "turbomole", raise_error=True) + res = checkver_and_convert(res, request.node.name, "post") assert res.driver == "energy" assert res.success is True @@ -129,7 +138,10 @@ def assert_hessian(H, ref_eigvals, ref_size): ("b-p", {"grid": "m5", "ri": True}, (1.59729409e-01, 7.21364827e-01, 9.63399519e-01)), ], ) -def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): +def test_turbomole_hessian(method, keywords, ref_eigvals, h2o_data, schema_versions, request): + models, _ = schema_versions + h2o = models.Molecule.from_data(h2o_data) + resi = { "molecule": h2o, "driver": "hessian", @@ -140,7 +152,10 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): "keywords": keywords, } + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "turbomole", raise_error=True) + res = checkver_and_convert(res, request.node.name, "post") + H = res.return_result size = h2o.geometry.size @@ -157,7 +172,10 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o): ("ricc2", {}, (1.65405531e-01, 9.63690706e-01, 1.24676634e00)), ], ) -def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp): +def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp_data, schema_versions, request): + models, _ = schema_versions + h2o_ricc2_def2svp = models.Molecule.from_data(h2o_ricc2_def2svp_data) + resi = { "molecule": h2o_ricc2_def2svp, "driver": "hessian", @@ -168,7 +186,10 @@ def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp) "keywords": keywords, } + resi = checkver_and_convert(resi, request.node.name, "pre") res = qcng.compute(resi, "turbomole", raise_error=True) + res = checkver_and_convert(res, request.node.name, "post") + H = res.return_result size = h2o_ricc2_def2svp.geometry.size diff --git a/qcengine/programs/tests/test_xtb.py b/qcengine/programs/tests/test_xtb.py index 86148ce06..98bb07ee7 100644 --- a/qcengine/programs/tests/test_xtb.py +++ b/qcengine/programs/tests/test_xtb.py @@ -11,12 +11,12 @@ from qcelemental.testing import compare_recursive import qcengine as qcng -from qcengine.testing import using +from qcengine.testing import checkver_and_convert, schema_versions, using @using("xtb") -def test_xtb_task_gfn1xtb_m01(): - +def test_xtb_task_gfn1xtb_m01(schema_versions, request): + models, _ = schema_versions thr = 1.0e-7 return_result = np.array( @@ -40,20 +40,23 @@ def test_xtb_task_gfn1xtb_m01(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-01", return_dict=True)), model={"method": "GFN1-xTB"}, driver="gradient", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn1xtb_m02(): +def test_xtb_task_gfn1xtb_m02(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -78,8 +81,8 @@ def test_xtb_task_gfn1xtb_m02(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-02"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)), model={"method": "GFN1-xTB"}, driver="gradient", keywords={ @@ -88,14 +91,17 @@ def test_xtb_task_gfn1xtb_m02(): }, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn1xtb_m03(): +def test_xtb_task_gfn1xtb_m03(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -120,8 +126,8 @@ def test_xtb_task_gfn1xtb_m03(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-03"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)), model={"method": "GFN1-xTB"}, driver="gradient", keywords={ @@ -129,14 +135,17 @@ def test_xtb_task_gfn1xtb_m03(): }, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn1xtb_m04(): +def test_xtb_task_gfn1xtb_m04(schema_versions, request): + models, _ = schema_versions thr = 1.0e-6 @@ -164,13 +173,15 @@ def test_xtb_task_gfn1xtb_m04(): ), } - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-04", return_dict=True)), model={"method": "GFN1-xTB"}, driver="properties", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result["dipole"], abs=thr) == return_result["dipole"] @@ -179,19 +190,22 @@ def test_xtb_task_gfn1xtb_m04(): @using("xtb") -def test_xtb_task_gfn1xtb_m05(): +def test_xtb_task_gfn1xtb_m05(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 return_result = -29.038403257613453 - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-05", return_dict=True)), model={"method": "GFN1-xTB"}, driver="energy", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @@ -199,7 +213,8 @@ def test_xtb_task_gfn1xtb_m05(): @using("xtb") -def test_xtb_task_gfn2xtb_m01(): +def test_xtb_task_gfn2xtb_m01(schema_versions, request): + models, _ = schema_versions thr = 1.0e-7 @@ -224,20 +239,23 @@ def test_xtb_task_gfn2xtb_m01(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-01"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-01", return_dict=True)), model={"method": "GFN2-xTB"}, driver="gradient", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn2xtb_m02(): +def test_xtb_task_gfn2xtb_m02(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -262,8 +280,8 @@ def test_xtb_task_gfn2xtb_m02(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-02"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)), model={"method": "GFN2-xTB"}, driver="gradient", keywords={ @@ -272,14 +290,17 @@ def test_xtb_task_gfn2xtb_m02(): }, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn2xtb_m03(): +def test_xtb_task_gfn2xtb_m03(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 @@ -304,8 +325,8 @@ def test_xtb_task_gfn2xtb_m03(): ] ) - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-03"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-03",return_dict=True)), model={"method": "GFN2-xTB"}, driver="gradient", keywords={ @@ -313,14 +334,17 @@ def test_xtb_task_gfn2xtb_m03(): }, ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @using("xtb") -def test_xtb_task_gfn2xtb_m04(): +def test_xtb_task_gfn2xtb_m04(schema_versions, request): + models, _ = schema_versions thr = 1.0e-6 @@ -348,13 +372,15 @@ def test_xtb_task_gfn2xtb_m04(): ), } - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-04"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-04", return_dict=True)), model={"method": "GFN2-xTB"}, driver="properties", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result["dipole"], abs=thr) == return_result["dipole"] @@ -363,19 +389,22 @@ def test_xtb_task_gfn2xtb_m04(): @using("xtb") -def test_xtb_task_gfn2xtb_m05(): +def test_xtb_task_gfn2xtb_m05(schema_versions, request): + models, _ = schema_versions thr = 1.0e-8 return_result = -27.73598761779656 - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("mindless-05"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("mindless-05", return_dict=True)), model={"method": "GFN2-xTB"}, driver="energy", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success assert pytest.approx(atomic_result.return_result, abs=thr) == return_result @@ -383,43 +412,50 @@ def test_xtb_task_gfn2xtb_m05(): @using("xtb") -def test_xtb_task_unknown_method(): +def test_xtb_task_unknown_method(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), model={"method": "GFN-xTB"}, driver="energy", ) - error = qcel.models.ComputeError(error_type="input_error", error_message="Invalid method GFN-xTB provided in model") + error = models_out.ComputeError(error_type="input_error", error_message="Invalid method GFN-xTB provided in model") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success assert atomic_result.error == error @using("xtb") -def test_xtb_task_unsupported_driver(): +def test_xtb_task_unsupported_driver(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( - molecule=qcng.get_molecule("water"), + atomic_input = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), model={"method": "GFN2-xTB"}, driver="hessian", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="input_error", error_message="Calculation succeeded but invalid driver request provided" ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success assert atomic_result.error == error @using("xtb") -def test_xtb_task_cold_fusion(): +def test_xtb_task_cold_fusion(schema_versions, request): + models, models_out = schema_versions - atomic_input = qcel.models.AtomicInput( + atomic_input = models.AtomicInput( molecule={ "symbols": ["Li", "Li", "Li", "Li"], "geometry": [ @@ -433,12 +469,14 @@ def test_xtb_task_cold_fusion(): model={"method": "GFN2-xTB"}, driver="energy", ) - error = qcel.models.ComputeError( + error = models_out.ComputeError( error_type="runtime_error", error_message="Setup of molecular structure failed:\n-1- xtb_api_newMolecule: Could not generate molecular structure", ) + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success assert atomic_result.error == error diff --git a/qcengine/stock_mols.py b/qcengine/stock_mols.py index 47522e5af..90592b041 100644 --- a/qcengine/stock_mols.py +++ b/qcengine/stock_mols.py @@ -189,11 +189,14 @@ } -def get_molecule(name): +def get_molecule(name, *, return_dict: bool = False): """ Returns a QC JSON representation of a test molecule. """ if name not in _test_mols: raise KeyError("Molecule name '{}' not found".format(name)) - return Molecule(**copy.deepcopy(_test_mols[name])) + if return_dict: + return copy.deepcopy(_test_mols[name]) + else: + return Molecule(**copy.deepcopy(_test_mols[name])) diff --git a/qcengine/testing.py b/qcengine/testing.py index 4cb31d5e0..2bbd8340e 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -78,7 +78,7 @@ def is_mdi_new_enough(version_feature_introduced): @pytest.fixture(scope="function") -def failure_engine(): +def failure_engine(schema_versions): unique_name = "testing_random_name" class FailEngine(qcng.programs.ProgramHarness): @@ -116,7 +116,8 @@ def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicRes grad = [0, 0, -grad_value, 0, 0, grad_value] if mode == "pass": - return qcel.models.AtomicResult( + # TODO return schema_versions[0].AtomicResult( + return qcel.models.v1.AtomicResult( **{ **input_data.dict(), **{ @@ -211,3 +212,84 @@ def using(program): _using_cache[program] = skip return _using_cache[program] + + +@pytest.fixture(scope="function", params=[None, "as_v1", "as_v2", "to_v1", "to_v2"]) +def schema_versions(request): + if request.param == "as_v1": + return qcel.models.v1, qcel.models.v1 + elif request.param == "to_v2": + return qcel.models.v1, qcel.models.v2 + elif request.param == "as_v2": + return qcel.models.v2, qcel.models.v2 + elif request.param == "to_v1": + return qcel.models.v2, qcel.models.v1 + else: + return qcel.models, qcel.models + + +def checkver_and_convert(mdl, tnm, prepost, vercheck: bool = True, cast_dict_as=None): + import json + + import pydantic + + def check_model_v1(m): + assert isinstance(m, pydantic.v1.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ v1.BaseModel" + assert isinstance( + m, qcel.models.v1.basemodels.ProtoModel + ), f"type({m.__class__.__name__}) = {type(m)} ⊄ v1.ProtoModel" + if vercheck: + assert m.schema_version == 1, f"{m.__class__.__name__}.schema_version = {m.schema_version} != 1" + + def check_model_v2(m): + assert isinstance(m, pydantic.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ BaseModel" + assert isinstance( + m, qcel.models.v2.basemodels.ProtoModel + ), f"type({m.__class__.__name__}) = {type(m)} ⊄ v2.ProtoModel" + if vercheck: + assert m.schema_version == 2, f"{m.__class__.__name__}.schema_version = {m.schema_version} != 2" + + if prepost == "pre": + dict_in = isinstance(mdl, dict) + if "as_v1" in tnm or "to_v2" in tnm or "None" in tnm: + if dict_in: + if cast_dict_as: + mdl = getattr(qcel.models.v1, cast_dict_as)(**mdl) + else: + mdl = qcel.models.v1.AtomicInput(**mdl) + check_model_v1(mdl) + elif "as_v2" in tnm or "to_v1" in tnm: + if dict_in: + if cast_dict_as: + mdl = getattr(qcel.models.v2, cast_dict_as)(**mdl) + else: + mdl = qcel.models.v2.AtomicInput(**mdl) + check_model_v2(mdl) + mdl = mdl.convert_v(1) + + if dict_in: + mdl = mdl.model_dump() + + elif prepost == "post": + dict_in = isinstance(mdl, dict) + if "as_v1" in tnm or "to_v1" in tnm or "None" in tnm: + if dict_in: + if cast_dict_as: + mdl = getattr(qcel.models.v1, cast_dict_as)(**mdl) + else: + mdl = qcel.models.v1.AtomicResult(**mdl) + check_model_v1(mdl) + elif "as_v2" in tnm or "to_v2" in tnm: + if dict_in: + if cast_dict_as: + mdl = getattr(qcel.models.v2, cast_dict_as)(**mdl) + else: + mdl = qcel.models.v2.AtomicResult(**mdl) + mdl = mdl.convert_v(2) + check_model_v2(mdl) + + if dict_in: + # imitates compute(..., return_dict=True) + mdl = json.loads(mdl.model_dump_json()) + + return mdl diff --git a/qcengine/tests/test_cli.py b/qcengine/tests/test_cli.py index ad8884ab6..60d35a912 100644 --- a/qcengine/tests/test_cli.py +++ b/qcengine/tests/test_cli.py @@ -4,7 +4,7 @@ import sys from typing import List -from qcelemental.models import AtomicInput, OptimizationInput +import qcelemental from qcengine import cli, get_molecule, util from qcengine.testing import using @@ -56,16 +56,22 @@ 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): """Tests qcengine run with psi4 and JSON input""" + models = qcelemental.models.v1 def check_result(stdout): output = json.loads(stdout) assert output["provenance"]["creator"].lower() == "psi4" assert output["success"] is True - inp = AtomicInput(molecule=get_molecule("hydrogen"), driver="energy", model={"method": "hf", "basis": "6-31G"}) + inp = models.AtomicInput( + molecule=models.Molecule(**get_molecule("hydrogen", return_dict=True)), + driver="energy", + model={"method": "hf", "basis": "6-31G"}, + ) args = ["run", "psi4", inp.json()] check_result(run_qcengine_cli(args)) @@ -82,6 +88,7 @@ def check_result(stdout): @using("psi4") def test_run_procedure(tmp_path): """Tests qcengine run-procedure with geometric, psi4, and JSON input""" + models = qcelemental.models.v1 def check_result(stdout): output = json.loads(stdout) @@ -91,9 +98,9 @@ def check_result(stdout): inp = { "keywords": {"coordsys": "tric", "maxiter": 100, "program": "psi4"}, "input_specification": {"driver": "gradient", "model": {"method": "HF", "basis": "sto-3g"}, "keywords": {}}, - "initial_molecule": get_molecule("hydrogen"), + "initial_molecule": models.Molecule(**get_molecule("hydrogen", return_dict=True)), } - inp = OptimizationInput(**inp) + inp = models.OptimizationInput(**inp) args = ["run-procedure", "geometric", inp.json()] check_result(run_qcengine_cli(args)) diff --git a/qcengine/tests/test_harness_canonical.py b/qcengine/tests/test_harness_canonical.py index 92d31cda7..8f018efad 100644 --- a/qcengine/tests/test_harness_canonical.py +++ b/qcengine/tests/test_harness_canonical.py @@ -1,16 +1,17 @@ """ Tests the DQM compute dispatch module """ +import copy + import msgpack import numpy as np import pytest -from qcelemental.models import AtomicInput, BasisSet from qcelemental.tests.test_model_results import center_data import qcengine as qcng -from qcengine.testing import has_program, using +from qcengine.testing import checkver_and_convert, has_program, schema_versions, using -qcsk_bs = BasisSet(name="custom_basis", center_data=center_data, atom_map=["bs_sto3g_h", "bs_sto3g_h"]) +qcsk_bs = {"name": "custom_basis", "center_data": center_data, "atom_map": ["bs_sto3g_h", "bs_sto3g_h"]} _canonical_methods = [ ("dftd3", {"method": "b3lyp-d3"}, {}), @@ -53,44 +54,55 @@ ] -def _get_molecule(program): +def _get_molecule(program, molcls): if program in ["openmm", "terachem_pbs"]: - return qcng.get_molecule("water") + dmol = qcng.get_molecule("water", return_dict=True) else: - return qcng.get_molecule("hydrogen") - + dmol = qcng.get_molecule("hydrogen", return_dict=True) + return molcls(**dmol) @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_compute_energy(program, model, keywords): +def test_compute_energy(program, model, keywords, schema_versions, request): + models, _ = schema_versions + if not has_program(program): pytest.skip(f"Program '{program}' not found.") - molecule = _get_molecule(program) + molecule = _get_molecule(program, models.Molecule) - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert isinstance(ret.return_result, float) @pytest.mark.parametrize("program, model, keywords", _canonical_methods) -def test_compute_gradient(program, model, keywords): +def test_compute_gradient(program, model, keywords, schema_versions, request): + models, _ = schema_versions + if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) - molecule = _get_molecule(program) + molecule = _get_molecule(program, models.Molecule) - inp = AtomicInput( + inp = models.AtomicInput( molecule=molecule, driver="gradient", model=model, extras={"mytag": "something"}, keywords=keywords ) if program in ["adcc"]: + inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.InputError) as e: qcng.compute(inp, program, raise_error=True) assert "gradient not implemented" in str(e.value) else: + inp = checkver_and_convert(inp, request.node.name, "pre") ret = qcng.compute(inp, program, raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True assert isinstance(ret.return_result, np.ndarray) @@ -100,15 +112,19 @@ def test_compute_gradient(program, model, keywords): @pytest.mark.parametrize("program, model, keywords", _canonical_methods_qcsk_basis) -def test_compute_energy_qcsk_basis(program, model, keywords): +def test_compute_energy_qcsk_basis(program, model, keywords, schema_versions, request): + models, _ = schema_versions + if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) - molecule = _get_molecule(program) - inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) + molecule = _get_molecule(program, models.Molecule) + inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) with pytest.raises(qcng.exceptions.InputError) as e: - qcng.compute(inp, program, raise_error=True) + inp = checkver_and_convert(inp, request.node.name, "pre") + res = qcng.compute(inp, program, raise_error=True) + checkver_and_convert(res, request.node.name, "post") assert "QCSchema BasisSet for model.basis not implemented" in str(e.value) @@ -142,27 +158,32 @@ def test_compute_energy_qcsk_basis(program, model, keywords): # ("xtb", {"method": "bad"}), ], ) -def test_compute_bad_models(program, model): +def test_compute_bad_models(program, model, schema_versions, request): + models, _ = schema_versions + if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) - adriver = model.pop("driver", "energy") - amodel = model - inp = AtomicInput(molecule=qcng.get_molecule("hydrogen"), driver=adriver, model=amodel) + amodel = copy.deepcopy(model) + adriver = amodel.pop("driver", "energy") + inp = models.AtomicInput(molecule=models.Molecule(**qcng.get_molecule("hydrogen",return_dict=True)), driver=adriver, model=amodel) + inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.InputError) as exc: ret = qcng.compute(inp, program, raise_error=True) -def test_psi4_restarts(monkeypatch): +def test_psi4_restarts(monkeypatch, schema_versions, request): """ Make sure that a random error is raised which can be restarted if psi4 fails with no error message """ + models, _ = schema_versions + if not has_program("psi4"): pytest.skip("Program psi4 not found.") # create the psi4 task - inp = AtomicInput(molecule=qcng.get_molecule("hydrogen"), driver="energy", model={"method": "hf", "basis": "6-31G"}) + inp = models.AtomicInput(molecule=models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), driver="energy", model={"method": "hf", "basis": "6-31G"}) def mock_execute(*args, **kwargs): """ @@ -174,5 +195,6 @@ def mock_execute(*args, **kwargs): monkeypatch.setattr("qcengine.programs.psi4.execute", mock_execute) + inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.RandomError): _ = qcng.compute(input_data=inp, program="psi4", raise_error=True, task_config={"retries": 0}) diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index 256df352c..e1a12088f 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -3,12 +3,10 @@ """ import pytest -from qcelemental.models import DriverEnum, FailedOperation, OptimizationInput -from qcelemental.models.common_models import Model -from qcelemental.models.procedures import OptimizationSpecification, QCInputSpecification, TDKeywords, TorsionDriveInput +import qcelemental as qcel import qcengine as qcng -from qcengine.testing import failure_engine, using +from qcengine.testing import checkver_and_convert, failure_engine, schema_versions, using @pytest.fixture(scope="function") @@ -30,20 +28,24 @@ def input_data(): pytest.param("berny", marks=using("berny")), ], ) -def test_geometric_psi4(input_data, optimizer, ncores): +def test_geometric_psi4(input_data, optimizer, ncores, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("hydrogen") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} input_data["input_specification"]["keywords"] = {"scf_properties": ["wiberg_lowdin_indices"]} input_data["keywords"]["program"] = "psi4" - input_data = OptimizationInput(**input_data) + input_data = models.OptimizationInput(**input_data) task_config = { "ncores": ncores, } + 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) + ret = checkver_and_convert(ret, request.node.name, "post") + assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.0e-4) == 1.3459150737 @@ -64,16 +66,20 @@ def test_geometric_psi4(input_data, optimizer, ncores): @using("psi4") @using("geometric") -def test_geometric_local_options(input_data): +def test_geometric_local_options(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("hydrogen") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} input_data["keywords"]["program"] = "psi4" - input_data = OptimizationInput(**input_data) + input_data = models.OptimizationInput(**input_data) # 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, local_options={"memory": "5000"}) + ret = checkver_and_convert(ret, request.node.name, "post") + assert pytest.approx(ret.trajectory[0].provenance.memory, 1) == 4900 # Make sure we cleaned up @@ -83,87 +89,109 @@ def test_geometric_local_options(input_data): @using("rdkit") @using("geometric") -def test_geometric_stdout(input_data): +def test_geometric_stdout(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""} input_data["keywords"]["program"] = "rdkit" - input_data = OptimizationInput(**input_data) + 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) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert "Converged!" in ret.stdout @using("psi4") @using("berny") -def test_berny_stdout(input_data): +def test_berny_stdout(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} input_data["keywords"]["program"] = "psi4" - input_data = OptimizationInput(**input_data) + 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) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert "All criteria matched" in ret.stdout @using("psi4") @using("berny") -def test_berny_failed_gradient_computation(input_data): +def test_berny_failed_gradient_computation(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} input_data["input_specification"]["keywords"] = {"badpsi4key": "badpsi4value"} input_data["keywords"]["program"] = "psi4" - input_data = OptimizationInput(**input_data) + 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) - assert isinstance(ret, FailedOperation) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + + assert isinstance(ret, (qcel.models.v1.FailedOperation, qcel.models.v2.FailedOperation)) assert ret.success is False assert ret.error.error_type == qcng.exceptions.InputError.error_type @using("geometric") @using("rdkit") -def test_geometric_rdkit_error(input_data): +def test_geometric_rdkit_error(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water").copy(exclude={"connectivity_"}) + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)).copy( + exclude={"connectivity_"} + ) input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""} input_data["keywords"]["program"] = "rdkit" - input_data = OptimizationInput(**input_data) + input_data = models.OptimizationInput(**input_data) + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute_procedure(input_data, "geometric") + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.success is False assert isinstance(ret.error.error_message, str) @using("rdkit") @using("geometric") -def test_optimization_protocols(input_data): +def test_optimization_protocols(input_data, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "UFF"} input_data["keywords"]["program"] = "rdkit" input_data["protocols"] = {"trajectory": "initial_and_final"} - input_data = OptimizationInput(**input_data) + 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) - assert ret.success, ret.error.error_message + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success, ret.error.error_message assert len(ret.trajectory) == 2 assert ret.initial_molecule.get_hash() == ret.trajectory[0].molecule.get_hash() assert ret.final_molecule.get_hash() == ret.trajectory[1].molecule.get_hash() @using("geometric") -def test_geometric_retries(failure_engine, input_data): +def test_geometric_retries(failure_engine, input_data, schema_versions, request): + models, _ = schema_versions failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"] # Iter 1 # Iter 2 failure_engine.iter_modes.extend(["pass"] * 20) @@ -176,9 +204,12 @@ def test_geometric_retries(failure_engine, input_data): input_data["keywords"]["program"] = failure_engine.name input_data["keywords"]["coordsys"] = "cart" # needed by geometric v1.0 to play nicely with failure_engine - input_data = OptimizationInput(**input_data) + 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) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert ret.trajectory[0].provenance.retries == 1 assert ret.trajectory[0].provenance.ncores == 13 @@ -188,7 +219,10 @@ def test_geometric_retries(failure_engine, input_data): # 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}) + ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) + assert ret.success is False assert ret.input_data["trajectory"][0]["provenance"]["retries"] == 1 assert len(ret.input_data["trajectory"]) == 2 @@ -251,14 +285,20 @@ def test_geometric_retries(failure_engine, input_data): ), ], ) -def test_geometric_generic(input_data, program, model, bench): +def test_geometric_generic(input_data, program, model, bench, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("water") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = model input_data["keywords"]["program"] = program - input_data["input_specification"]["extras"] = {"_secret_tags": {"mysecret_tag": "data1"}} # pragma: allowlist secret + input_data["input_specification"]["extras"] = { + "_secret_tags": {"mysecret_tag": "data1"} # pragma: allowlist secret + } + 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) + ret = checkver_and_convert(ret, request.node.name, "post") + assert ret.success is True assert "Converged!" in ret.stdout @@ -275,26 +315,33 @@ def test_geometric_generic(input_data, program, model, bench): @using("nwchem") @pytest.mark.parametrize("linopt", [0, 1]) -def test_nwchem_relax(linopt): +def test_nwchem_relax(linopt, schema_versions, request): + models, _ = schema_versions + # Make the input file input_data = { "input_specification": { "model": {"method": "HF", "basis": "sto-3g"}, "keywords": {"set__driver:linopt": linopt}, }, - "initial_molecule": qcng.get_molecule("hydrogen"), + "initial_molecule": models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), } - input_data = OptimizationInput(**input_data) + input_data = models.OptimizationInput(**input_data) # Run the relaxation + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute_procedure(input_data, "nwchemdriver", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") + assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.0e-4) == 1.3459150737 @using("nwchem") -def test_nwchem_restart(tmpdir): +def test_nwchem_restart(tmpdir, schema_versions, request): + models, _ = schema_versions + # Make the input file input_data = { "input_specification": { @@ -302,28 +349,40 @@ def test_nwchem_restart(tmpdir): "keywords": {"driver__maxiter": 2, "set__driver:linopt": 0}, "extras": {"allow_restarts": True}, }, - "initial_molecule": qcng.get_molecule("hydrogen"), + "initial_molecule": models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), } - input_data = OptimizationInput(**input_data) + input_data = models.OptimizationInput(**input_data) # Run an initial step, which should not converge 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", local_options=local_opts, raise_error=False) + 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", local_options=local_opts, raise_error=True) + new_ret = checkver_and_convert(new_ret, request.node.name, "post") assert new_ret.success @using("rdkit") @using("torsiondrive") -def test_torsiondrive_generic(): +def test_torsiondrive_generic(schema_versions, request): + models, _ = schema_versions + TorsionDriveInput = models.TorsionDriveInput + TDKeywords = models.procedures.TDKeywords + OptimizationSpecification = models.procedures.OptimizationSpecification + QCInputSpecification = models.procedures.QCInputSpecification + DriverEnum = models.DriverEnum + Model = models.Model + Molecule = models.Molecule input_data = TorsionDriveInput( keywords=TDKeywords(dihedrals=[(2, 0, 1, 5)], grid_spacing=[180]), input_specification=QCInputSpecification(driver=DriverEnum.gradient, model=Model(method="UFF", basis=None)), - initial_molecule=[qcng.get_molecule("ethane")] * 2, + initial_molecule=[Molecule(**qcng.get_molecule("ethane", return_dict=True))] * 2, optimization_spec=OptimizationSpecification( procedure="geomeTRIC", keywords={ @@ -334,7 +393,9 @@ def test_torsiondrive_generic(): ), ) + input_data = checkver_and_convert(input_data, request.node.name, "pre") ret = qcng.compute_procedure(input_data, "torsiondrive", raise_error=True) + ret = checkver_and_convert(ret, request.node.name, "post") assert ret.error is None assert ret.success @@ -368,18 +429,21 @@ def test_torsiondrive_generic(): pytest.param("berny", marks=using("berny")), ], ) -def test_optimization_mrchem(input_data, optimizer): +def test_optimization_mrchem(input_data, optimizer, schema_versions, request): + models, _ = schema_versions - input_data["initial_molecule"] = qcng.get_molecule("hydrogen") + input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF"} input_data["input_specification"]["keywords"] = {"world_prec": 1.0e-4} input_data["keywords"]["program"] = "mrchem" - input_data = OptimizationInput(**input_data) + 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) - assert 10 > len(ret.trajectory) > 1 + ret = checkver_and_convert(ret, request.node.name, "post") + assert 10 > len(ret.trajectory) > 1 assert pytest.approx(ret.final_molecule.measure([0, 1]), 1.0e-3) == 1.3860734486984705 assert ret.provenance.creator.lower() == optimizer assert ret.trajectory[0].provenance.creator.lower() == "mrchem" diff --git a/qcengine/tests/test_utils.py b/qcengine/tests/test_utils.py index 6b84dfa4c..9c84a2a49 100644 --- a/qcengine/tests/test_utils.py +++ b/qcengine/tests/test_utils.py @@ -3,17 +3,18 @@ import time import pytest -from qcelemental.models import AtomicInput +import qcelemental from qcengine import util from qcengine.exceptions import InputError +# TODO add schema_versions when change model_wrapper def test_model_wrapper(): with pytest.raises(InputError): -#pydantic.v1.error_wrappers.ValidationError - util.model_wrapper({"bad": "yup"}, AtomicInput) + # pydantic.v1.error_wrappers.ValidationError + util.model_wrapper({"bad": "yup"}, qcelemental.models.AtomicInput) def test_compute_wrapper_capture(): diff --git a/qcengine/util.py b/qcengine/util.py index 7bcff4196..df3366a43 100644 --- a/qcengine/util.py +++ b/qcengine/util.py @@ -55,6 +55,7 @@ def create_mpi_invocation(executable: str, task_config: TaskConfig) -> List[str] return command +# TODO v1.BaseModel def model_wrapper(input_data: Dict[str, Any], model: BaseModel) -> BaseModel: """ Wrap input data in the given model, or return a controlled error @@ -64,6 +65,7 @@ def model_wrapper(input_data: Dict[str, Any], model: BaseModel) -> BaseModel: try: input_data = model(**input_data) except (pydantic.v1.ValidationError) as exc: + # TODO except (pydantic.v1.ValidationError, pydantic.ValidationError) as exc: raise InputError( f"Error creating '{model.__name__}', data could not be correctly parsed:\n{str(exc)}" ) from None From 6082f4ae35e171396e7dfa76d12249340e5c3129 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Thu, 3 Oct 2024 02:43:14 -0400 Subject: [PATCH 2/7] merge compute and compute_procedure --- docs/source/changelog.rst | 2 + qcengine/compute.py | 105 +++++------------- qcengine/programs/model.py | 12 ++ qcengine/programs/tests/test_adcc.py | 4 +- .../programs/tests/test_canonical_fields.py | 4 +- qcengine/programs/tests/test_molpro.py | 2 +- qcengine/programs/tests/test_nwchem.py | 58 +++++----- qcengine/tests/test_procedures.py | 6 +- 8 files changed, 78 insertions(+), 115 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 434a275c5..01f24ade5 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -36,6 +36,8 @@ Breaking Changes - (:pr:`453`) Deps - Require pydantic v2 dependency (don't worry, this isn't changing QCEngine's role as QCSchema I/O runner. Also require pydantic-settings for CLI. @loriab +* as promised, `local_options` has been removed in favor of `task_config`. +* compute and compute_procedure have been merged in favor of the former. New Features ++++++++++++ diff --git a/qcengine/compute.py b/qcengine/compute.py index af4754bf4..09ea21a89 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -33,13 +33,16 @@ def _process_failure_and_return(model, return_dict, raise_error): def compute( - input_data: Union[Dict[str, Any], "AtomicInput"], + input_data: Union[Dict[str, Any], "BaseModel"], # TODO Input base class program: str, raise_error: bool = False, task_config: Optional[Dict[str, Any]] = None, local_options: Optional[Dict[str, Any]] = None, return_dict: bool = False, -) -> Union["AtomicResult", "FailedOperation", Dict[str, Any]]: + schema_version: int = -1, +) -> Union[ + "BaseModel", "FailedOperation", Dict[str, Any] +]: # TODO Output base class, was AtomicResult OptimizationResult """Executes a single CMS program given a QCSchema input. The full specification can be found at: @@ -50,7 +53,7 @@ def compute( input_data A QCSchema input specification in dictionary or model from QCElemental.models program - The CMS program with which to execute the input. + The CMS program or procedure with which to execute the input. raise_error Determines if compute should raise an error or not. retries : int, optional @@ -61,38 +64,36 @@ def compute( Deprecated parameter, renamed to ``task_config`` return_dict Returns a dict instead of qcelemental.models.AtomicResult + schema_version + The schema version to return. If -1, the input schema_version is used. Returns ------- result - AtomicResult, FailedOperation, or Dict representation of either object type + AtomicResult, OptimizationResult, FailedOperation, etc., or Dict representation of any object type A QCSchema representation of the requested output, type depends on return_dict key. """ - output_data = input_data.copy() # lgtm [py/multiple-definition] + try: + output_data = input_data.model_copy() + except AttributeError: + output_data = input_data.copy() # lgtm [py/multiple-definition] + with compute_wrapper(capture_output=False, raise_error=raise_error) as metadata: # Grab the executor and build the input model - executor = get_program(program) + try: + executor = get_procedure(program) + except InputError: + executor = get_program(program) # Build the model and validate - input_data = model_wrapper(input_data, AtomicInput) + input_data = executor.build_input_model(input_data) # calls model_wrapper # Build out task_config if task_config is None: task_config = {} - - if local_options: - warnings.warn( - "Using the `local_options` keyword argument is deprecated in favor of using `task_config`, " - "in version 0.30.0 it will stop working.", - category=FutureWarning, - stacklevel=2, - ) - task_config = {**local_options, **task_config} - input_engine_options = input_data.extras.pop("_qcengine_local_config", {}) - task_config = {**task_config, **input_engine_options} config = get_config(task_config=task_config) @@ -116,63 +117,11 @@ def compute( return handle_output_metadata(output_data, metadata, raise_error=raise_error, return_dict=return_dict) -def compute_procedure( - input_data: Union[Dict[str, Any], "BaseModel"], - procedure: str, - raise_error: bool = False, - task_config: Optional[Dict[str, str]] = None, - local_options: Optional[Dict[str, str]] = None, - return_dict: bool = False, -) -> Union["OptimizationResult", "FailedOperation", Dict[str, Any]]: - """Runs a procedure (a collection of the quantum chemistry executions) - - Parameters - ---------- - input_data : dict or qcelemental.models.OptimizationInput - A JSON input specific to the procedure executed in dictionary or model from QCElemental.models - procedure : {"geometric", "berny"} - The name of the procedure to run - raise_error : bool, option - Determines if compute should raise an error or not. - task_config - A dictionary of local configuration options corresponding to a TaskConfig object. - local_options - Deprecated parameter, renamed to ``task_config`` - return_dict : bool, optional, default True - Returns a dict instead of qcelemental.models.AtomicInput - - Returns - ------ - dict, OptimizationResult, FailedOperation - A QC Schema representation of the requested output, type depends on return_dict key. - """ - # Build out task_config - if task_config is None: - task_config = {} - - if local_options: - warnings.warn( - "Using the `local_options` keyword argument is depreciated in favor of using `task_config`, " - "in version 0.30.0 it will stop working.", - category=FutureWarning, - stacklevel=2, - ) - task_config = {**local_options, **task_config} - - output_data = input_data.copy() # lgtm [py/multiple-definition] - with compute_wrapper(capture_output=False, raise_error=raise_error) as metadata: - - # Grab the executor and build the input model - executor = get_procedure(procedure) - - config = get_config(task_config=task_config) - input_data = executor.build_input_model(input_data) - - # Create a base output data in case of errors - output_data = input_data.copy() # lgtm [py/multiple-definition] - - # Set environment parameters and execute - with environ_context(config=config): - output_data = executor.compute(input_data, config) - - return handle_output_metadata(output_data, metadata, raise_error=raise_error, return_dict=return_dict) +def compute_procedure(*args, **kwargs): + warnings.warn( + "Using the `compute_procedure` function is deprecated in favor of using `compute`, " + "and as soon as version 0.70.0 it will stop working.", + category=FutureWarning, + stacklevel=2, + ) + return compute(*args, **kwargs) diff --git a/qcengine/programs/model.py b/qcengine/programs/model.py index 27da36341..34b86f769 100644 --- a/qcengine/programs/model.py +++ b/qcengine/programs/model.py @@ -8,6 +8,8 @@ from qcengine.config import TaskConfig from qcengine.exceptions import KnownErrorException +from ..util import model_wrapper + logger = logging.getLogger(__name__) @@ -64,6 +66,16 @@ def found(raise_error: bool = False) -> bool: ## Utility + # def _build_model + + def build_input_model(self, data: Dict[str, Any]) -> "AtomicInput": + """ + Quick wrapper around util.model_wrapper for inherited classes + """ + from qcelemental.models.v1 import AtomicInput # TODO v2 + + return model_wrapper(data, AtomicInput) + def get_version(self) -> str: """Finds program, extracts version, returns normalized version string. diff --git a/qcengine/programs/tests/test_adcc.py b/qcengine/programs/tests/test_adcc.py index 97236e704..103151fe0 100644 --- a/qcengine/programs/tests/test_adcc.py +++ b/qcengine/programs/tests/test_adcc.py @@ -25,8 +25,8 @@ def test_run(h2o_data, schema_versions, request): inp = models.AtomicInput( molecule=h2o, driver="properties", model={"method": "adc2", "basis": "sto-3g"}, keywords={"n_singlets": 3} ) - inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, "adcc", raise_error=True, local_options={"ncores": 1}, return_dict=True) + inp = checkver_and_convert(inp, request.node.name, "pre") + ret = qcng.compute(inp, "adcc", raise_error=True, task_config={"ncores": 1}, return_dict=True) ret = checkver_and_convert(ret, request.node.name, "post") ref_excitations = np.array([0.0693704245883876, 0.09773854881340478, 0.21481589246935925]) diff --git a/qcengine/programs/tests/test_canonical_fields.py b/qcengine/programs/tests/test_canonical_fields.py index fd641ac2b..d620df6d3 100644 --- a/qcengine/programs/tests/test_canonical_fields.py +++ b/qcengine/programs/tests/test_canonical_fields.py @@ -51,9 +51,9 @@ def test_protocol_native(program, model, keywords, native, schema_versions, requ inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model, keywords=keywords, protocols=protocols) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True, local_options=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) ret = checkver_and_convert(ret, request.node.name, "post") - + pprint.pprint(ret.dict(), width=200) assert ret.success is True diff --git a/qcengine/programs/tests/test_molpro.py b/qcengine/programs/tests/test_molpro.py index b36459be5..8ef6d2258 100644 --- a/qcengine/programs/tests/test_molpro.py +++ b/qcengine/programs/tests/test_molpro.py @@ -49,7 +49,7 @@ def test_molpro_executor(test_case, schema_versions, request): # Run Molpro inp = checkver_and_convert(inp, request.node.name, "pre") - result = qcng.compute(inp, "molpro", local_options={"ncores": 4}) + result = qcng.compute(inp, "molpro", task_config={"ncores": 4}) result = checkver_and_convert(result, request.node.name, "post") assert result.success is True diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 325eabfe1..0ea8099fb 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -289,30 +289,30 @@ def test_autoz_error(schema_versions, request): # Large molecule that leads to an AutoZ error mol = models.Molecule.from_data(_auto_z_problem) - resi = { - "molecule": mol, - "model": {"method": "hf", "basis": "sto-3g"}, - "driver": "energy", - "protocols": {"error_correction": {"default_policy": False}}, - } # Turn off error correction + resi = { + "molecule": mol, + "model": {"method": "hf", "basis": "sto-3g"}, + "driver": "energy", + "protocols": {"error_correction": {"default_policy": False}}, + } # Turn off error correction resi = checkver_and_convert(resi, request.node.name, "pre") result = qcng.compute(resi, "nwchem", raise_error=False) - result = checkver_and_convert(result, request.node.name, "post", vercheck=False) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) assert not result.success assert "Error when generating redundant atomic coordinates" in result.error.error_message # Turn off autoz resi = { - "molecule": mol, - "model": {"method": "hf", "basis": "sto-3g"}, - "driver": "energy", - "keywords": {"geometry__noautoz": True}, - } + "molecule": mol, + "model": {"method": "hf", "basis": "sto-3g"}, + "driver": "energy", + "keywords": {"geometry__noautoz": True}, + } resi = checkver_and_convert(resi, request.node.name, "pre") result = qcng.compute(resi, "nwchem", raise_error=False) - result = checkver_and_convert(result, request.node.name, "post",vercheck=False) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) # Ok if it crashes for other reasons assert "Error when generating redundant atomic coordinates" not in result.error.error_message @@ -326,11 +326,11 @@ def test_autoz_error_correction(schema_versions, request): # Large molecule that leads to an AutoZ error mol = models.Molecule.from_data(_auto_z_problem) resi = { - "molecule": mol, - "model": {"method": "hf", "basis": "sto-3g"}, - "driver": "energy", - "keywords": {"scf__maxiter": 250, "scf__thresh": 1e-1}, - } + "molecule": mol, + "model": {"method": "hf", "basis": "sto-3g"}, + "driver": "energy", + "keywords": {"scf__maxiter": 250, "scf__thresh": 1e-1}, + } resi = checkver_and_convert(resi, request.node.name, "pre") result = qcng.compute(resi, "nwchem", raise_error=True) @@ -359,15 +359,15 @@ def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema h20v2 = models.Molecule.from_data(h20v2_data) resi = { - "molecule": h20v2, - "model": {"method": method, "basis": "sto-3g"}, - "driver": "energy", - "keywords": { - keyword: init_iters, - "qc_module": use_tce, - "scf__uhf": True, - }, # UHF needed for SCF test - } + "molecule": h20v2, + "model": {"method": method, "basis": "sto-3g"}, + "driver": "energy", + "keywords": { + keyword: init_iters, + "qc_module": use_tce, + "scf__uhf": True, + }, # UHF needed for SCF test + } resi = checkver_and_convert(resi, request.node.name, "pre") result = qcng.compute(resi, "nwchem", raise_error=True) @@ -398,7 +398,7 @@ def test_restart(nh2_data, tmpdir, schema_versions, request): local_options = {"scratch_messy": True, "scratch_directory": str(tmpdir)} resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", local_options=local_options, raise_error=False) + result = qcng.compute(resi, "nwchem", task_config=local_options, raise_error=False) result = checkver_and_convert(result, request.node.name, "post", vercheck=False) assert not result.success @@ -408,7 +408,7 @@ def test_restart(nh2_data, tmpdir, schema_versions, request): result = qcng.compute( resi, "nwchem", - local_options=local_options, + task_config=local_options, raise_error=False, ) result = checkver_and_convert(result, request.node.name, "post") diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index e1a12088f..47ee8949c 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -77,7 +77,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, local_options={"memory": "5000"}) + ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, task_config={"memory": "5000"}) ret = checkver_and_convert(ret, request.node.name, "post") assert pytest.approx(ret.trajectory[0].provenance.memory, 1) == 4900 @@ -357,12 +357,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", local_options=local_opts, raise_error=False) + ret = qcng.compute_procedure(input_data, "nwchemdriver", task_config=local_opts, raise_error=False) 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", local_options=local_opts, raise_error=True) + new_ret = qcng.compute_procedure(input_data, "nwchemdriver", task_config=local_opts, raise_error=True) new_ret = checkver_and_convert(new_ret, request.node.name, "post") assert new_ret.success From 0f7af724fae4bc68f00ba65c9b8985c996d4d570 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Thu, 3 Oct 2024 12:17:53 -0400 Subject: [PATCH 3/7] fix procedure kw --- qcengine/compute.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qcengine/compute.py b/qcengine/compute.py index 09ea21a89..587e6a950 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -124,4 +124,6 @@ def compute_procedure(*args, **kwargs): category=FutureWarning, stacklevel=2, ) + if "procedure" in kwargs: + kwargs["program"] = kwargs.pop("procedure") return compute(*args, **kwargs) From b289c02ee6726e6b3a3caa020b62e382921c2496 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Sat, 5 Oct 2024 01:37:51 -0400 Subject: [PATCH 4/7] tests working --- docs/source/changelog.rst | 3 + qcengine/compute.py | 25 ++-- qcengine/procedures/berny.py | 6 +- qcengine/procedures/geometric.py | 6 +- qcengine/procedures/model.py | 28 +++- qcengine/procedures/nwchem_opt/__init__.py | 6 +- qcengine/procedures/optking.py | 6 +- qcengine/procedures/torsiondrive.py | 6 +- qcengine/programs/model.py | 44 ++++-- .../programs/tests/standard_suite_runner.py | 8 +- qcengine/programs/tests/test_adcc.py | 8 +- qcengine/programs/tests/test_alignment.py | 6 +- .../programs/tests/test_canonical_config.py | 17 +-- .../programs/tests/test_canonical_fields.py | 4 +- qcengine/programs/tests/test_dftd3_mp2d.py | 44 +++--- qcengine/programs/tests/test_dftd4.py | 30 ++-- qcengine/programs/tests/test_ghost.py | 16 +-- qcengine/programs/tests/test_molpro.py | 4 +- qcengine/programs/tests/test_mrchem.py | 14 +- qcengine/programs/tests/test_nwchem.py | 68 ++++----- qcengine/programs/tests/test_programs.py | 129 +++++++++++------- qcengine/programs/tests/test_qchem.py | 10 +- qcengine/programs/tests/test_qcore.py | 10 +- qcengine/programs/tests/test_sdftd3.py | 28 ++-- .../programs/tests/test_standard_suite.py | 21 +-- .../tests/test_standard_suite_ccsd(t).py | 13 +- .../programs/tests/test_standard_suite_hf.py | 15 +- qcengine/programs/tests/test_terachem.py | 4 +- qcengine/programs/tests/test_turbomole.py | 20 +-- qcengine/programs/tests/test_xtb.py | 58 ++++---- qcengine/testing.py | 28 ++-- qcengine/tests/test_harness_canonical.py | 35 +++-- qcengine/tests/test_procedures.py | 68 +++++---- qcengine/util.py | 26 +++- 34 files changed, 486 insertions(+), 328 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 01f24ade5..88a4cd733 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -38,6 +38,9 @@ Breaking Changes for CLI. @loriab * as promised, `local_options` has been removed in favor of `task_config`. * compute and compute_procedure have been merged in favor of the former. +* `compute` learned an optional argument `return_version` to specify the schema_version of the + returned model or dictionary. By default it'll return the input schema_version. If not + determinable, will return v1. @loriab New Features ++++++++++++ diff --git a/qcengine/compute.py b/qcengine/compute.py index 587e6a950..e43979469 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -37,9 +37,8 @@ def compute( program: str, raise_error: bool = False, task_config: Optional[Dict[str, Any]] = None, - local_options: Optional[Dict[str, Any]] = None, return_dict: bool = False, - schema_version: int = -1, + return_version: int = -1, ) -> Union[ "BaseModel", "FailedOperation", Dict[str, Any] ]: # TODO Output base class, was AtomicResult OptimizationResult @@ -53,18 +52,16 @@ def compute( input_data A QCSchema input specification in dictionary or model from QCElemental.models program - The CMS program or procedure with which to execute the input. + The CMS program or procedure with which to execute the input. E.g., "psi4", "rdkit", "geometric". raise_error Determines if compute should raise an error or not. retries : int, optional The number of random tries to retry for. task_config A dictionary of local configuration options corresponding to a TaskConfig object. - local_options - Deprecated parameter, renamed to ``task_config`` return_dict - Returns a dict instead of qcelemental.models.AtomicResult - schema_version + Returns a dict instead of qcelemental.models.AtomicResult # TODO base Result class + return_version The schema version to return. If -1, the input schema_version is used. Returns @@ -75,20 +72,24 @@ def compute( """ try: + # models, v1 or v2 output_data = input_data.model_copy() except AttributeError: + # dicts output_data = input_data.copy() # lgtm [py/multiple-definition] with compute_wrapper(capture_output=False, raise_error=raise_error) as metadata: - - # Grab the executor and build the input model + # Grab the executor harness try: executor = get_procedure(program) except InputError: executor = get_program(program) # Build the model and validate - input_data = executor.build_input_model(input_data) # calls model_wrapper + # * calls model_wrapper with the (Atomic|Optimization|etc)Input for which the harness was designed + # * upon return, input_data is a model of the type (e.g., Atomic) and version (e.g., 1 or 2) the harness prefers. for now, v1. + input_data, input_schema_version = executor.build_input_model(input_data, return_input_schema_version=True) + convert_version = input_schema_version if return_version == -1 else return_version # Build out task_config if task_config is None: @@ -114,7 +115,9 @@ def compute( except: raise - return handle_output_metadata(output_data, metadata, raise_error=raise_error, return_dict=return_dict) + return handle_output_metadata( + output_data, metadata, raise_error=raise_error, return_dict=return_dict, convert_version=convert_version + ) def compute_procedure(*args, **kwargs): diff --git a/qcengine/procedures/berny.py b/qcengine/procedures/berny.py index 7159f7d4a..65dffacbd 100644 --- a/qcengine/procedures/berny.py +++ b/qcengine/procedures/berny.py @@ -26,8 +26,10 @@ def found(self, raise_error: bool = False) -> bool: raise_msg="Please install via `pip install pyberny`.", ) - def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> "OptimizationInput": - return self._build_model(data, OptimizationInput) + def build_input_model( + self, data: Union[Dict[str, Any], "OptimizationInput"], *, return_input_schema_version: bool = False + ) -> "OptimizationInput": + return self._build_model(data, "OptimizationInput", return_input_schema_version=return_input_schema_version) def compute( self, input_data: "OptimizationInput", config: "TaskConfig" diff --git a/qcengine/procedures/geometric.py b/qcengine/procedures/geometric.py index d8b19e40a..f5c75d7a2 100644 --- a/qcengine/procedures/geometric.py +++ b/qcengine/procedures/geometric.py @@ -31,8 +31,10 @@ def get_version(self) -> str: return self.version_cache[which_prog] - def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> "OptimizationInput": - return self._build_model(data, OptimizationInput) + def build_input_model( + self, data: Union[Dict[str, Any], "OptimizationInput"], *, return_input_schema_version: bool = False + ) -> "OptimizationInput": + return self._build_model(data, "OptimizationInput", return_input_schema_version=return_input_schema_version) def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "OptimizationResult": try: diff --git a/qcengine/procedures/model.py b/qcengine/procedures/model.py index e5a334ddf..0b25588f9 100644 --- a/qcengine/procedures/model.py +++ b/qcengine/procedures/model.py @@ -1,6 +1,7 @@ import abc -from typing import Any, Dict, Union +from typing import Any, Dict, Tuple, Union +import qcelemental from pydantic import BaseModel, ConfigDict from ..util import model_wrapper @@ -52,12 +53,33 @@ def found(self, raise_error: bool = False) -> bool: If the proceudre was found or not. """ - def _build_model(self, data: Dict[str, Any], model: "BaseModel") -> "BaseModel": + def _build_model( + self, data: Dict[str, Any], model: "BaseModel", /, *, return_input_schema_version: bool = False + ) -> Union["BaseModel", Tuple["BaseModel", int]]: """ Quick wrapper around util.model_wrapper for inherited classes """ - return model_wrapper(data, model) + v1_model = getattr(qcelemental.models.v1, model) + v2_model = getattr(qcelemental.models.v2, model) + + if isinstance(data, v1_model): + mdl = model_wrapper(data, v1_model) + elif isinstance(data, v2_model): + mdl = model_wrapper(data, v2_model) + elif isinstance(data, dict): + # remember these are user-provided dictionaries, so they'll have the mandatory fields, + # like driver, not the helpful discriminator fields like schema_version. + + # 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 + + 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 + else: + return mdl.convert_v(1) 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 e11b6db23..3b066c173 100644 --- a/qcengine/procedures/nwchem_opt/__init__.py +++ b/qcengine/procedures/nwchem_opt/__init__.py @@ -22,8 +22,10 @@ def get_version(self) -> str: nwc_harness = NWChemHarness() return nwc_harness.get_version() - def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> OptimizationInput: - return self._build_model(data, OptimizationInput) + def build_input_model( + self, data: Union[Dict[str, Any], "OptimizationInput"], *, return_input_schema_version: bool = False + ) -> "OptimizationInput": + return self._build_model(data, "OptimizationInput", return_input_schema_version=return_input_schema_version) def compute(self, input_data: OptimizationInput, config: TaskConfig) -> "BaseModel": nwc_harness = NWChemHarness() diff --git a/qcengine/procedures/optking.py b/qcengine/procedures/optking.py index 0c078a2d1..472f44775 100644 --- a/qcengine/procedures/optking.py +++ b/qcengine/procedures/optking.py @@ -20,8 +20,10 @@ def found(self, raise_error: bool = False) -> bool: raise_msg="Please install via `conda install optking -c conda-forge`.", ) - def build_input_model(self, data: Union[Dict[str, Any], "OptimizationInput"]) -> "OptimizationInput": - return self._build_model(data, OptimizationInput) + def build_input_model( + self, data: Union[Dict[str, Any], "OptimizationInput"], *, return_input_schema_version: bool = False + ) -> "OptimizationInput": + return self._build_model(data, "OptimizationInput", return_input_schema_version=return_input_schema_version) def get_version(self) -> str: self.found(raise_error=True) diff --git a/qcengine/procedures/torsiondrive.py b/qcengine/procedures/torsiondrive.py index ad7f88059..5bcd64066 100644 --- a/qcengine/procedures/torsiondrive.py +++ b/qcengine/procedures/torsiondrive.py @@ -26,8 +26,10 @@ def found(self, raise_error: bool = False) -> bool: raise_msg="Please install via `conda install torsiondrive -c conda-forge`.", ) - def build_input_model(self, data: Union[Dict[str, Any], "TorsionDriveInput"]) -> "TorsionDriveInput": - return self._build_model(data, TorsionDriveInput) + def build_input_model( + self, data: Union[Dict[str, Any], "TorsionDriveInput"], *, return_input_schema_version: bool = False + ) -> "TorsionDriveInput": + return self._build_model(data, "TorsionDriveInput", return_input_schema_version=return_input_schema_version) def _compute(self, input_model: "TorsionDriveInput", config: "TaskConfig"): diff --git a/qcengine/programs/model.py b/qcengine/programs/model.py index 34b86f769..ad2e32502 100644 --- a/qcengine/programs/model.py +++ b/qcengine/programs/model.py @@ -2,8 +2,8 @@ import logging from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union +import qcelemental from pydantic import BaseModel, ConfigDict -from qcelemental.models import AtomicInput, AtomicResult, FailedOperation from qcengine.config import TaskConfig from qcengine.exceptions import KnownErrorException @@ -33,7 +33,7 @@ def __init__(self, **kwargs): super().__init__(**{**self._defaults, **kwargs}) @abc.abstractmethod - def compute(self, input_data: AtomicInput, config: TaskConfig) -> Union[AtomicResult, FailedOperation]: + def compute(self, input_data: "AtomicInput", config: TaskConfig) -> Union["AtomicResult", "FailedOperation"]: """Top-level compute method to be implemented for every ProgramHarness Note: @@ -68,13 +68,34 @@ def found(raise_error: bool = False) -> bool: # def _build_model - def build_input_model(self, data: Dict[str, Any]) -> "AtomicInput": + def build_input_model( + self, data: Dict[str, Any], *, return_input_schema_version: bool = False + ) -> Union["AtomicInput", Tuple["AtomicInput", int]]: """ Quick wrapper around util.model_wrapper for inherited classes """ - from qcelemental.models.v1 import AtomicInput # TODO v2 - - return model_wrapper(data, AtomicInput) + # Note: Someday when the multiple QCSchema versions QCEngine supports are all within the + # Pydantic v2 API base class, this can use discriminated unions instead of logic. + + if isinstance(data, qcelemental.models.v1.AtomicInput): + mdl = model_wrapper(data, qcelemental.models.v1.AtomicInput) + elif isinstance(data, qcelemental.models.v2.AtomicInput): + mdl = model_wrapper(data, qcelemental.models.v2.AtomicInput) + elif isinstance(data, dict): + # remember these are user-provided dictionaries, so they'll have the mandatory fields, + # like driver, not the helpful discriminator fields like schema_version. + + # 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, qcelemental.models.v1.AtomicInput + ) # TODO v2 # TODO kill off excuse_as_v2, now fix 2->-1 in schema_versions + + 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 + else: + return mdl.convert_v(1) def get_version(self) -> str: """Finds program, extracts version, returns normalized version string. @@ -88,7 +109,7 @@ def get_version(self) -> str: ## Computers def build_input( - self, input_model: AtomicInput, config: TaskConfig, template: Optional[str] = None + self, input_model: "AtomicInput", config: TaskConfig, template: Optional[str] = None ) -> Dict[str, Any]: raise ValueError("build_input is not implemented for {}.", self.__class__) @@ -124,10 +145,10 @@ class ErrorCorrectionProgramHarness(ProgramHarness, abc.ABC): ``ErrorCorrectionProgramHarness`` and used to determine if/how to re-run the computation. """ - def _compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult: + def _compute(self, input_data: "AtomicInput", config: TaskConfig) -> "AtomicResult": raise NotImplementedError() - def compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult: + def compute(self, input_data: "AtomicInput", config: TaskConfig) -> "AtomicResult": # Get the error correction configuration error_policy = input_data.protocols.error_correction @@ -163,7 +184,10 @@ def compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult: keyword_updates = e.create_keyword_update(local_input_data) new_keywords = local_input_data.keywords.copy() new_keywords.update(keyword_updates) - local_input_data = AtomicInput(**local_input_data.dict(exclude={"keywords"}), keywords=new_keywords) + # TODO v2 + local_input_data = qcelemental.models.v1.AtomicInput( + **local_input_data.dict(exclude={"keywords"}), keywords=new_keywords + ) # Store the error details and mitigations employed observed_errors[e.error_name] = {"details": e.details, "keyword_updates": keyword_updates} diff --git a/qcengine/programs/tests/standard_suite_runner.py b/qcengine/programs/tests/standard_suite_runner.py index a6060d846..37a400a41 100644 --- a/qcengine/programs/tests/standard_suite_runner.py +++ b/qcengine/programs/tests/standard_suite_runner.py @@ -17,7 +17,7 @@ pp = pprint.PrettyPrinter(width=120) -def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame, models): +def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame, models, retver): qcprog = inp["call"] qc_module_in = inp["qc_module"] # returns ""|"-" # input-specified routing @@ -143,14 +143,16 @@ def runner_asserter(inp, ref_subject, method, basis, tnm, scramble, frame, model errtype, errmatch, reason = inp["error"] with pytest.raises(errtype) as e: atin = checkver_and_convert(atin, tnm, "pre") - qcng.compute(atin, qcprog, raise_error=True, return_dict=True, task_config=local_options) + qcng.compute( + atin, qcprog, raise_error=True, return_dict=True, task_config=local_options, return_version=retver + ) assert re.search(errmatch, str(e.value)), f"Not found: {errtype} '{errmatch}' in {e.value}" # _recorder(qcprog, qc_module_in, driver, method, reference, fcae, scf_type, corl_type, "error", "nyi: " + reason) return atin = checkver_and_convert(atin, tnm, "pre") - wfn = qcng.compute(atin, qcprog, raise_error=True, task_config=local_options) + wfn = qcng.compute(atin, qcprog, raise_error=True, task_config=local_options, return_version=retver) wfn = checkver_and_convert(wfn, tnm, "post") print("WFN") diff --git a/qcengine/programs/tests/test_adcc.py b/qcengine/programs/tests/test_adcc.py index 103151fe0..55ed4f7f9 100644 --- a/qcengine/programs/tests/test_adcc.py +++ b/qcengine/programs/tests/test_adcc.py @@ -19,15 +19,19 @@ def h2o_data(): @using("adcc") def test_run(h2o_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) inp = models.AtomicInput( molecule=h2o, driver="properties", model={"method": "adc2", "basis": "sto-3g"}, keywords={"n_singlets": 3} ) + inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, "adcc", raise_error=True, task_config={"ncores": 1}, return_dict=True) + ret = qcng.compute( + inp, "adcc", raise_error=True, task_config={"ncores": 1}, return_dict=True, return_version=retver + ) ret = checkver_and_convert(ret, request.node.name, "post") + # note dict-out ref_excitations = np.array([0.0693704245883876, 0.09773854881340478, 0.21481589246935925]) ref_hf_energy = -74.45975898670224 diff --git a/qcengine/programs/tests/test_alignment.py b/qcengine/programs/tests/test_alignment.py index f3f7f29dc..e8e49ff9a 100644 --- a/qcengine/programs/tests/test_alignment.py +++ b/qcengine/programs/tests/test_alignment.py @@ -11,7 +11,7 @@ @pytest.fixture def clsd_open_pmols(schema_versions): - models, _ = schema_versions + models, _, _ = schema_versions frame_not_important = { name[:-4]: models.Molecule.from_data(smol, name=name[:-4]) for name, smol in std_molecules.items() @@ -94,6 +94,7 @@ def clsd_open_pmols(schema_versions): ], ) def test_hf_alignment(inp, scramble, frame, driver, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( *_processor( inp, @@ -102,7 +103,8 @@ def test_hf_alignment(inp, scramble, frame, driver, basis, subjects, clsd_open_p subjects, clsd_open_pmols, request, - schema_versions[0], + models, + retver, driver, "hf", scramble=scramble, diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index 9b8237af3..6df632418 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -47,6 +47,7 @@ def _get_molecule(program, method, molcls): return molcls(**dmol) + @pytest.mark.parametrize( "memory_trickery", [ @@ -80,7 +81,7 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, sch * If this test doesn't work, implement or adjust ``config.memory`` in your harness. """ - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip(f"Program '{program}' not found.") @@ -106,8 +107,8 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, sch inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=use_keywords) - inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + inp = checkver_and_convert(inp, request.node.name, "pre") + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict(), return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") pprint.pprint(ret.dict(), width=200) @@ -152,7 +153,7 @@ def test_local_options_scratch(program, model, keywords, schema_versions, reques ``config.scratch_messy`` in your harness. """ - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip(f"Program '{program}' not found.") @@ -177,7 +178,7 @@ def test_local_options_scratch(program, model, keywords, schema_versions, reques inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict(), return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") pprint.pprint(ret.dict(), width=200) @@ -240,7 +241,7 @@ def test_local_options_ncores(program, model, keywords, ncores, schema_versions, * If this test doesn't work, implement or adjust ``config.ncores`` in your harness. """ - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip(f"Program '{program}' not found.") @@ -263,9 +264,9 @@ def test_local_options_ncores(program, model, keywords, ncores, schema_versions, inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict(), return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") - + pprint.pprint(ret.dict(), width=200) assert ret.success is True diff --git a/qcengine/programs/tests/test_canonical_fields.py b/qcengine/programs/tests/test_canonical_fields.py index d620df6d3..e35918239 100644 --- a/qcengine/programs/tests/test_canonical_fields.py +++ b/qcengine/programs/tests/test_canonical_fields.py @@ -25,7 +25,7 @@ def test_protocol_native(program, model, keywords, native, schema_versions, requ * If this test doesn't work, implement or adjust ``native_files`` in your harness. """ - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip(f"Program '{program}' not found.") @@ -51,7 +51,7 @@ def test_protocol_native(program, model, keywords, native, schema_versions, requ inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model, keywords=keywords, protocols=protocols) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict(), return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") pprint.pprint(ret.dict(), width=200) diff --git a/qcengine/programs/tests/test_dftd3_mp2d.py b/qcengine/programs/tests/test_dftd3_mp2d.py index fe83f67d7..38c8824b6 100644 --- a/qcengine/programs/tests/test_dftd3_mp2d.py +++ b/qcengine/programs/tests/test_dftd3_mp2d.py @@ -14,12 +14,16 @@ @using("dftd3") @pytest.mark.parametrize("method", ["b3lyp-d3", "b3lyp-d3m", "b3lyp-d3bj", "b3lyp-d3mbj"]) def test_dftd3_task(method, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions - json_data = {"molecule": models.Molecule(**qcng.get_molecule("eneyne", return_dict=True)), "driver": "energy", "model": {"method": method}} + json_data = { + "molecule": models.Molecule(**qcng.get_molecule("eneyne", return_dict=True)), + "driver": "energy", + "model": {"method": method}, + } json_data = checkver_and_convert(json_data, request.node.name, "pre") - ret = qcng.compute(json_data, "dftd3", raise_error=True, return_dict=True) + ret = qcng.compute(json_data, "dftd3", raise_error=True, return_dict=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret["driver"] == "energy" @@ -34,7 +38,7 @@ def test_dftd3_task(method, schema_versions, request): @using("dftd3") def test_dftd3_error(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions json_data = { "molecule": models.Molecule(**qcng.get_molecule("eneyne", return_dict=True)), @@ -49,7 +53,7 @@ def test_dftd3_error(schema_versions, request): input_data["driver"] = "properties" input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "dftd3", raise_error=True) + ret = qcng.compute(input_data, "dftd3", raise_error=True, return_version=retver) assert "Driver properties not implemented" in str(exc.value) @@ -59,7 +63,7 @@ def test_dftd3_error(schema_versions, request): input_data["model"]["method"] = "b3lyp-quadD" input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "dftd3", raise_error=True) + ret = qcng.compute(input_data, "dftd3", raise_error=True, return_version=retver) assert "correction level" in str(exc.value) @@ -1524,7 +1528,7 @@ def test_dftd3__from_arrays__supplement(): @using("dftd3") def test_3(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions sys = qcel.molparse.from_string(seneyne)["qm"] @@ -1538,7 +1542,7 @@ def test_3(schema_versions, request): } resinp = checkver_and_convert(resinp, request.node.name, "pre") - res = qcng.compute(resinp, "dftd3", raise_error=True) + res = qcng.compute(resinp, "dftd3", raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") res = res.dict() @@ -1649,7 +1653,7 @@ def test_qcdb__energy_d3(): ], ) def test_mp2d__run_mp2d__2body(inp, subjects, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1669,7 +1673,7 @@ def test_mp2d__run_mp2d__2body(inp, subjects, schema_versions, request): "keywords": {}, } resinp = checkver_and_convert(resinp, request.node.name, "pre") - jrec = qcng.compute(resinp, "mp2d", raise_error=True) + jrec = qcng.compute(resinp, "mp2d", raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() @@ -1745,7 +1749,7 @@ def test_mp2d__run_mp2d__2body(inp, subjects, schema_versions, request): # fmt: on ) def test_dftd3__run_dftd3__2body(inp, program, subjects, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1762,7 +1766,7 @@ def test_dftd3__run_dftd3__2body(inp, program, subjects, schema_versions, reques **inp["qcsk"], ) atin = checkver_and_convert(atin, request.node.name, "pre") - jrec = qcng.compute(atin, program, raise_error=True) + jrec = qcng.compute(atin, program, raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() pprint.pprint(jrec) @@ -1813,7 +1817,7 @@ def test_dftd3__run_dftd3__2body(inp, program, subjects, schema_versions, reques # fmt: on ) def test_dftd3__run_dftd3__2body_error(inp, subjects, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1832,7 +1836,7 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, schema_versions, request): **inp["qcsk"], ) atin = checkver_and_convert(atin, request.node.name, "pre") - jrec = qcng.compute(atin, program, raise_error=True) + jrec = qcng.compute(atin, program, raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() @@ -1864,7 +1868,7 @@ def test_dftd3__run_dftd3__2body_error(inp, subjects, schema_versions, request): ], ) def test_dftd3__run_dftd3__3body(inp, subjects, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1884,7 +1888,7 @@ def test_dftd3__run_dftd3__3body(inp, subjects, schema_versions, request): "keywords": {}, } resinp = checkver_and_convert(resinp, request.node.name, "pre") - jrec = qcng.compute(resinp, "dftd3", raise_error=True) + jrec = qcng.compute(resinp, "dftd3", raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() @@ -1932,7 +1936,7 @@ def test_dftd3__run_dftd3__3body(inp, subjects, schema_versions, request): ], ) def test_sapt_pairwise(inp, program, extrakw, subjects, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1953,7 +1957,7 @@ def test_sapt_pairwise(inp, program, extrakw, subjects, schema_versions, request }, ) atin = checkver_and_convert(atin, request.node.name, "pre") - jrec = qcng.compute(atin, program, raise_error=True) + jrec = qcng.compute(atin, program, raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() @@ -1996,7 +2000,7 @@ def test_sapt_pairwise(inp, program, extrakw, subjects, schema_versions, request ], ) def test_gcp(inp, subjects, program, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -2016,7 +2020,7 @@ def test_gcp(inp, subjects, program, schema_versions, request): "keywords": {}, } resinp = checkver_and_convert(resinp, request.node.name, "pre") - jrec = qcng.compute(resinp, program, raise_error=True) + jrec = qcng.compute(resinp, program, raise_error=True, return_version=retver) jrec = checkver_and_convert(jrec, request.node.name, "post") jrec = jrec.dict() diff --git a/qcengine/programs/tests/test_dftd4.py b/qcengine/programs/tests/test_dftd4.py index 4a4fe3983..99a9d2279 100644 --- a/qcengine/programs/tests/test_dftd4.py +++ b/qcengine/programs/tests/test_dftd4.py @@ -15,7 +15,7 @@ @using("dftd4") def test_dftd4_task_b97m_m01(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -27,8 +27,8 @@ def test_dftd4_task_b97m_m01(schema_versions, request): driver="energy", ) - atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") + atomic_result = qcng.compute(atomic_input, "dftd4", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) @@ -38,7 +38,7 @@ def test_dftd4_task_b97m_m01(schema_versions, request): @using("dftd4") def test_dftd4_task_tpss_m02(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 2.0e-8 @@ -76,8 +76,8 @@ def test_dftd4_task_tpss_m02(schema_versions, request): driver="gradient", ) - atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") + atomic_result = qcng.compute(atomic_input, "dftd4", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -86,7 +86,7 @@ def test_dftd4_task_tpss_m02(schema_versions, request): @using("dftd4") def test_dftd4_task_r2scan_m03(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -118,8 +118,8 @@ def test_dftd4_task_r2scan_m03(schema_versions, request): model={"method": "r2scan"}, ) - atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") + atomic_result = qcng.compute(atomic_input, "dftd4", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) @@ -129,7 +129,7 @@ def test_dftd4_task_r2scan_m03(schema_versions, request): @using("dftd4") def test_dftd4_task_unknown_method(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -141,8 +141,8 @@ def test_dftd4_task_unknown_method(schema_versions, request): error_type="input error", error_message="Functional 'non-existent-method' not known" ) - atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") + atomic_result = qcng.compute(atomic_input, "dftd4", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) @@ -152,7 +152,7 @@ def test_dftd4_task_unknown_method(schema_versions, request): @using("dftd4") def test_dftd4_task_cold_fusion(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule={ @@ -174,8 +174,8 @@ def test_dftd4_task_cold_fusion(schema_versions, request): error_message="Too close interatomic distances found", ) - atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "dftd4") + atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") + atomic_result = qcng.compute(atomic_input, "dftd4", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) diff --git a/qcengine/programs/tests/test_ghost.py b/qcengine/programs/tests/test_ghost.py index d9a43d3c2..76e46f86b 100644 --- a/qcengine/programs/tests/test_ghost.py +++ b/qcengine/programs/tests/test_ghost.py @@ -39,18 +39,18 @@ def hene_data(): ], ) def test_simple_ghost(driver, program, basis, keywords, hene_data, schema_versions, request): - models, models_out = schema_versions + models, retver, _ = schema_versions hene = models.Molecule.from_data(hene_data) resi = {"molecule": hene, "driver": driver, "model": {"method": "hf", "basis": basis}, "keywords": keywords} if program == "gamess": with pytest.raises(qcng.exceptions.InputError) as e: - qcng.compute(resi, program, raise_error=True, return_dict=True) + qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) pytest.xfail("no ghosts with gamess") resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == driver @@ -171,7 +171,7 @@ def test_simple_ghost(driver, program, basis, keywords, hene_data, schema_versio ], ) def test_tricky_ghost(driver, qcprog, subject, basis, keywords, schema_versions, request): - models, models_out = schema_versions + models, retver, _ = schema_versions dmol = eneyne_ne_qcschemamols()["eneyne"][subject] # Freeze the input orientation so that output arrays are aligned to input @@ -196,11 +196,11 @@ def test_tricky_ghost(driver, qcprog, subject, basis, keywords, schema_versions, if qcprog == "gamess" and subject in ["mAgB", "gAmB"]: with pytest.raises(qcng.exceptions.InputError) as e: - res = qcng.compute(atin, qcprog, raise_error=True) + qcng.compute(atin, qcprog, raise_error=True, return_version=retver) pytest.xfail("no ghosts with gamess") atin = checkver_and_convert(atin, request.node.name, "pre") - atres = qcng.compute(atin, qcprog) + atres = qcng.compute(atin, qcprog, return_version=retver) atres = checkver_and_convert(atres, request.node.name, "post") pprint.pprint(atres.dict(), width=200) @@ -270,7 +270,7 @@ def test_tricky_ghost(driver, qcprog, subject, basis, keywords, schema_versions, ], ) def test_atom_labels(qcprog, basis, keywords, schema_versions, request): - models, models_out = schema_versions + models, retver, _ = schema_versions kmol = models.Molecule.from_data( """ @@ -290,7 +290,7 @@ def test_atom_labels(qcprog, basis, keywords, schema_versions, request): ) atin = checkver_and_convert(atin, request.node.name, "pre") - atres = qcng.compute(atin, qcprog) + atres = qcng.compute(atin, qcprog, return_version=retver) atres = checkver_and_convert(atres, request.node.name, "post") pprint.pprint(atres.dict(), width=200) diff --git a/qcengine/programs/tests/test_molpro.py b/qcengine/programs/tests/test_molpro.py index 8ef6d2258..a2b535330 100644 --- a/qcengine/programs/tests/test_molpro.py +++ b/qcengine/programs/tests/test_molpro.py @@ -41,7 +41,7 @@ def test_molpro_input_formatter(test_case): @using("molpro") @pytest.mark.parametrize("test_case", molpro_info.list_test_cases()) def test_molpro_executor(test_case, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # Get input file data data = molpro_info.get_test_data(test_case) @@ -49,7 +49,7 @@ def test_molpro_executor(test_case, schema_versions, request): # Run Molpro inp = checkver_and_convert(inp, request.node.name, "pre") - result = qcng.compute(inp, "molpro", task_config={"ncores": 4}) + result = qcng.compute(inp, "molpro", task_config={"ncores": 4}, return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success is True diff --git a/qcengine/programs/tests/test_mrchem.py b/qcengine/programs/tests/test_mrchem.py index 421b745da..d0c74a2e1 100644 --- a/qcengine/programs/tests/test_mrchem.py +++ b/qcengine/programs/tests/test_mrchem.py @@ -19,7 +19,7 @@ def h2o(schema_versions): @pytest.fixture def fh(schema_versions): - models, _ = schema_versions + models, _, _ = schema_versions return models.Molecule( geometry=[[0.000000000000, 0.000000000000, -1.642850273986], [0.000000000000, 0.000000000000, 0.087149726014]], symbols=["H", "F"], @@ -31,7 +31,7 @@ def fh(schema_versions): @using("mrchem") def test_energy(h2o, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions mr_kws = { "world_prec": 1.0e-3, @@ -49,7 +49,7 @@ def test_energy(h2o, schema_versions, request): ) inp = checkver_and_convert(inp, request.node.name, "pre") - res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully @@ -69,7 +69,7 @@ def test_energy(h2o, schema_versions, request): @using("mrchem") def test_dipole(h2o, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions mr_kws = { "world_prec": 1.0e-3, @@ -87,7 +87,7 @@ def test_dipole(h2o, schema_versions, request): ) inp = checkver_and_convert(inp, request.node.name, "pre") - res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully @@ -107,7 +107,7 @@ def test_dipole(h2o, schema_versions, request): @using("mrchem") def test_gradient(fh, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions mr_kws = { "world_prec": 1.0e-3, @@ -124,7 +124,7 @@ def test_gradient(fh, schema_versions, request): ) inp = checkver_and_convert(inp, request.node.name, "pre") - res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True) + res = qcng.compute(inp, "mrchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully diff --git a/qcengine/programs/tests/test_nwchem.py b/qcengine/programs/tests/test_nwchem.py index 0ea8099fb..745a4048b 100644 --- a/qcengine/programs/tests/test_nwchem.py +++ b/qcengine/programs/tests/test_nwchem.py @@ -62,14 +62,14 @@ def nh2_data(): @using("nwchem") def test_b3lyp(nh2_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) # Run NH2 resi = {"molecule": nh2, "driver": "energy", "model": {"method": "b3lyp", "basis": "3-21g"}} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") # Make sure the calculation completed successfully @@ -92,14 +92,14 @@ def test_b3lyp(nh2_data, schema_versions, request): @using("nwchem") def test_hess(nh2_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "hessian", "model": {"method": "b3lyp", "basis": "3-21g"}} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False) - res = checkver_and_convert(res, request.node.name, "post") + res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False, return_version=retver) + res = checkver_and_convert(res, request.node.name, "post") # , excuse_as_v2=True) assert compare_values(-3.5980754370e-02, res.return_result[0, 0], atol=1e-3) assert compare_values(0, res.return_result[1, 0], atol=1e-3) @@ -112,8 +112,8 @@ def test_hess(nh2_data, schema_versions, request): resi["molecule"] = shifted_nh2 resi = checkver_and_convert(resi, request.node.name, "pre") - res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False) - res_shifted = checkver_and_convert(res_shifted, request.node.name, "post") + res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=False, return_version=retver) + res_shifted = checkver_and_convert(res_shifted, request.node.name, "post") # , excuse_as_v2=True) assert not np.allclose(res.return_result, res_shifted.return_result, atol=1e-8) assert np.isclose(np.linalg.det(res.return_result), np.linalg.det(res_shifted.return_result)) @@ -121,7 +121,7 @@ def test_hess(nh2_data, schema_versions, request): @using("nwchem") def test_gradient(nh2_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = { @@ -131,8 +131,9 @@ def test_gradient(nh2_data, schema_versions, request): "keywords": {"dft__convergence__gradient": "1e-6"}, } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") + # Note this is the dict-in-dict-out case so all model versions are artificial and pass assert compare_values(4.22418267e-2, res["return_result"][2], atol=1e-7) # Beyond accuracy of NWChem stdout @@ -142,7 +143,7 @@ def test_gradient(nh2_data, schema_versions, request): resi["molecule"] = shifted_nh2 resi = checkver_and_convert(resi, request.node.name, "pre") - res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res_shifted = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert not compare_values(4.22418267e-2, res_shifted["return_result"][2], atol=1e-7) @@ -175,7 +176,7 @@ def h20_data(): @using("nwchem") def test_dipole(h20_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h20 = models.Molecule.from_data(h20_data) # Run NH2 @@ -187,8 +188,9 @@ def test_dipole(h20_data, schema_versions, request): } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") + # note dict-in-dict-out # Make sure the calculation completed successfully assert compare_values(-75.764944, res["return_result"], atol=1e-3) @@ -224,7 +226,7 @@ def h20v2_data(): @using("nwchem") def test_homo_lumo(h20v2_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h20v2 = models.Molecule.from_data(h20v2_data) # Run NH2 @@ -236,8 +238,9 @@ def test_homo_lumo(h20v2_data, schema_versions, request): } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True) + res = qcng.compute(resi, "nwchem", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") + # note dict-in-dict-out # Make sure the calculation completed successfully assert compare_values(-75.968095, res["return_result"], atol=1e-3) @@ -263,7 +266,7 @@ def test_homo_lumo(h20v2_data, schema_versions, request): @using("nwchem") def test_geometry_bug(schema_versions, request): """Make sure that the harvester does not crash if NWChem's autosym moves atoms too far""" - models, _ = schema_versions + models, retver, _ = schema_versions # Example molecule that has an RMSD of 2e-4 after NWChem symmetrizes the coordinates xyz = """6 @@ -278,14 +281,14 @@ def test_geometry_bug(schema_versions, request): atin = {"molecule": mol, "model": {"method": "b3lyp", "basis": "6-31g"}, "driver": "gradient"} atin = checkver_and_convert(atin, request.node.name, "pre") - atres = qcng.compute(atin, "nwchem", raise_error=True) - atres = checkver_and_convert(atres, request.node.name, "post") + atres = qcng.compute(atin, "nwchem", raise_error=True, return_version=retver) + atres = checkver_and_convert(atres, request.node.name, "post") # , excuse_as_v2=True) @using("nwchem") def test_autoz_error(schema_versions, request): """Test ability to turn off autoz""" - models, _ = schema_versions + models, retver, _ = schema_versions # Large molecule that leads to an AutoZ error mol = models.Molecule.from_data(_auto_z_problem) @@ -297,8 +300,8 @@ def test_autoz_error(schema_versions, request): } # Turn off error correction resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", raise_error=False) - result = checkver_and_convert(result, request.node.name, "post", vercheck=False) + result = qcng.compute(resi, "nwchem", raise_error=False, return_version=retver) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) # , excuse_as_v2=True) assert not result.success assert "Error when generating redundant atomic coordinates" in result.error.error_message @@ -311,8 +314,8 @@ def test_autoz_error(schema_versions, request): "keywords": {"geometry__noautoz": True}, } resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", raise_error=False) - result = checkver_and_convert(result, request.node.name, "post", vercheck=False) + result = qcng.compute(resi, "nwchem", raise_error=False, return_version=retver) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) # , excuse_as_v2=True) # Ok if it crashes for other reasons assert "Error when generating redundant atomic coordinates" not in result.error.error_message @@ -321,7 +324,7 @@ def test_autoz_error(schema_versions, request): @using("nwchem") def test_autoz_error_correction(schema_versions, request): """See if error correction for autoz works""" - models, _ = schema_versions + models, retver, _ = schema_versions # Large molecule that leads to an AutoZ error mol = models.Molecule.from_data(_auto_z_problem) @@ -333,8 +336,8 @@ def test_autoz_error_correction(schema_versions, request): } resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", raise_error=True) - result = checkver_and_convert(result, request.node.name, "post") + result = qcng.compute(resi, "nwchem", raise_error=True, return_version=retver) + result = checkver_and_convert(result, request.node.name, "post") # , excuse_as_v2=True) assert result.success assert "geom_binvr" in result.extras["observed_errors"] @@ -355,7 +358,7 @@ def test_autoz_error_correction(schema_versions, request): ) @using("nwchem") def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h20v2 = models.Molecule.from_data(h20v2_data) resi = { @@ -370,8 +373,8 @@ def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema } resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", raise_error=True) - result = checkver_and_convert(result, request.node.name, "post") + result = qcng.compute(resi, "nwchem", return_version=retver, raise_error=True) + result = checkver_and_convert(result, request.node.name, "post") # , excuse_as_v2=True) assert result.success assert "convergence_failed" in result.extras["observed_errors"] @@ -382,7 +385,7 @@ def test_conv_threshold(h20v2_data, method, keyword, init_iters, use_tce, schema def test_restart(nh2_data, tmpdir, schema_versions, request): # Create a molecule that takes 5-8 steps for NWChem to relax it, # but only run the relaxation for 4 steps - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = { @@ -398,8 +401,8 @@ def test_restart(nh2_data, tmpdir, schema_versions, request): local_options = {"scratch_messy": True, "scratch_directory": str(tmpdir)} resi = checkver_and_convert(resi, request.node.name, "pre") - result = qcng.compute(resi, "nwchem", task_config=local_options, raise_error=False) - result = checkver_and_convert(result, request.node.name, "post", vercheck=False) + result = qcng.compute(resi, "nwchem", task_config=local_options, raise_error=False, return_version=retver) + result = checkver_and_convert(result, request.node.name, "post", vercheck=False) # , excuse_as_v2=True) assert not result.success assert "computation failed to converge" in str(result.error) @@ -410,6 +413,7 @@ def test_restart(nh2_data, tmpdir, schema_versions, request): "nwchem", task_config=local_options, raise_error=False, + return_version=retver, ) - result = checkver_and_convert(result, request.node.name, "post") + result = checkver_and_convert(result, request.node.name, "post") # , excuse_as_v2=True) assert result.success diff --git a/qcengine/programs/tests/test_programs.py b/qcengine/programs/tests/test_programs.py index 39bff546e..ee5339643 100644 --- a/qcengine/programs/tests/test_programs.py +++ b/qcengine/programs/tests/test_programs.py @@ -12,8 +12,9 @@ def test_missing_key(schema_versions, request): + _, retver, _ = schema_versions - ret = qcng.compute({"hello": "hi"}, "bleh") + ret = qcng.compute({"hello": "hi"}, "bleh", return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.success is False @@ -27,7 +28,7 @@ def test_missing_key_raises(schema_versions, request): @using("psi4") def test_psi4_task(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -37,7 +38,7 @@ def test_psi4_task(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = qcng.compute(input_data, "psi4", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.driver == "energy" @@ -53,7 +54,7 @@ def test_psi4_task(schema_versions, request): @using("psi4") @using("gcp") def test_psi4_hf3c_task(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), "driver": "energy", @@ -62,7 +63,7 @@ def test_psi4_hf3c_task(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = qcng.compute(input_data, "psi4", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -71,7 +72,7 @@ def test_psi4_hf3c_task(schema_versions, request): @using("psi4_runqcsk") def test_psi4_interactive_task(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -82,7 +83,7 @@ def test_psi4_interactive_task(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = qcng.compute(input_data, "psi4", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert "Final Energy" in ret.stdout @@ -93,7 +94,7 @@ def test_psi4_interactive_task(schema_versions, request): @using("psi4_runqcsk") def test_psi4_wavefunction_task(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -104,7 +105,7 @@ def test_psi4_wavefunction_task(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "psi4", raise_error=True) + ret = qcng.compute(input_data, "psi4", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success, ret.error.error_message @@ -113,7 +114,7 @@ def test_psi4_wavefunction_task(schema_versions, request): @using("psi4") def test_psi4_internal_failure(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions mol = models.Molecule.from_data( """0 3 @@ -129,14 +130,14 @@ def test_psi4_internal_failure(schema_versions, request): } with pytest.raises(qcng.exceptions.InputError) as exc: psi4_task = checkver_and_convert(psi4_task, request.node.name, "pre") - ret = qcng.compute(psi4_task, "psi4", raise_error=True) + ret = qcng.compute(psi4_task, "psi4", raise_error=True, return_version=retver) assert "reference is only" in str(exc.value) @using("psi4") def test_psi4_ref_switch(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions inp = models.AtomicInput( **{ @@ -148,7 +149,7 @@ def test_psi4_ref_switch(schema_versions, request): ) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, "psi4", raise_error=True, return_dict=False) + ret = qcng.compute(inp, "psi4", raise_error=True, return_dict=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -159,7 +160,7 @@ def test_psi4_ref_switch(schema_versions, request): @using("rdkit") @pytest.mark.parametrize("method", ["UFF", "MMFF94", "MMFF94s"]) def test_rdkit_task(method, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -169,7 +170,7 @@ def test_rdkit_task(method, schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "rdkit", raise_error=True) + ret = qcng.compute(input_data, "rdkit", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -177,7 +178,7 @@ def test_rdkit_task(method, schema_versions, request): @using("rdkit") def test_rdkit_connectivity_error(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": qcng.get_molecule("water", return_dict=True), @@ -188,19 +189,19 @@ def test_rdkit_connectivity_error(schema_versions, request): del input_data["molecule"]["connectivity"] input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "rdkit") + ret = qcng.compute(input_data, "rdkit", return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.success is False assert "connectivity" in ret.error.error_message with pytest.raises(qcng.exceptions.InputError): - qcng.compute(input_data, "rdkit", raise_error=True) + qcng.compute(input_data, "rdkit", raise_error=True, return_version=retver) @using("torchani") def test_torchani_task(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data = { "molecule": models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -210,7 +211,7 @@ def test_torchani_task(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "torchani", raise_error=True) + ret = qcng.compute(input_data, "torchani", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -219,6 +220,8 @@ def test_torchani_task(schema_versions, request): @using("mopac") def test_mopac_task(schema_versions, request): + _, retver, _ = schema_versions + input_data = { "molecule": qcng.get_molecule("water", return_dict=True), "driver": "gradient", @@ -227,7 +230,7 @@ def test_mopac_task(schema_versions, request): } input_data1 = checkver_and_convert(input_data.copy(), request.node.name, "pre") - ret = qcng.compute(input_data1, "mopac", raise_error=True) + ret = qcng.compute(input_data1, "mopac", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.extras.keys() >= {"heat_of_formation", "dip_vec"} @@ -235,7 +238,7 @@ def test_mopac_task(schema_versions, request): # Check gradient input_data2 = checkver_and_convert(input_data.copy(), request.node.name, "pre") - ret = qcng.compute(input_data2, "mopac", raise_error=True) + ret = qcng.compute(input_data2, "mopac", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.extras.keys() >= {"heat_of_formation", "dip_vec"} @@ -245,7 +248,7 @@ def test_mopac_task(schema_versions, request): # Check energy input_data3 = input_data.copy() input_data3["driver"] = "energy" - ret = qcng.compute(input_data3, "mopac", raise_error=True) + ret = qcng.compute(input_data3, "mopac", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.return_result == energy @@ -253,15 +256,17 @@ def test_mopac_task(schema_versions, request): def test_random_failure_no_retries(failure_engine, schema_versions, request): + _, retver, _ = schema_versions + failure_engine.iter_modes = ["input_error"] - ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False) + ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.error.error_type == "input_error" assert "retries" not in ret.input_data["provenance"].keys() failure_engine.iter_modes = ["random_error"] - ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False) + ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.error.error_type == "random_error" @@ -269,15 +274,29 @@ def test_random_failure_no_retries(failure_engine, schema_versions, request): def test_random_failure_with_retries(failure_engine, schema_versions, request): + _, retver, _ = schema_versions + failure_engine.iter_modes = ["random_error", "random_error", "random_error"] - ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 2}) + ret = qcng.compute( + failure_engine.get_job(), + failure_engine.name, + raise_error=False, + task_config={"retries": 2}, + return_version=retver, + ) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.input_data["provenance"]["retries"] == 2 assert ret.error.error_type == "random_error" failure_engine.iter_modes = ["random_error", "input_error"] - ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 4}) + ret = qcng.compute( + failure_engine.get_job(), + failure_engine.name, + raise_error=False, + task_config={"retries": 4}, + return_version=retver, + ) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.input_data["provenance"]["retries"] == 1 @@ -285,9 +304,17 @@ def test_random_failure_with_retries(failure_engine, schema_versions, request): def test_random_failure_with_success(failure_engine, schema_versions, request): + _, retver, _ = schema_versions + failure_engine.iter_modes = ["random_error", "pass"] failure_engine.ncalls = 0 - ret = qcng.compute(failure_engine.get_job(), failure_engine.name, raise_error=False, task_config={"retries": 1}) + ret = qcng.compute( + failure_engine.get_job(), + failure_engine.name, + raise_error=False, + task_config={"retries": 1}, + return_version=retver, + ) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success, ret.error.error_message @@ -297,7 +324,7 @@ def test_random_failure_with_success(failure_engine, schema_versions, request): @using("openmm") def test_openmm_task_smirnoff(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions from qcengine.programs.openmm import OpenMMHarness @@ -309,7 +336,7 @@ def test_openmm_task_smirnoff(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = qcng.compute(input_data, "openmm", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") cachelength = len(OpenMMHarness._CACHE) @@ -317,7 +344,7 @@ def test_openmm_task_smirnoff(schema_versions, request): assert cachelength > 0 assert ret.success is True - ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = qcng.compute(input_data, "openmm", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") # ensure cache has not grown @@ -328,7 +355,7 @@ def test_openmm_task_smirnoff(schema_versions, request): @pytest.mark.skip("`basis` must be explicitly specified at this time") @using("openmm") def test_openmm_task_url_basis(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions from qcengine.programs.openmm import OpenMMHarness @@ -344,7 +371,7 @@ def test_openmm_task_url_basis(schema_versions, request): } input_data = checkver_and_convert(input_data, request.node.name, "pre") - ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = qcng.compute(input_data, "openmm", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") cachelength = len(OpenMMHarness._CACHE) @@ -352,7 +379,7 @@ def test_openmm_task_url_basis(schema_versions, request): assert cachelength > 0 assert ret.success is True - ret = qcng.compute(input_data, "openmm", raise_error=True) + ret = qcng.compute(input_data, "openmm", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") # ensure cache has not grown @@ -362,7 +389,7 @@ def test_openmm_task_url_basis(schema_versions, request): @using("openmm") def test_openmm_cmiles_gradient(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions program = "openmm" @@ -379,7 +406,7 @@ def test_openmm_cmiles_gradient(schema_versions, request): inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=False) + ret = qcng.compute(inp, program, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -387,7 +414,7 @@ def test_openmm_cmiles_gradient(schema_versions, request): @using("openmm") def test_openmm_cmiles_gradient_nomatch(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions program = "openmm" @@ -407,7 +434,7 @@ def test_openmm_cmiles_gradient_nomatch(schema_versions, request): inp = models.AtomicInput(molecule=molecule, driver="gradient", model=model) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=False) + ret = qcng.compute(inp, program, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) # if we correctly find the cmiles this should fail as the molecule and cmiles are different @@ -437,7 +464,7 @@ def test_openmm_gaff_keywords(gaff_settings, schema_versions, request): """ Test the different running settings with gaff. """ - models, _ = schema_versions + models, retver, _ = schema_versions program = "openmm" water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) @@ -453,10 +480,10 @@ def test_openmm_gaff_keywords(gaff_settings, schema_versions, request): if error is not None: with pytest.raises(error): inp = checkver_and_convert(inp, request.node.name, "pre") - _ = qcng.compute(inp, program, raise_error=True) + _ = qcng.compute(inp, program, raise_error=True, return_version=retver) else: inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=False) + ret = qcng.compute(inp, program, raise_error=False, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -468,12 +495,12 @@ def test_mace_energy(schema_versions, request): """ Test calculating the energy with mace """ - models, _ = schema_versions + models, retver, _ = schema_versions water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) atomic_input = models.AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="energy") atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - result = qcng.compute(atomic_input, "mace") + result = qcng.compute(atomic_input, "mace", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success @@ -485,7 +512,7 @@ def test_mace_gradient(schema_versions, request): """ Test calculating the gradient with mace """ - models, _ = schema_versions + models, retver, _ = schema_versions water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) expected_result = np.array( @@ -499,7 +526,7 @@ def test_mace_gradient(schema_versions, request): atomic_input = models.AtomicInput(molecule=water, model={"method": "small", "basis": None}, driver="gradient") atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - result = qcng.compute(atomic_input, "mace") + result = qcng.compute(atomic_input, "mace", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success @@ -516,13 +543,13 @@ def test_mace_gradient(schema_versions, request): ) def test_aimnet2_energy(model, expected_energy, schema_versions, request): """Test computing the energies of water with two aimnet2 models.""" - models, _ = schema_versions + models, retver, _ = schema_versions water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) atomic_input = models.AtomicInput(molecule=water, model={"method": model, "basis": None}, driver="energy") atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - result = qcng.compute(atomic_input, "aimnet2") + result = qcng.compute(atomic_input, "aimnet2", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success @@ -535,13 +562,13 @@ def test_aimnet2_energy(model, expected_energy, schema_versions, request): @using("aimnet2") def test_aimnet2_gradient(schema_versions, request): """Test computing the gradient of water using one aimnet2 model.""" - models, _ = schema_versions + models, retver, _ = schema_versions water = models.Molecule(**qcng.get_molecule("water", return_dict=True)) atomic_input = models.AtomicInput(molecule=water, model={"method": "wb97m-d3", "basis": None}, driver="gradient") atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - result = qcng.compute(atomic_input, "aimnet2") + result = qcng.compute(atomic_input, "aimnet2", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success @@ -563,7 +590,7 @@ def test_aimnet2_gradient(schema_versions, request): @using("psi4") def test_psi4_properties_driver(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions import numpy as np @@ -652,7 +679,7 @@ def test_psi4_properties_driver(schema_versions, request): } json_data = checkver_and_convert(json_data, request.node.name, "pre") - json_ret = qcng.compute(json_data, "psi4") + json_ret = qcng.compute(json_data, "psi4", return_version=retver) json_ret = checkver_and_convert(json_ret, request.node.name, "post") assert json_ret.success diff --git a/qcengine/programs/tests/test_qchem.py b/qcengine/programs/tests/test_qchem.py index 7ff555548..a7d1a0661 100644 --- a/qcengine/programs/tests/test_qchem.py +++ b/qcengine/programs/tests/test_qchem.py @@ -67,7 +67,7 @@ def test_qchem_input_formatter_template(test_case): @using("qchem") @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) def test_qchem_executor(test_case, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # Get input file data data = qchem_info.get_test_data(test_case) @@ -75,7 +75,7 @@ def test_qchem_executor(test_case, schema_versions, request): # Run qchem inp = checkver_and_convert(inp, request.node.name, "pre") - result = qcng.compute(inp, "qchem") + result = qcng.compute(inp, "qchem", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") assert result.success is True @@ -89,7 +89,7 @@ def test_qchem_executor(test_case, schema_versions, request): @using("qchem") def test_qchem_orientation(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions mol = models.Molecule.from_data( """ @@ -102,7 +102,7 @@ def test_qchem_orientation(schema_versions, request): inp = {"molecule": mol, "driver": "gradient", "model": {"method": "HF", "basis": "6-31g"}} inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, "qchem", raise_error=True) + ret = qcng.compute(inp, "qchem", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert compare_values(np.linalg.norm(ret.return_result, axis=0), [0, 0, 0.00791539]) @@ -112,7 +112,7 @@ def test_qchem_orientation(schema_versions, request): inp = {"molecule": mol_noorient, "driver": "gradient", "model": {"method": "HF", "basis": "6-31g"}} inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, "qchem", raise_error=True) + ret = qcng.compute(inp, "qchem", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert compare_values(np.linalg.norm(ret.return_result, axis=0), [0, 0.00559696541, 0.00559696541]) diff --git a/qcengine/programs/tests/test_qcore.py b/qcengine/programs/tests/test_qcore.py index ca891a207..e3e40220d 100644 --- a/qcengine/programs/tests/test_qcore.py +++ b/qcengine/programs/tests/test_qcore.py @@ -17,7 +17,7 @@ ], ) def test_qcore_methods(method, energy, gradient_norm, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -26,7 +26,7 @@ def test_qcore_methods(method, energy, gradient_norm, schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "qcore") + atomic_result = qcng.compute(atomic_input, "qcore", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success, atomic_result.error.error_message @@ -37,7 +37,7 @@ def test_qcore_methods(method, energy, gradient_norm, schema_versions, request): @using("qcore") def test_qcore_wavefunction(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -47,9 +47,9 @@ def test_qcore_wavefunction(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "qcore") + atomic_result = qcng.compute(atomic_input, "qcore", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") - + assert atomic_result.success, atomic_result.error.error_message assert atomic_result.wavefunction is not None assert atomic_result.wavefunction.scf_orbitals_a is not None diff --git a/qcengine/programs/tests/test_sdftd3.py b/qcengine/programs/tests/test_sdftd3.py index 24477355e..e27c62e01 100644 --- a/qcengine/programs/tests/test_sdftd3.py +++ b/qcengine/programs/tests/test_sdftd3.py @@ -15,7 +15,7 @@ @using("s-dftd3") def test_dftd3_task_b97m_m01(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -28,7 +28,7 @@ def test_dftd3_task_b97m_m01(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") print(atomic_result.return_result) @@ -49,7 +49,7 @@ def test_dftd3_task_b97m_m01(schema_versions, request): ids=["d3bj", "d3zero", "d3mbj", "d3mzero", "d3op"], ) def test_dftd3_task_pbe_m02(inp, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # return to 1.0e-8 after https://github.com/MolSSI/QCEngine/issues/370 thr = 1.0e-7 @@ -64,7 +64,7 @@ def test_dftd3_task_pbe_m02(inp, schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -73,7 +73,7 @@ def test_dftd3_task_pbe_m02(inp, schema_versions, request): @using("s-dftd3") def test_dftd3_task_tpss_m02(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -113,7 +113,7 @@ def test_dftd3_task_tpss_m02(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -122,7 +122,7 @@ def test_dftd3_task_tpss_m02(schema_versions, request): @using("s-dftd3") def test_dftd3_task_r2scan_m03(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -155,7 +155,7 @@ def test_dftd3_task_r2scan_m03(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -165,7 +165,7 @@ def test_dftd3_task_r2scan_m03(schema_versions, request): @using("s-dftd3") def test_dftd3_task_unknown_method(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -178,8 +178,10 @@ def test_dftd3_task_unknown_method(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") - atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False, cast_dict_as="FailedOperation") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) + atomic_result = checkver_and_convert( + atomic_result, request.node.name, "post", vercheck=False, cast_dict_as="FailedOperation" + ) print(atomic_result.error) assert not atomic_result.success @@ -188,7 +190,7 @@ def test_dftd3_task_unknown_method(schema_versions, request): @using("s-dftd3") def test_dftd3_task_cold_fusion(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule={ @@ -211,7 +213,7 @@ def test_dftd3_task_cold_fusion(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "s-dftd3") + atomic_result = qcng.compute(atomic_input, "s-dftd3", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) print(atomic_result.error) diff --git a/qcengine/programs/tests/test_standard_suite.py b/qcengine/programs/tests/test_standard_suite.py index 5280f6179..7e6e039ac 100644 --- a/qcengine/programs/tests/test_standard_suite.py +++ b/qcengine/programs/tests/test_standard_suite.py @@ -47,7 +47,7 @@ @pytest.fixture def clsd_open_pmols(schema_versions): - models, _ = schema_versions + models, retver, _ = schema_versions return { name[:-4]: models.Molecule.from_data(smol, name=name[:-4]) for name, smol in std_molecules.items() @@ -160,8 +160,9 @@ def _trans_key(qc, bas, key): ], ) def test_hf_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( - *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "hf") + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, "energy", "hf") ) @@ -214,8 +215,9 @@ def test_hf_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reques ], ) def test_hf_gradient_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( - *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "gradient", "hf") + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, "gradient", "hf") ) @@ -267,8 +269,9 @@ def test_hf_gradient_module(inp, dertype, basis, subjects, clsd_open_pmols, requ ], ) def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( - *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "hessian", "hf") + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, "hessian", "hf") ) @@ -346,8 +349,9 @@ def test_hf_hessian_module(inp, dertype, basis, subjects, clsd_open_pmols, reque ], ) def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( - *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "mp2") + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, "energy", "mp2") ) @@ -434,8 +438,9 @@ def test_mp2_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, reque ], ) def test_ccsd_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions): + models, retver, _ = schema_versions runner_asserter( - *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, schema_versions[0], "energy", "ccsd") + *_processor(inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, "energy", "ccsd") ) @@ -486,7 +491,7 @@ def test_ccsd_energy_module(inp, dertype, basis, subjects, clsd_open_pmols, requ def _processor( - inp, dertype, basis, subjects, clsd_open_pmols, request, models, driver, method, *, scramble=None, frame="" + inp, dertype, basis, subjects, clsd_open_pmols, request, models, retver, driver, method, *, scramble=None, frame="" ): method = method qcprog = inp["call"] @@ -519,4 +524,4 @@ def _processor( ] ).strip("-") - return inpcopy, subject, method, basis, tnm, scramble, frame, models + return inpcopy, subject, method, basis, tnm, scramble, frame, models, retver diff --git a/qcengine/programs/tests/test_standard_suite_ccsd(t).py b/qcengine/programs/tests/test_standard_suite_ccsd(t).py index bc2a12686..67b44f02f 100644 --- a/qcengine/programs/tests/test_standard_suite_ccsd(t).py +++ b/qcengine/programs/tests/test_standard_suite_ccsd(t).py @@ -45,13 +45,13 @@ def test_sp_ccsd_t_rhf_full(program, basis, keywords, h2o_data, schema_versions, #! single point CCSD(T)/adz on water """ - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = {"molecule": h2o, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" @@ -85,14 +85,14 @@ def test_sp_ccsd_t_rhf_full(program, basis, keywords, h2o_data, schema_versions, ], ) def test_sp_ccsd_t_uhf_fc_error(program, basis, keywords, nh2_data, errmsg, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} with pytest.raises(qcng.exceptions.InputError) as e: resi = checkver_and_convert(resi, request.node.name, "pre") - qcng.compute(resi, program, raise_error=True, return_dict=True) + qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) assert errmsg in str(e.value) @@ -112,14 +112,15 @@ def test_sp_ccsd_t_uhf_fc_error(program, basis, keywords, nh2_data, errmsg, sche ], ) def test_sp_ccsd_t_rohf_full(program, basis, keywords, nh2_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "ccsd(t)", "basis": basis}, "keywords": keywords} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") + res = res.model_dump() assert res["driver"] == "energy" assert "provenance" in res diff --git a/qcengine/programs/tests/test_standard_suite_hf.py b/qcengine/programs/tests/test_standard_suite_hf.py index 6dfacfee8..a8506d3c2 100644 --- a/qcengine/programs/tests/test_standard_suite_hf.py +++ b/qcengine/programs/tests/test_standard_suite_hf.py @@ -56,13 +56,13 @@ def test_sp_hf_rhf(program, basis, keywords, h2o_data, schema_versions, request) #! single point HF/adz on water """ - models, models_out = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = {"molecule": h2o, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" @@ -107,15 +107,18 @@ def test_sp_hf_rhf(program, basis, keywords, h2o_data, schema_versions, request) ], ) def test_sp_hf_uhf(program, basis, keywords, nh2_data, schema_versions, request): - models, models_out = schema_versions + models, retver, _ = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} + resi = models.AtomicInput(**resi) resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_dict=False, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") + assert res.success is True + res = res.model_dump() assert res["driver"] == "energy" assert "provenance" in res assert res["success"] is True @@ -151,13 +154,13 @@ def test_sp_hf_uhf(program, basis, keywords, nh2_data, schema_versions, request) ], ) def test_sp_hf_rohf(program, basis, keywords, nh2_data, schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions nh2 = models.Molecule.from_data(nh2_data) resi = {"molecule": nh2, "driver": "energy", "model": {"method": "hf", "basis": basis}, "keywords": keywords} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, program, raise_error=True, return_dict=True) + res = qcng.compute(resi, program, raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" diff --git a/qcengine/programs/tests/test_terachem.py b/qcengine/programs/tests/test_terachem.py index 724fe68e4..a778744c7 100644 --- a/qcengine/programs/tests/test_terachem.py +++ b/qcengine/programs/tests/test_terachem.py @@ -36,7 +36,7 @@ def test_terachem_input_formatter(test_case): @using("terachem") @pytest.mark.parametrize("test_case", terachem_info.list_test_cases()) def test_terachem_executor(test_case, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # Get input file data data = terachem_info.get_test_data(test_case) @@ -44,7 +44,7 @@ def test_terachem_executor(test_case, schema_versions, request): # Run Terachem inp = checkver_and_convert(inp, request.node.name, "pre") - result = qcng.compute(inp, "terachem") + result = qcng.compute(inp, "terachem", return_version=retver) result = checkver_and_convert(result, request.node.name, "post") # result = qcng.get_program('terachem').compute(inp, qcng.get_config()) diff --git a/qcengine/programs/tests/test_turbomole.py b/qcengine/programs/tests/test_turbomole.py index fff4bf87d..40e89b49e 100644 --- a/qcengine/programs/tests/test_turbomole.py +++ b/qcengine/programs/tests/test_turbomole.py @@ -46,13 +46,13 @@ def h2o_ricc2_def2svp_data(): ], ) def test_turbomole_energy(method, keywords, ref_energy, h2o_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = {"molecule": h2o, "driver": "energy", "model": {"method": method, "basis": "def2-SVP"}, "keywords": keywords} resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "turbomole", raise_error=True, return_dict=True) + res = qcng.compute(resi, "turbomole", raise_error=True, return_dict=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res["driver"] == "energy" @@ -71,7 +71,7 @@ def test_turbomole_energy(method, keywords, ref_energy, h2o_data, schema_version ], ) def test_turbomole_gradient(method, keywords, ref_norm, h2o_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = { @@ -82,7 +82,7 @@ def test_turbomole_gradient(method, keywords, ref_norm, h2o_data, schema_version } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "turbomole", raise_error=True) + res = qcng.compute(resi, "turbomole", raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res.driver == "gradient" @@ -96,7 +96,7 @@ def test_turbomole_gradient(method, keywords, ref_norm, h2o_data, schema_version @using("turbomole") def test_turbomole_ri_dsp(h2o_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = { @@ -107,7 +107,7 @@ def test_turbomole_ri_dsp(h2o_data, schema_versions, request): } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "turbomole", raise_error=True) + res = qcng.compute(resi, "turbomole", raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") assert res.driver == "energy" @@ -139,7 +139,7 @@ def assert_hessian(H, ref_eigvals, ref_size): ], ) def test_turbomole_hessian(method, keywords, ref_eigvals, h2o_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o = models.Molecule.from_data(h2o_data) resi = { @@ -153,7 +153,7 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o_data, schema_versi } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "turbomole", raise_error=True) + res = qcng.compute(resi, "turbomole", raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") H = res.return_result @@ -173,7 +173,7 @@ def test_turbomole_hessian(method, keywords, ref_eigvals, h2o_data, schema_versi ], ) def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions h2o_ricc2_def2svp = models.Molecule.from_data(h2o_ricc2_def2svp_data) resi = { @@ -187,7 +187,7 @@ def test_turbomole_num_hessian(method, keywords, ref_eigvals, h2o_ricc2_def2svp_ } resi = checkver_and_convert(resi, request.node.name, "pre") - res = qcng.compute(resi, "turbomole", raise_error=True) + res = qcng.compute(resi, "turbomole", raise_error=True, return_version=retver) res = checkver_and_convert(res, request.node.name, "post") H = res.return_result diff --git a/qcengine/programs/tests/test_xtb.py b/qcengine/programs/tests/test_xtb.py index 98bb07ee7..d1eccfcd7 100644 --- a/qcengine/programs/tests/test_xtb.py +++ b/qcengine/programs/tests/test_xtb.py @@ -16,7 +16,7 @@ @using("xtb") def test_xtb_task_gfn1xtb_m01(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-7 return_result = np.array( @@ -47,7 +47,7 @@ def test_xtb_task_gfn1xtb_m01(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -56,7 +56,7 @@ def test_xtb_task_gfn1xtb_m01(schema_versions, request): @using("xtb") def test_xtb_task_gfn1xtb_m02(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -92,7 +92,7 @@ def test_xtb_task_gfn1xtb_m02(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -101,7 +101,7 @@ def test_xtb_task_gfn1xtb_m02(schema_versions, request): @using("xtb") def test_xtb_task_gfn1xtb_m03(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -136,7 +136,7 @@ def test_xtb_task_gfn1xtb_m03(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -145,7 +145,7 @@ def test_xtb_task_gfn1xtb_m03(schema_versions, request): @using("xtb") def test_xtb_task_gfn1xtb_m04(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-6 @@ -180,7 +180,7 @@ def test_xtb_task_gfn1xtb_m04(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -190,8 +190,8 @@ def test_xtb_task_gfn1xtb_m04(schema_versions, request): @using("xtb") -def test_xtb_task_gfn1xtb_m05(schema_versions, request): - models, _ = schema_versions +def test_xtb_task_gfn1xtb_m05(schema_versions, request): + models, retver, _ = schema_versions thr = 1.0e-8 @@ -204,7 +204,7 @@ def test_xtb_task_gfn1xtb_m05(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -214,7 +214,7 @@ def test_xtb_task_gfn1xtb_m05(schema_versions, request): @using("xtb") def test_xtb_task_gfn2xtb_m01(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-7 @@ -246,7 +246,7 @@ def test_xtb_task_gfn2xtb_m01(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -255,7 +255,7 @@ def test_xtb_task_gfn2xtb_m01(schema_versions, request): @using("xtb") def test_xtb_task_gfn2xtb_m02(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -291,7 +291,7 @@ def test_xtb_task_gfn2xtb_m02(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -299,8 +299,8 @@ def test_xtb_task_gfn2xtb_m02(schema_versions, request): @using("xtb") -def test_xtb_task_gfn2xtb_m03(schema_versions, request): - models, _ = schema_versions +def test_xtb_task_gfn2xtb_m03(schema_versions, request): + models, retver, _ = schema_versions thr = 1.0e-8 @@ -326,7 +326,7 @@ def test_xtb_task_gfn2xtb_m03(schema_versions, request): ) atomic_input = models.AtomicInput( - molecule=models.Molecule(**qcng.get_molecule("mindless-03",return_dict=True)), + molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)), model={"method": "GFN2-xTB"}, driver="gradient", keywords={ @@ -335,7 +335,7 @@ def test_xtb_task_gfn2xtb_m03(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -344,7 +344,7 @@ def test_xtb_task_gfn2xtb_m03(schema_versions, request): @using("xtb") def test_xtb_task_gfn2xtb_m04(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-6 @@ -379,7 +379,7 @@ def test_xtb_task_gfn2xtb_m04(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -390,7 +390,7 @@ def test_xtb_task_gfn2xtb_m04(schema_versions, request): @using("xtb") def test_xtb_task_gfn2xtb_m05(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions thr = 1.0e-8 @@ -403,7 +403,7 @@ def test_xtb_task_gfn2xtb_m05(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post") assert atomic_result.success @@ -413,7 +413,7 @@ def test_xtb_task_gfn2xtb_m05(schema_versions, request): @using("xtb") def test_xtb_task_unknown_method(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -423,7 +423,7 @@ def test_xtb_task_unknown_method(schema_versions, request): error = models_out.ComputeError(error_type="input_error", error_message="Invalid method GFN-xTB provided in model") atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success @@ -432,7 +432,7 @@ def test_xtb_task_unknown_method(schema_versions, request): @using("xtb") def test_xtb_task_unsupported_driver(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule=models.Molecule(**qcng.get_molecule("water", return_dict=True)), @@ -444,7 +444,7 @@ def test_xtb_task_unsupported_driver(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success @@ -453,7 +453,7 @@ def test_xtb_task_unsupported_driver(schema_versions, request): @using("xtb") def test_xtb_task_cold_fusion(schema_versions, request): - models, models_out = schema_versions + models, retver, models_out = schema_versions atomic_input = models.AtomicInput( molecule={ @@ -475,7 +475,7 @@ def test_xtb_task_cold_fusion(schema_versions, request): ) atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre") - atomic_result = qcng.compute(atomic_input, "xtb") + atomic_result = qcng.compute(atomic_input, "xtb", return_version=retver) atomic_result = checkver_and_convert(atomic_result, request.node.name, "post", vercheck=False) assert not atomic_result.success diff --git a/qcengine/testing.py b/qcengine/testing.py index 2bbd8340e..ccc09b608 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -116,7 +116,7 @@ def compute(self, input_data: "AtomicInput", config: "TaskConfig") -> "AtomicRes grad = [0, 0, -grad_value, 0, 0, grad_value] if mode == "pass": - # TODO return schema_versions[0].AtomicResult( + # TODO return v2 schema_versions[2].AtomicResult( return qcel.models.v1.AtomicResult( **{ **input_data.dict(), @@ -217,15 +217,20 @@ def using(program): @pytest.fixture(scope="function", params=[None, "as_v1", "as_v2", "to_v1", "to_v2"]) def schema_versions(request): if request.param == "as_v1": - return qcel.models.v1, qcel.models.v1 + return qcel.models.v1, -1, qcel.models.v1 elif request.param == "to_v2": - return qcel.models.v1, qcel.models.v2 + return qcel.models.v1, 2, qcel.models.v2 elif request.param == "as_v2": - return qcel.models.v2, qcel.models.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. elif request.param == "to_v1": - return qcel.models.v2, qcel.models.v1 + return qcel.models.v2, 1, qcel.models.v1 else: - return qcel.models, qcel.models + return qcel.models, -1, qcel.models def checkver_and_convert(mdl, tnm, prepost, vercheck: bool = True, cast_dict_as=None): @@ -234,7 +239,7 @@ def checkver_and_convert(mdl, tnm, prepost, vercheck: bool = True, cast_dict_as= import pydantic def check_model_v1(m): - assert isinstance(m, pydantic.v1.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ v1.BaseModel" + assert isinstance(m, pydantic.v1.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ v1.BaseModel (Pyd v1)" assert isinstance( m, qcel.models.v1.basemodels.ProtoModel ), f"type({m.__class__.__name__}) = {type(m)} ⊄ v1.ProtoModel" @@ -242,7 +247,7 @@ def check_model_v1(m): assert m.schema_version == 1, f"{m.__class__.__name__}.schema_version = {m.schema_version} != 1" def check_model_v2(m): - assert isinstance(m, pydantic.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ BaseModel" + assert isinstance(m, pydantic.BaseModel), f"type({m.__class__.__name__}) = {type(m)} ⊄ BaseModel (Pyd v2)" assert isinstance( m, qcel.models.v2.basemodels.ProtoModel ), f"type({m.__class__.__name__}) = {type(m)} ⊄ v2.ProtoModel" @@ -265,12 +270,15 @@ def check_model_v2(m): else: mdl = qcel.models.v2.AtomicInput(**mdl) check_model_v2(mdl) - mdl = mdl.convert_v(1) + # NOW IN COMPUTE mdl = mdl.convert_v(1) if dict_in: mdl = mdl.model_dump() elif prepost == "post": + # excuse_as_v2 is only for "post" when the compute input was a dict and while dicts are not discriminable. + # for now these always go to v1 in programs/model.py so as_v2 returns wrongly as v1 + # follow-up: there are too many ways this can happen, so now it's forestalled by the schema_versions fixture passing 2 to as_v2 dict_in = isinstance(mdl, dict) if "as_v1" in tnm or "to_v1" in tnm or "None" in tnm: if dict_in: @@ -285,7 +293,7 @@ def check_model_v2(m): mdl = getattr(qcel.models.v2, cast_dict_as)(**mdl) else: mdl = qcel.models.v2.AtomicResult(**mdl) - mdl = mdl.convert_v(2) + # NOW IN COMPUTE mdl = mdl.convert_v(2) check_model_v2(mdl) if dict_in: diff --git a/qcengine/tests/test_harness_canonical.py b/qcengine/tests/test_harness_canonical.py index 8f018efad..5064f4d6d 100644 --- a/qcengine/tests/test_harness_canonical.py +++ b/qcengine/tests/test_harness_canonical.py @@ -61,9 +61,10 @@ def _get_molecule(program, molcls): dmol = qcng.get_molecule("hydrogen", return_dict=True) return molcls(**dmol) + @pytest.mark.parametrize("program, model, keywords", _canonical_methods) def test_compute_energy(program, model, keywords, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip(f"Program '{program}' not found.") @@ -73,7 +74,7 @@ def test_compute_energy(program, model, keywords, schema_versions, request): inp = models.AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True) + ret = qcng.compute(inp, program, raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -82,7 +83,7 @@ def test_compute_energy(program, model, keywords, schema_versions, request): @pytest.mark.parametrize("program, model, keywords", _canonical_methods) def test_compute_gradient(program, model, keywords, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) @@ -95,13 +96,13 @@ def test_compute_gradient(program, model, keywords, schema_versions, request): if program in ["adcc"]: inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.InputError) as e: - qcng.compute(inp, program, raise_error=True) + qcng.compute(inp, program, raise_error=True, return_version=retver) assert "gradient not implemented" in str(e.value) else: inp = checkver_and_convert(inp, request.node.name, "pre") - ret = qcng.compute(inp, program, raise_error=True) + ret = qcng.compute(inp, program, raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -113,7 +114,7 @@ def test_compute_gradient(program, model, keywords, schema_versions, request): @pytest.mark.parametrize("program, model, keywords", _canonical_methods_qcsk_basis) def test_compute_energy_qcsk_basis(program, model, keywords, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) @@ -123,7 +124,7 @@ def test_compute_energy_qcsk_basis(program, model, keywords, schema_versions, re with pytest.raises(qcng.exceptions.InputError) as e: inp = checkver_and_convert(inp, request.node.name, "pre") - res = qcng.compute(inp, program, raise_error=True) + res = qcng.compute(inp, program, raise_error=True, return_version=retver) checkver_and_convert(res, request.node.name, "post") assert "QCSchema BasisSet for model.basis not implemented" in str(e.value) @@ -159,31 +160,37 @@ def test_compute_energy_qcsk_basis(program, model, keywords, schema_versions, re ], ) def test_compute_bad_models(program, model, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program(program): pytest.skip("Program '{}' not found.".format(program)) amodel = copy.deepcopy(model) adriver = amodel.pop("driver", "energy") - inp = models.AtomicInput(molecule=models.Molecule(**qcng.get_molecule("hydrogen",return_dict=True)), driver=adriver, model=amodel) + inp = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), driver=adriver, model=amodel + ) inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.InputError) as exc: - ret = qcng.compute(inp, program, raise_error=True) + ret = qcng.compute(inp, program, raise_error=True, return_version=retver) def test_psi4_restarts(monkeypatch, schema_versions, request): """ Make sure that a random error is raised which can be restarted if psi4 fails with no error message """ - models, _ = schema_versions + models, retver, _ = schema_versions if not has_program("psi4"): pytest.skip("Program psi4 not found.") # create the psi4 task - inp = models.AtomicInput(molecule=models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), driver="energy", model={"method": "hf", "basis": "6-31G"}) + inp = models.AtomicInput( + molecule=models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)), + driver="energy", + model={"method": "hf", "basis": "6-31G"}, + ) def mock_execute(*args, **kwargs): """ @@ -197,4 +204,6 @@ def mock_execute(*args, **kwargs): inp = checkver_and_convert(inp, request.node.name, "pre") with pytest.raises(qcng.exceptions.RandomError): - _ = qcng.compute(input_data=inp, program="psi4", raise_error=True, task_config={"retries": 0}) + _ = qcng.compute( + input_data=inp, program="psi4", raise_error=True, task_config={"retries": 0}, return_version=retver + ) diff --git a/qcengine/tests/test_procedures.py b/qcengine/tests/test_procedures.py index 47ee8949c..6e3f56b0c 100644 --- a/qcengine/tests/test_procedures.py +++ b/qcengine/tests/test_procedures.py @@ -29,7 +29,7 @@ def input_data(): ], ) def test_geometric_psi4(input_data, optimizer, ncores, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} @@ -43,7 +43,9 @@ 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) + 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 @@ -67,7 +69,7 @@ def test_geometric_psi4(input_data, optimizer, ncores, schema_versions, request) @using("psi4") @using("geometric") def test_geometric_local_options(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} @@ -77,7 +79,9 @@ 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"}) + ret = qcng.compute_procedure( + 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 @@ -90,7 +94,7 @@ def test_geometric_local_options(input_data, schema_versions, request): @using("rdkit") @using("geometric") def test_geometric_stdout(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "UFF", "basis": ""} @@ -99,7 +103,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) + ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -109,7 +113,7 @@ def test_geometric_stdout(input_data, schema_versions, request): @using("psi4") @using("berny") def test_berny_stdout(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} @@ -118,7 +122,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) + ret = qcng.compute_procedure(input_data, "berny", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -128,7 +132,7 @@ def test_berny_stdout(input_data, schema_versions, request): @using("psi4") @using("berny") def test_berny_failed_gradient_computation(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF", "basis": "sto-3g"} @@ -138,7 +142,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) + ret = qcng.compute_procedure(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)) @@ -149,7 +153,7 @@ def test_berny_failed_gradient_computation(input_data, schema_versions, request) @using("geometric") @using("rdkit") def test_geometric_rdkit_error(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)).copy( exclude={"connectivity_"} @@ -160,7 +164,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") + ret = qcng.compute_procedure(input_data, "geometric", return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post", vercheck=False) assert ret.success is False @@ -170,7 +174,7 @@ def test_geometric_rdkit_error(input_data, schema_versions, request): @using("rdkit") @using("geometric") def test_optimization_protocols(input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = {"method": "UFF"} @@ -180,7 +184,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) + ret = qcng.compute_procedure(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 @@ -191,7 +195,7 @@ def test_optimization_protocols(input_data, schema_versions, request): @using("geometric") def test_geometric_retries(failure_engine, input_data, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions failure_engine.iter_modes = ["random_error", "pass", "random_error", "random_error", "pass"] # Iter 1 # Iter 2 failure_engine.iter_modes.extend(["pass"] * 20) @@ -207,7 +211,9 @@ 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) + ret = qcng.compute_procedure( + 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 @@ -220,7 +226,9 @@ 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}) + ret = qcng.compute_procedure( + 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 @@ -286,7 +294,7 @@ def test_geometric_retries(failure_engine, input_data, schema_versions, request) ], ) def test_geometric_generic(input_data, program, model, bench, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("water", return_dict=True)) input_data["input_specification"]["model"] = model @@ -296,7 +304,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) + ret = qcng.compute_procedure(input_data, "geometric", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.success is True @@ -316,7 +324,7 @@ def test_geometric_generic(input_data, program, model, bench, schema_versions, r @using("nwchem") @pytest.mark.parametrize("linopt", [0, 1]) def test_nwchem_relax(linopt, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # Make the input file input_data = { @@ -330,7 +338,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) + ret = qcng.compute_procedure(input_data, "nwchemdriver", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert 10 > len(ret.trajectory) > 1 @@ -340,7 +348,7 @@ def test_nwchem_relax(linopt, schema_versions, request): @using("nwchem") def test_nwchem_restart(tmpdir, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions # Make the input file input_data = { @@ -357,12 +365,16 @@ 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) + ret = qcng.compute_procedure( + 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) + new_ret = qcng.compute_procedure( + 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 @@ -370,7 +382,7 @@ def test_nwchem_restart(tmpdir, schema_versions, request): @using("rdkit") @using("torsiondrive") def test_torsiondrive_generic(schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions TorsionDriveInput = models.TorsionDriveInput TDKeywords = models.procedures.TDKeywords OptimizationSpecification = models.procedures.OptimizationSpecification @@ -394,7 +406,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) + ret = qcng.compute_procedure(input_data, "torsiondrive", raise_error=True, return_version=retver) ret = checkver_and_convert(ret, request.node.name, "post") assert ret.error is None @@ -430,7 +442,7 @@ def test_torsiondrive_generic(schema_versions, request): ], ) def test_optimization_mrchem(input_data, optimizer, schema_versions, request): - models, _ = schema_versions + models, retver, _ = schema_versions input_data["initial_molecule"] = models.Molecule(**qcng.get_molecule("hydrogen", return_dict=True)) input_data["input_specification"]["model"] = {"method": "HF"} @@ -440,7 +452,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) + ret = qcng.compute_procedure(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/util.py b/qcengine/util.py index df3366a43..710793c43 100644 --- a/qcengine/util.py +++ b/qcengine/util.py @@ -19,7 +19,6 @@ from typing import Any, BinaryIO, Dict, List, Optional, TextIO, Tuple, Union import pydantic -from pydantic import BaseModel from qcelemental.models import AtomicResult, FailedOperation, OptimizationResult from qcengine.config import TaskConfig @@ -56,7 +55,9 @@ def create_mpi_invocation(executable: str, task_config: TaskConfig) -> List[str] # TODO v1.BaseModel -def model_wrapper(input_data: Dict[str, Any], model: BaseModel) -> BaseModel: +def model_wrapper( + input_data: Dict[str, Any], model: Union[pydantic.BaseModel, pydantic.v1.BaseModel] +) -> Union[pydantic.BaseModel, pydantic.v1.BaseModel]: """ Wrap input data in the given model, or return a controlled error """ @@ -149,15 +150,23 @@ def handle_output_metadata( metadata: Dict[str, Any], raise_error: bool = False, return_dict: bool = True, + convert_version: int = -1, ) -> Union[Dict[str, Any], "AtomicResult", "OptimizationResult", "FailedOperation"]: """ Fuses general metadata and output together. - Parameters: - output_data: The original output object to be fused with metadata - metadata: Metadata produced by the compute_wrapper context manager - raise_error: Raise an exception if errors exist (True) or return FailedOperation (False) - return_dict: Return dictionary or object representation of data + Parameters + ---------- + output_data + The original output object to be fused with metadata + metadata + Metadata produced by the compute_wrapper context manager + raise_error + Raise an exception if errors exist (True) or return FailedOperation (False) + return_dict + Return dictionary or object representation of data + convert_version + The schema version to convert to before return. If -1, don't convert. Returns ------- @@ -214,6 +223,9 @@ def handle_output_metadata( 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) + if return_dict: return json.loads(ret.json()) # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals else: From e2d99f214ef5ce6c8a758b4b5530ba7464c40c25 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Sat, 5 Oct 2024 03:02:15 -0400 Subject: [PATCH 5/7] fix errors --- qcengine/compute.py | 4 ++-- qcengine/util.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qcengine/compute.py b/qcengine/compute.py index e43979469..2ca9b4394 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -89,7 +89,7 @@ def compute( # * calls model_wrapper with the (Atomic|Optimization|etc)Input for which the harness was designed # * upon return, input_data is a model of the type (e.g., Atomic) and version (e.g., 1 or 2) the harness prefers. for now, v1. input_data, input_schema_version = executor.build_input_model(input_data, return_input_schema_version=True) - convert_version = input_schema_version if return_version == -1 else return_version + return_version = input_schema_version if return_version == -1 else return_version # Build out task_config if task_config is None: @@ -116,7 +116,7 @@ def compute( raise return handle_output_metadata( - output_data, metadata, raise_error=raise_error, return_dict=return_dict, convert_version=convert_version + output_data, metadata, raise_error=raise_error, return_dict=return_dict, convert_version=return_version ) diff --git a/qcengine/util.py b/qcengine/util.py index 710793c43..3b1048653 100644 --- a/qcengine/util.py +++ b/qcengine/util.py @@ -227,7 +227,9 @@ def handle_output_metadata( ret = ret.convert_v(convert_version) if return_dict: - return json.loads(ret.json()) # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals + return json.loads( + ret.model_dump_json() + ) # Use Pydantic to serialize, then reconstruct as Python dict of Python Primals else: return ret From 4613cc79ab1ca2a247738ee26426b18e45be8a19 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Sun, 6 Oct 2024 14:01:01 -0400 Subject: [PATCH 6/7] fix mdi --- qcengine/compute.py | 9 ++++++--- qcengine/mdi_server.py | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/qcengine/compute.py b/qcengine/compute.py index 2ca9b4394..224394059 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -4,7 +4,8 @@ import warnings from typing import TYPE_CHECKING, Any, Dict, Optional, Union -from qcelemental.models import AtomicInput, AtomicResult, FailedOperation, OptimizationResult +import qcelemental +from qcelemental.models import AtomicInput, AtomicResult, FailedOperation, OptimizationResult # TODO from .config import get_config from .exceptions import InputError, RandomError @@ -121,9 +122,11 @@ def compute( def compute_procedure(*args, **kwargs): + vchanges = qcelemental.models.common_models._qcsk_v2_default_v1_importpathschange + warnings.warn( - "Using the `compute_procedure` function is deprecated in favor of using `compute`, " - "and as soon as version 0.70.0 it will stop working.", + f"Using the `compute_procedure` function is deprecated in favor of using `compute`, " + "and as soon as version {vchanges} it will stop working.", category=FutureWarning, stacklevel=2, ) diff --git a/qcengine/mdi_server.py b/qcengine/mdi_server.py index 90fff9e32..f3e6bf9a2 100644 --- a/qcengine/mdi_server.py +++ b/qcengine/mdi_server.py @@ -41,6 +41,7 @@ def __init__( model, keywords, raise_error: bool = False, + task_config: Optional[Dict[str, Any]] = None, local_options: Optional[Dict[str, Any]] = None, ): """Initialize an MDIServer object for communication with MDI @@ -59,8 +60,9 @@ def __init__( Program-specific keywords. raise_error : bool, optional Determines if compute should raise an error or not. - local_options : Optional[Dict[str, Any]], optional + task_config : Optional[Dict[str, Any]], optional A dictionary of local configuration options + Can be passed as `local_options`, but `task_config` preferred. """ if not use_mdi: @@ -81,7 +83,17 @@ def __init__( self.keywords = keywords self.program = program self.raise_error = raise_error - self.local_options = local_options + if task_config is None: + task_config = {} + if local_options: + warnings.warn( + "Using the `local_options` keyword argument is deprecated in favor of using `task_config`, " + "and as soon as version 0.70.0 it will stop working.", + category=FutureWarning, + stacklevel=2, + ) + task_config = {**local_options, **task_config} + self.local_options = task_config # The MDI interface does not currently support multiple fragments if len(self.molecule.fragments) != 1: @@ -320,7 +332,7 @@ def run_energy(self) -> None: molecule=self.molecule, driver="gradient", model=self.model, keywords=self.keywords ) self.compute_return = compute( - input_data=input, program=self.program, raise_error=self.raise_error, local_options=self.local_options + input_data=input, program=self.program, raise_error=self.raise_error, task_config=self.local_options ) # If there is an error message, print it out From 44ac0a62a406728b92f2be27f89b0b1c764d51bc Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Sun, 6 Oct 2024 15:37:16 -0400 Subject: [PATCH 7/7] compensate v1.6 --- .github/workflows/CI.yml | 6 ++++++ docs/source/changelog.rst | 17 ++++++++++++----- qcengine/compute.py | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 76c21e0c1..9815c41d5 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -173,6 +173,12 @@ jobs: run: | sed -i s/from\ pydantic\ /from\ pydantic.v1\ /g ${CONDA_PREFIX}/lib/python${{ matrix.cfg.python-version }}/site-packages/psi4/driver/*py + - name: Special Config - Forced Interface Upgrade + if: "(matrix.cfg.label == 'Psi4-1.6')" + run: | + grep -r "local_options" ${CONDA_PREFIX}/lib/python${{ matrix.cfg.python-version }}/site-packages/psi4/driver/ + sed -i "s/local_options/task_config/g" ${CONDA_PREFIX}/lib/python${{ matrix.cfg.python-version }}/site-packages/psi4/driver/procrouting/*py + - name: Install QCEngine run: | python -m pip install . --no-deps diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 88a4cd733..61c4022ba 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -36,11 +36,17 @@ Breaking Changes - (:pr:`453`) Deps - Require pydantic v2 dependency (don't worry, this isn't changing QCEngine's role as QCSchema I/O runner. Also require pydantic-settings for CLI. @loriab -* as promised, `local_options` has been removed in favor of `task_config`. -* compute and compute_procedure have been merged in favor of the former. -* `compute` learned an optional argument `return_version` to specify the schema_version of the - returned model or dictionary. By default it'll return the input schema_version. If not - determinable, will return v1. @loriab +- (:pr:`455`) API - As promised 2 years ago for >=v0.30, `local_options` has + been removed in favor of `task_config` in `compute` and `compute_procedure`. + Note that Psi4 v1.6 will need an older qcel or a sed to work (see GHA). The + `qcengine.MDIEngine` is on notice (probably not user-facing. @loriab +- (:pr:`455`) API - `qcengine.compute` and `qcengine.compute_procedure` have been + merged in favor of the former. Also, treat the second argument (e.g., "nwchem" + or "geometric") as a positional argument, rather than keyword argument with key + "program" or "procedure". @loriab +- (:pr:`455`) API - `compute` learned an optional argument `return_version` to + specify the schema_version of the returned model or dictionary. By default it'll + return the input schema_version. If not determinable, it will return v1. @loriab New Features ++++++++++++ @@ -50,6 +56,7 @@ Enhancements - (:pr:`453`) Maint - Convert internal (non-QCSchema) pydantic classes to pydantic v2 API, namely `NodeDescriptor`, `TaskConfig`, `ProgramHarness`, `ProcedureHarness`. @loriab +- (:pr:`454`) Testing - Tests check QCSchema v1 and v2. @loriab Bug Fixes +++++++++ diff --git a/qcengine/compute.py b/qcengine/compute.py index 224394059..ab3139f69 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -60,6 +60,7 @@ def compute( The number of random tries to retry for. task_config A dictionary of local configuration options corresponding to a TaskConfig object. + Formerly local_options. return_dict Returns a dict instead of qcelemental.models.AtomicResult # TODO base Result class return_version