From bb75987aa0fd1f4e24e33bf9ebfd0c9e2b2ed680 Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Mon, 14 Oct 2024 12:32:32 -0400 Subject: [PATCH] Csse pyd2 test use multiple schema versions (#454) * 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