From f12766f8a343726803c009dd6e720f0fbbff0bfd Mon Sep 17 00:00:00 2001 From: "Lori A. Burns" Date: Wed, 30 Nov 2022 02:34:29 -0500 Subject: [PATCH] adapt s-dftd3 harness for psi4 (#385) * adapt s-dftd3 harness for psi4 * retrofit s-dftd3 adaptations to old psi * tidying * fix pairwise in classic dftd3 * fix sapt pairwise and format * work on tests --- docs/source/changelog.rst | 48 +++-- qcengine/programs/dftd3.py | 15 +- qcengine/programs/dftd_ng.py | 14 +- .../empirical_dispersion_resources.py | 179 ++++++++++++++---- .../programs/tests/test_canonical_config.py | 6 +- qcengine/programs/tests/test_dftd3_mp2d.py | 92 +++++---- qcengine/programs/tests/test_dftd4.py | 2 +- 7 files changed, 260 insertions(+), 96 deletions(-) diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 5a258a2cb..e17bd64e9 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -17,20 +17,36 @@ Changelog .. +++++++++ -v0.26.0 / 2022-MM-DD (Unreleased) +v0.26.0 / 2022-11-30 -------------------- -.. Breaking Changes -.. ++++++++++++++++ +Breaking Changes +++++++++++++++++ -.. New Features -.. ++++++++++++ +- (:pr:`385`) Dispersion - the dispersion parameters resources file has been altered so that for D3 variants there's a + 2b set (e.g., d3bj2b) that is pure 2-body and doesn't accept s9 (effectively fixed at 0.0) and a atm set (e.g., + d3zeroatm) that does accept s9 (by default 1.0 but user-variable). Previous D3 levels are aliased to 2b. Only + downstreams that call the dispersion resources directly should be affected, and retrofits are in place for the known + victim/instigator (Psi4). @loriab -.. Enhancements -.. ++++++++++++ +New Features +++++++++++++ -.. Bug Fixes -.. +++++++++ +Enhancements +++++++++++++ +- (:pr:`380`) MRChem - added gradient and thus geometry optimizations support. @robertodr +- (:pr:`385`) dftd3 - the classic interface now accepts e.g., ``d3mbj2b`` as a level hint. @loriab +- (:pr:`385`) s-dftd3 - added keyword ``apply_qcengine_aliases`` that when True and ``level_hint`` present allows the + levels and aliases in the dispersion resources (e.g., ``d3``, ``d3atm``, ``d32b``) to be given as ``level_hint``. The + resource parameters are passed to s-dftd3 as param_tweaks. @loriab + +Bug Fixes ++++++++++ +- (:pr:`383`) yaml - uses safe loading. @mbanck, @loriab +- (:pr:`385`) dftd3 - the pairwise analysis requested through ``AtomicInput.keywords["pair_resolved"] = True`` and + returned in ``AtomicResult.extras["qcvars"]["2-BODY PAIRWISE DISPERSION CORRECTION ANALYSIS"]`` was elementwise too + large by a factor of 2. It now matches the ``s-dftd3`` harness and fulfills that the sum of the array equals the + 2-body dispersion energy. @loriab v0.25.0 / 2022-11-11 @@ -39,20 +55,20 @@ v0.25.0 / 2022-11-11 Breaking Changes ++++++++++++++++ - (:pr:`376`) GAMESS - slight breaking changes of (1) ROHF MP2 ZAPT quantities now stored in "ZAPT" variables, not "MP2" - variables; and (2) "HF TOTAL ENERGY" no longer stores DFT energy in DFT computation. + variables; and (2) "HF TOTAL ENERGY" no longer stores DFT energy in DFT computation. @loriab - (:pr:`376`) testing - reference quantities now indexed by "standard" or "semicanonical" orbitals since program defaults differ (mostly in CCSD ROHF FC). Downstream projects using the stdsuite interface will need to add an extra argument to query - reference data. + reference data. @loriab New Features ++++++++++++ Enhancements ++++++++++++ -- (:pr:`376`) Cfour - added parsing for BCCD and BCCD(T) methods. -- (:pr:`376`) NWChem - B2PLYP double-hybrid can now be run and parsed. Added CC2 parsing. -- (:pr:`376`) testing - added parsing contracts for ZAPT2, CEPA(1), CEPA(3), ACPF, AQCC, BCCD, BCCD(T), CC2, CC3, and DH-DFT. Added conventional references for most. -- (:pr:`378`) OpenFF - Support OpenFF Toolkit v0.11+ +- (:pr:`376`) Cfour - added parsing for BCCD and BCCD(T) methods. @loriab +- (:pr:`376`) NWChem - B2PLYP double-hybrid can now be run and parsed. Added CC2 parsing. @loriab +- (:pr:`376`) testing - added parsing contracts for ZAPT2, CEPA(1), CEPA(3), ACPF, AQCC, BCCD, BCCD(T), CC2, CC3, and DH-DFT. Added conventional references for most. @loriab +- (:pr:`378`) OpenFF - Support OpenFF Toolkit v0.11+. @Yoshanuikabundi Bug Fixes +++++++++ @@ -63,7 +79,7 @@ v0.24.1 / 2022-08-16 Enhancements ++++++++++++ -- (:pr:`375`) testing - in standard suite, add reference values for occ, a-ccsd(t), olccd grad, remp2, omp2, omp2.5, omp3, oremp2, density fitted ccsd, ccsd(t), a-ccsd(t). +- (:pr:`375`) testing - in standard suite, add reference values for occ, a-ccsd(t), olccd grad, remp2, omp2, omp2.5, omp3, oremp2, density fitted ccsd, ccsd(t), a-ccsd(t). @loriab v0.24.0 / 2022-07-08 diff --git a/qcengine/programs/dftd3.py b/qcengine/programs/dftd3.py index db733013f..2f67fcfee 100644 --- a/qcengine/programs/dftd3.py +++ b/qcengine/programs/dftd3.py @@ -201,7 +201,7 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> atom1 = int(data[0]) - 1 atom2 = int(data[1]) - 1 Edisp = Decimal(data[-1]) - D3pairs[atom1, atom2] = Edisp / Decimal(Grimme_h2kcal) + D3pairs[atom1, atom2] = Decimal(0.5) * Edisp / Decimal(Grimme_h2kcal) D3pairs[atom2, atom1] = D3pairs[atom1, atom2] elif re.match(" normal termination of dftd3", ln): @@ -297,6 +297,9 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> output_data["extras"]["local_keywords"] = input_model.extras["info"] output_data["extras"]["qcvars"] = calcinfo if input_model.keywords.get("pair_resolved", False): + assert ( + abs(D3pairs.sum() - float(retres)) < 1.0e-6 + ), f"pairwise sum {D3pairs.sum()} != energy {float(retres)}" output_data["extras"]["qcvars"]["2-BODY PAIRWISE DISPERSION CORRECTION ANALYSIS"] = D3pairs output_data["success"] = True @@ -315,6 +318,8 @@ def dftd3_coeff_formatter(dashlvl: str, dashcoeff: Dict) -> str: d3mbj: s6 a1 s8 a2 alpha6=None version=6 atmgr: s6=1.0 sr6=None s8=None a2=None alpha6 version=3 (needs -abc, too) + 2-body variant here. that is, d3zero2b + Parameters ---------- dashlvl : {'d2', 'd3zero', d3bj', 'd3mzero', 'd3mbj', 'atmgr'} @@ -339,15 +344,15 @@ def dftd3_coeff_formatter(dashlvl: str, dashcoeff: Dict) -> str: dashlvl = dashlvl.lower() if dashlvl == "d2": return dashformatter.format(dashcoeff["s6"], dashcoeff["sr6"], 0.0, 0.0, dashcoeff["alpha6"], 2) - elif dashlvl == "d3zero": + elif dashlvl == "d3zero2b": return dashformatter.format( dashcoeff["s6"], dashcoeff["sr6"], dashcoeff["s8"], dashcoeff["sr8"], dashcoeff["alpha6"], 3 ) - elif dashlvl == "d3bj": + elif dashlvl == "d3bj2b": return dashformatter.format(dashcoeff["s6"], dashcoeff["a1"], dashcoeff["s8"], dashcoeff["a2"], 0.0, 4) - elif dashlvl == "d3mzero": + elif dashlvl == "d3mzero2b": return dashformatter.format(dashcoeff["s6"], dashcoeff["sr6"], dashcoeff["s8"], dashcoeff["beta"], 14.0, 5) - elif dashlvl == "d3mbj": + elif dashlvl == "d3mbj2b": return dashformatter.format(dashcoeff["s6"], dashcoeff["a1"], dashcoeff["s8"], dashcoeff["a2"], 0.0, 6) elif dashlvl == "atmgr": # need to set first four parameters to something other than None, otherwise DFTD3 gets mad or a bit wrong diff --git a/qcengine/programs/dftd_ng.py b/qcengine/programs/dftd_ng.py index e4aa59f61..8fe0af980 100644 --- a/qcengine/programs/dftd_ng.py +++ b/qcengine/programs/dftd_ng.py @@ -242,7 +242,7 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult: input_data["model"]["method"] = method qcvkey = method.upper() if method is not None else None - # send `from_arrays` the dftd3 behavior of functional specification overrides explicit parameters specification + # send `from_arrays` the s-dftd3 behavior of functional specification overrides explicit parameters specification # * differs from dftd3 harness behavior where parameters extend or override functional # * stash the resolved plan in extras or, if errored, leave it for the proper dftd3 api to reject param_tweaks = None if method else input_model.keywords.get("params_tweaks", None) @@ -265,6 +265,18 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult: method = method[: -(len(alias) + 1)] input_data["model"]["method"] = method + # consolidate dispersion level aliases + if input_model.keywords.pop("apply_qcengine_aliases", False): + level_hint = input_model.keywords.get("level_hint", None) + if level_hint: + level_hint = get_dispersion_aliases()[level_hint.lower()] + if level_hint.endswith("atm"): + level_hint = level_hint[:-3] + if level_hint.endswith("2b"): + level_hint = level_hint[:-2] + input_data["keywords"]["params_tweaks"] = {**planinfo["dashparams"], "s9": 0.0} + input_data["keywords"]["level_hint"] = level_hint + input_model = AtomicInput(**input_data) # Run the Harness diff --git a/qcengine/programs/empirical_dispersion_resources.py b/qcengine/programs/empirical_dispersion_resources.py index 67ac2fd8e..2812284c1 100644 --- a/qcengine/programs/empirical_dispersion_resources.py +++ b/qcengine/programs/empirical_dispersion_resources.py @@ -2,6 +2,7 @@ import collections import copy +from typing import Dict, List, Optional, Union from ..exceptions import InputError @@ -44,9 +45,22 @@ "core-dsd-blyp": {"params": {"s6": 0.41, "alpha6": 60.0, "sr6": 1.1}}, }, }, - "d3zero": { + "d3zeroatm": { + "formal": "D3ATM", + "alias": [], + "description": " Grimme's -D3 (zero-damping) Dispersion Correction with ATM", + "citation": " Grimme S.; Antony J.; Ehrlich S.; Krieg H. (2010), J. Chem. Phys., 132: 154104\n", + "bibtex": "Grimme:2010:154104", + "default": collections.OrderedDict( + [("s6", 1.0), ("s8", 0.0), ("sr6", 1.0), ("alpha6", 14.0), ("sr8", 1.0), ("s9", 1.0)] + ), + "definitions": { + # D3 parameters loaded from d3zero2b and from authoritative source below and s9 parameter added + }, + }, + "d3zero2b": { "formal": "D3", - "alias": ["d3"], + "alias": ["d3", "d3zero", "d32b"], "description": " Grimme's -D3 (zero-damping) Dispersion Correction", "citation": " Grimme S.; Antony J.; Ehrlich S.; Krieg H. (2010), J. Chem. Phys., 132: 154104\n", "bibtex": "Grimme:2010:154104", @@ -348,9 +362,20 @@ "hf": {"params": {"s6": 1.0, "s8": 1.746, "sr6": 1.158, "alpha6": 14.0, "sr8": 1.000}}, }, }, - "d3bj": { - "formal": "D3(BJ)", + "d3bjatm": { + "formal": "D3(BJ)ATM", "alias": [], + "description": " Grimme's -D3 (BJ-damping) Dispersion Correction with ATM", + "citation": " Grimme S.; Ehrlich S.; Goerigk L. (2011), J. Comput. Chem., 32: 1456\n", + "bibtex": "Grimme:2011:1456", + "default": collections.OrderedDict([("s6", 1.0), ("s8", 1.0), ("a1", 0.0), ("a2", 1.0), ("s9", 1.0)]), + "definitions": { + # D3 parameters loaded from d3bj2b and from authoritative source below and s9 parameter added + }, + }, + "d3bj2b": { + "formal": "D3(BJ)", + "alias": ["d3bj"], "description": " Grimme's -D3 (BJ-damping) Dispersion Correction", "citation": " Grimme S.; Ehrlich S.; Goerigk L. (2011), J. Comput. Chem., 32: 1456\n", "bibtex": "Grimme:2011:1456", @@ -620,9 +645,21 @@ "core-dsd-blyp": {"params": {"s6": 0.500, "s8": 0.2130, "a1": 0.0000, "a2": 6.0519}}, }, }, - "d3mzero": { + "d3mzeroatm": { + "formal": "D3MATM", + "alias": [], + "description": " Grimme's -D3 (zero-damping, short-range refitted) Dispersion Correction with ATM", + "citation": " Grimme S.; Antony J.; Ehrlich S.; Krieg H. (2010), J. Chem. Phys., 132: 154104\n" + + " Smith, D. G. A.; Burns, L. A.; Patkowski, K.; Sherrill, C. D. (2016), J. Phys. Chem. Lett.; 7: 2197\n", + "bibtex": "Grimme:2010:154104", + "default": collections.OrderedDict([("s6", 1.0), ("s8", 1.0), ("sr6", 1.0), ("beta", 1.0), ("s9", 1.0)]), + "definitions": { + # D3 parameters loaded from d3mzero2b and from authoritative source below and s9 parameter added + }, + }, + "d3mzero2b": { "formal": "D3M", - "alias": ["d3m"], + "alias": ["d3m", "d3mzero", "d3m2b"], "description": " Grimme's -D3 (zero-damping, short-range refitted) Dispersion Correction", "citation": " Grimme S.; Antony J.; Ehrlich S.; Krieg H. (2010), J. Chem. Phys., 132: 154104\n" + " Smith, D. G. A.; Burns, L. A.; Patkowski, K.; Sherrill, C. D. (2016), J. Phys. Chem. Lett.; 7: 2197\n", @@ -641,9 +678,21 @@ "hf": {"params": {"s6": 1.000, "s8": 0.885517, "sr6": 1.383214, "beta": 0.075488}}, # JBS 01/2021 }, }, - "d3mbj": { - "formal": "D3M(BJ)", + "d3mbjatm": { + "formal": "D3M(BJ)ATM", "alias": [], + "description": " Grimme's -D3 (BJ-damping, short-range refitted) Dispersion Correction with ATM", + "citation": " Grimme S.; Ehrlich S.; Goerigk L. (2011), J. Comput. Chem., 32: 1456\n" + + " Smith, D. G. A.; Burns, L. A.; Patkowski, K.; Sherrill, C. D. (2016), J. Phys. Chem. Lett.; 7: 2197\n", + "bibtex": "Grimme:2011:1456", + "default": collections.OrderedDict([("s6", 1.0), ("s8", 1.0), ("a1", 1.0), ("a2", 1.0), ("s9", 1.0)]), + "definitions": { + # D3 parameters loaded from d3mbj2b and from authoritative source below and s9 parameter added + }, + }, + "d3mbj2b": { + "formal": "D3M(BJ)", + "alias": ["d3mbj"], "description": " Grimme's -D3 (BJ-damping, short-range refitted) Dispersion Correction", "citation": " Grimme S.; Ehrlich S.; Goerigk L. (2011), J. Comput. Chem., 32: 1456\n" + " Smith, D. G. A.; Burns, L. A.; Patkowski, K.; Sherrill, C. D. (2016), J. Phys. Chem. Lett.; 7: 2197\n", @@ -662,19 +711,33 @@ "hf": {"params": {"s6": 1.000, "s8": 0.713190, "a1": 0.079541, "a2": 3.627854}}, # JBS 01/2021 }, }, - "d3op": { - "formal": "D3(op)", + "d3opatm": { + "formal": "D3(op)ATM", "alias": [], + "description": " D3 dispersion correction with optimized power damping function with ATM. Based on rational damping function and additional zero-damping like power function.", + "citation": " S. Grimme, S. Ehrlich, and L. Goerigk., Comput. Chem., 32:1456–1465, 2011. doi:10.1002/jcc.21759.\n" + + " Jonathon Witte, Narbe Mardirossian, Jeffrey B Neaton, and Martin Head-Gordon., J. Chem. Theory Comput., 13(5):2043–2052, 2017. doi:10.1021/acs.jctc.7b00176.\n", + "bibtex": "Grimme:2011:1456", + "default": collections.OrderedDict( + [("s6", 1.0), ("s8", 1.0), ("a1", 1.0), ("a2", 1.0), ("beta", 0.0), ("s9", 1.0)] + ), + "definitions": { + # D3 parameters loaded from d3op2b and from authoritative source below and s9 parameter added + }, + }, + "d3op2b": { + "formal": "D3(op)", + "alias": ["d3op"], "description": " D3 dispersion correction with optimized power damping function. Based on rational damping function and additional zero-damping like power function.", "citation": " S. Grimme, S. Ehrlich, and L. Goerigk., Comput. Chem., 32:1456–1465, 2011. doi:10.1002/jcc.21759.\n" + " Jonathon Witte, Narbe Mardirossian, Jeffrey B Neaton, and Martin Head-Gordon., J. Chem. Theory Comput., 13(5):2043–2052, 2017. doi:10.1021/acs.jctc.7b00176.\n", "bibtex": "Grimme:2011:1456", "default": collections.OrderedDict( - [("a1", 1.0), ("a2", 1.0), ("alp", 14.0), ("s6", 1.0), ("s8", 1.0), ("s9", 1.0), ("bet", 0.0)] + [("s6", 1.0), ("s8", 1.0), ("a1", 1.0), ("a2", 1.0), ("beta", 0.0)], ), "definitions": { # will be loaded later - # From https://github.com/awvwgk/simple-dftd3/blob/main/assets/parameters.toml + # From https://github.com/dftd3/simple-dftd3/blob/main/assets/parameters.toml }, }, "nl": { @@ -829,6 +892,15 @@ } +# for d3*atm, only skeleton entries with metadata defined above. below copies in parameters from d3*2b +for d in ["d3zero", "d3bj", "d3mzero", "d3mbj", "d3op"]: + for k, v in dashcoeff[d + "2b"]["definitions"].items(): + dashcoeff[d + "atm"]["definitions"][k] = copy.deepcopy(dashcoeff[d + "2b"]["definitions"][k]) + dashcoeff[d + "atm"]["definitions"][k]["params"][ + "s9" + ] = 1.0 # set twice: this one adds s9 to the params list for atm + + def _get_d4bj_definitions() -> dict: """DFTD4 provides access to damping parameters on per functional basis. But we want all of them. @@ -912,11 +984,11 @@ def _get_d3_definitions(dashlevel: str) -> dict: # The names here are the subset allowed by qcng with the names used in dftd3 allowed = { - "bj": ["a1", "a2", "s6", "s8"], - "zero": ["rs6", "rs8", "alp", "s6", "s8"], - "mbj": ["a1", "a2", "s6", "s8"], - "mzero": ["rs6", "rs8", "alp", "s6", "s8", "bet"], - "op": ["a1", "a2", "s6", "s8", "bet"], + "bj": ["a1", "a2", "s6", "s8", "s9"], + "zero": ["rs6", "rs8", "alp", "s6", "s8", "s9"], + "mbj": ["a1", "a2", "s6", "s8", "s9"], + "mzero": ["rs6", "rs8", "alp", "s6", "s8", "bet", "s9"], + "op": ["a1", "a2", "s6", "s8", "bet", "s9"], } # mapping from dftd3 to qcng names, also, we have to reverse it later again rename = { @@ -941,11 +1013,17 @@ def _get_d3_definitions(dashlevel: str) -> dict: for key, params in get_all_damping_params([dashlevel]).items() } - dashcoeff["d3bj"]["definitions"].update(_get_d3_definitions("bj")) - dashcoeff["d3zero"]["definitions"].update(_get_d3_definitions("zero")) - dashcoeff["d3mbj"]["definitions"].update(_get_d3_definitions("mbj")) - dashcoeff["d3mzero"]["definitions"].update(_get_d3_definitions("mzero")) - dashcoeff["d3op"]["definitions"].update(_get_d3_definitions("op")) + dashcoeff["d3bjatm"]["definitions"].update(_get_d3_definitions("bj")) + dashcoeff["d3zeroatm"]["definitions"].update(_get_d3_definitions("zero")) + dashcoeff["d3mbjatm"]["definitions"].update(_get_d3_definitions("mbj")) + dashcoeff["d3mzeroatm"]["definitions"].update(_get_d3_definitions("mzero")) + dashcoeff["d3opatm"]["definitions"].update(_get_d3_definitions("op")) + + for d in ["d3zero", "d3bj", "d3mzero", "d3mbj", "d3op"]: + for k, v in dashcoeff[d + "atm"]["definitions"].items(): + dashcoeff[d + "atm"]["definitions"][k]["params"][ + "s9" + ] = 1.0 # set twice: this one establishes 1.0 if upstream is still 0.0 (pre v0.7.0 simple-dftd3) except ModuleNotFoundError: pass @@ -967,7 +1045,13 @@ def get_dispersion_aliases(): return alias -def from_arrays(name_hint=None, level_hint=None, param_tweaks=None, dashcoeff_supplement=None, verbose=1): +def from_arrays( + name_hint: Optional[str] = None, + level_hint: Optional[str] = None, + param_tweaks: Union[List[float], Dict[str, float]] = None, + dashcoeff_supplement: Optional[Dict[str, Dict]] = None, + verbose: int = 1, +): """Use the three paths of empirical dispersion parameter information (DFT functional, dispersion correction level, and particular parameters) to populate the parameter array and validate a @@ -975,27 +1059,27 @@ def from_arrays(name_hint=None, level_hint=None, param_tweaks=None, dashcoeff_su Parameters ---------- - name_hint : str, optional + name_hint Name of functional (func only, func & disp, or disp only) for which to compute dispersion (e.g., blyp, BLYP-D2, blyp-d3bj, blyp-d3(bj), hf+d). Any or all parameters initialized from `dashcoeff[dashlevel][functional-without-dashlevel]` or `dashcoeff_supplement[dashlevel][functional-with-dashlevel] can be overwritten via `param_tweaks`. - level_hint : str, optional + level_hint Name of dispersion correction to be applied (e.g., d, D2, d3(bj), das2010). Must be key in `dashcoeff` or "alias" or "formal" to one. - param_tweaks : list or dict, optional + param_tweaks Values for the same keys as `dashcoeff[dashlevel]['default']` (and same order if list) used to override any or all values initialized by `name_hint`. Extra parameters will error. - dashcoeff_supplement: dict, optional + dashcoeff_supplement Dictionary of the same structure as `dashcoeff` that contains in "definitions" field full functional names, rather than fctl less dashlvl. Used to validate dict_builder fctls with dispersion or identify disp level from just `name_hint`. - verbose : int, optional + verbose Amount of printing. Returns @@ -1003,7 +1087,7 @@ def from_arrays(name_hint=None, level_hint=None, param_tweaks=None, dashcoeff_su dict Metadata defining dispersion calculation. - dashlevel : {'d1', 'd2', 'd3zero', 'd3bj', 'd3mzero', 'd3mbj', 'd3op', 'chg', 'das2009', 'das2010', 'nl', "d4bjeeqatm"} + dashlevel : {'d1', 'd2', 'd3zero2b', 'd3bj2b', 'd3mzero2b', 'd3mbj2b', 'd3op2b', 'd3zeroatm', 'd3bjatm', 'd3mzeroatm', 'd3mbjatm', 'd3opatm', 'chg', 'das2009', 'das2010', 'nl', "d4bjeeqatm"} Name (de-aliased, de-formalized, lowercase) of dispersion correction -- atom data, dispersion model, damping functional form -- to be applied. Resolved from `name_hint` and/or @@ -1032,14 +1116,45 @@ def from_arrays(name_hint=None, level_hint=None, param_tweaks=None, dashcoeff_su * Function intended to be idempotent. """ + try: + # try/except block retrofits Psi4 for pre-2b/atm-split in dashcoeff + import psi4 + + if "d3bj" in psi4.procrouting.empirical_dispersion._engine_can_do["dftd3"]: + psi4.procrouting.empirical_dispersion._engine_can_do["dftd3"] = [ + "d2", + "d3zero2b", + "d3bj2b", + "d3mzero2b", + "d3mbj2b", + ] + for d in ["d3zero", "d3bj", "d3mzero", "d3mbj"]: + psi4.procrouting.empirical_dispersion._capable_engines_for_disp[ + d + "2b" + ] = psi4.procrouting.empirical_dispersion._capable_engines_for_disp.pop(d) + except ImportError: + pass + if verbose > 1: - print("dftd3.from_arrays HINTS:", name_hint, level_hint, param_tweaks, bool(dashcoeff_supplement)) + print( + "empirical_dispersion_resources.from_arrays HINTS:", + name_hint, + level_hint, + param_tweaks, + bool(dashcoeff_supplement), + ) # << 0 >> prep if dashcoeff_supplement is not None: supplement_dashlevel_lookup = {} - for disp, ddisp in dashcoeff_supplement.items(): + for disp, ddisp in dashcoeff_supplement.copy().items(): for func, params in ddisp["definitions"].items(): + try: + dashcoeff[disp] + except KeyError: + # try/except block accommodates dashcoeff_supplement from pre-2b/atm-split in dashcoeff + disp = get_dispersion_aliases()[disp] + dashcoeff_supplement[disp] = ddisp if params["params"].keys() != dashcoeff[disp]["default"].keys(): if verbose > 2: print( @@ -1212,7 +1327,7 @@ def from_arrays(name_hint=None, level_hint=None, param_tweaks=None, dashcoeff_su if verbose > 1: print( - f"dftd3.from_arrays RESOLVED: dashlevel={dashleveleff}, dashparams={disp_params}, fctldash={fctldasheff}, dashparams_citation={citeff}" + f"empirical_dispersion_resources.from_arrays RESOLVED: dashlevel={dashleveleff}, dashparams={disp_params}, fctldash={fctldasheff}, dashparams_citation={citeff}" ) return { diff --git a/qcengine/programs/tests/test_canonical_config.py b/qcengine/programs/tests/test_canonical_config.py index e1a276a92..192a4ea7b 100644 --- a/qcengine/programs/tests/test_canonical_config.py +++ b/qcengine/programs/tests/test_canonical_config.py @@ -102,7 +102,7 @@ def test_local_options_memory_gib(program, model, keywords, memory_trickery, req # << Run inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=use_keywords) - ret = qcng.compute(inp, program, raise_error=True, local_options=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) pprint.pprint(ret.dict(), width=200) assert ret.success is True @@ -166,7 +166,7 @@ def test_local_options_scratch(program, model, keywords): # << Run inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) - ret = qcng.compute(inp, program, raise_error=True, local_options=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) pprint.pprint(ret.dict(), width=200) assert ret.success is True @@ -243,7 +243,7 @@ def test_local_options_ncores(program, model, keywords, ncores): # << Run inp = AtomicInput(molecule=molecule, driver="energy", model=model, keywords=keywords) - ret = qcng.compute(inp, program, raise_error=True, local_options=config.dict()) + ret = qcng.compute(inp, program, raise_error=True, task_config=config.dict()) 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 ba5c1321c..ee8c2e782 100644 --- a/qcengine/programs/tests/test_dftd3_mp2d.py +++ b/qcengine/programs/tests/test_dftd3_mp2d.py @@ -1049,7 +1049,8 @@ def test_dftd3_error(): zip( dmm, [ - np.array( + 0.5 + * np.array( [ [ 0.00000000e00, @@ -1397,7 +1398,7 @@ def eneyne_ne_qcschemamols(): db3lypd3bj = { - "dashlevel": "d3bj", + "dashlevel": "d3bj2b", "dashparams": {"s8": 1.9889, "s6": 1.0, "a2": 4.4211, "a1": 0.3981}, "dashparams_citation": "", "fctldash": "b3lyp-d3(bj)", @@ -1407,7 +1408,7 @@ def eneyne_ne_qcschemamols(): db3lypd3bjcustom["dashparams"]["a2"] = 5.4211 dpbed3zero = { - "dashlevel": "d3zero", + "dashlevel": "d3zero2b", "dashparams": {"s6": 1.0, "s8": 0.722, "sr6": 1.217, "sr8": 1.0, "alpha6": 14.0}, "dashparams_citation": "", "fctldash": "pbe-d3", @@ -1686,37 +1687,46 @@ def test_mp2d__run_mp2d__2body(inp, subjects, request): ids=["qmol", "pmol", "qcmol"], ) @pytest.mark.parametrize( - "inp", + "program, inp", # fmt: off [ - pytest.param({"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "d3-b3lyp-d"}, "keywords": {}}, "lbl": "B3LYP-D2"}, marks=using("dftd3")), - pytest.param({"parent": "eneyne", "subject": "mA", "qcsk": {"model": {"method": "d3-b3lyp-d3bj"}, "keywords": {}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("dftd3")), - pytest.param({"parent": "eneyne", "subject": "mB", "qcsk": {"model": { "method": "d3-PBE-D3zero"}, "keywords": {}}, "lbl": "PBE-D3"}, marks=using("dftd3")), - pytest.param({ "parent": "eneyne", "subject": "gAmB", "qcsk": { "model": { "method": "d3-PBE-D3zero"}, "keywords": {}}, "lbl": "PBE-D3"}, marks=using("dftd3")), - pytest.param({ "parent": "eneyne", "subject": "mAgB", "qcsk": { "model": { "method": "d3-PBE-D2"}, "keywords": {}}, "lbl": "PBE-D2"}, marks=using("dftd3")), - pytest.param({ "parent": "ne", "subject": "atom", "qcsk": { "model": { "method": "d3-b3lyp-d3bj"}, "keywords": {}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("dftd3")), - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "d3-SAPT0-D3M(BJ)"}, "keywords": {}}, "lbl": "SAPT0-D3M(BJ)"}, marks=using("dftd3")), - pytest.param({ "parent": "eneyne", "subject": "mA", "qcsk": { "model": { "method": "d3-SAPT0-D3M"}, "keywords": {}}, "lbl": "SAPT0-D3M"}, marks=using("dftd3")), - - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - pytest.param({ "parent": "ne", "subject": "atom", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - pytest.param({ "parent": "eneyne", "subject": "mA", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - pytest.param({ "parent": "eneyne", "subject": "gAmB", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - pytest.param({ "parent": "eneyne", "subject": "mB", "qcsk": { "model": { "method": "d4-PBE-D4"}, "keywords": {}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - pytest.param({ "parent": "eneyne", "subject": "mAgB", "qcsk": { "model": { "method": "d4-PBE-D4"}, "keywords": {}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), - - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # params only - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "b3lyp-d3"}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # method reinforcing params - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "pbe-d3"}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # method contradicting params (D3: params win) - - pytest.param({ "parent": "eneyne", "subject": "mAgB", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp_2body}}, "lbl": "B3LYP-D4(BJ)-2BODY"}, marks=using("dftd4")), # params only - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # params only - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "b3lyp-d4"}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method reinforcing params - pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": { "model": {"method": "pbe-d4"}, "keywords": {"pair_resolved": True, "level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method contradicting params (D4: method wins) + pytest.param("dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "d3-b3lyp-d"}, "keywords": {}}, "lbl": "B3LYP-D2"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "mA", "qcsk": {"model": {"method": "d3-b3lyp-d3bj"}, "keywords": {}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "mB", "qcsk": {"model": { "method": "d3-PBE-D3zero"}, "keywords": {}}, "lbl": "PBE-D3"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "gAmB", "qcsk": { "model": { "method": "d3-PBE-D3zero"}, "keywords": {}}, "lbl": "PBE-D3"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "mAgB", "qcsk": { "model": { "method": "d3-PBE-D2"}, "keywords": {}}, "lbl": "PBE-D2"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "ne", "subject": "atom", "qcsk": { "model": { "method": "d3-b3lyp-d3bj"}, "keywords": {}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "d3-SAPT0-D3M(BJ)"}, "keywords": {}}, "lbl": "SAPT0-D3M(BJ)"}, marks=using("dftd3")), + pytest.param("dftd3", {"parent": "eneyne", "subject": "mA", "qcsk": { "model": { "method": "d3-SAPT0-D3M"}, "keywords": {}}, "lbl": "SAPT0-D3M"}, marks=using("dftd3")), + + pytest.param("s-dftd3", {"parent": "eneyne", "subject": "mA", "qcsk": {"model": {"method": "d3-b3lyp-d3bj"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3bj"}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("s-dftd3")), + pytest.param("s-dftd3", {"parent": "eneyne", "subject": "mB", "qcsk": {"model": { "method": "d3-PBE-D3zero"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3zero"}}, "lbl": "PBE-D3"}, marks=using("s-dftd3")), + pytest.param("s-dftd3", {"parent": "eneyne", "subject": "gAmB", "qcsk": { "model": { "method": "d3-PBE-D3zero"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3"}}, "lbl": "PBE-D3"}, marks=using("s-dftd3")), + pytest.param("s-dftd3", {"parent": "ne", "subject": "atom", "qcsk": { "model": { "method": "d3-b3lyp-d3bj"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3bj"}}, "lbl": "B3LYP-D3(BJ)"}, marks=using("s-dftd3")), + pytest.param("s-dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "d3-SAPT0-D3M(BJ)"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3mbj"}}, "lbl": "SAPT0-D3M(BJ)"}, marks=using("s-dftd3")), + pytest.param("s-dftd3", {"parent": "eneyne", "subject": "mA", "qcsk": { "model": { "method": "d3-SAPT0-D3M"}, "keywords": {"apply_qcengine_aliases": True, "level_hint": "d3m"}}, "lbl": "SAPT0-D3M"}, marks=using("s-dftd3")), + + pytest.param("dftd4", {"parent": "eneyne", "subject": "dimer", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param("dftd4", {"parent": "ne", "subject": "atom", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param("dftd4", {"parent": "eneyne", "subject": "mA", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param("dftd4", {"parent": "eneyne", "subject": "gAmB", "qcsk": { "model": { "method": "d4-b3lyp-d4"}, "keywords": {}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param("dftd4", {"parent": "eneyne", "subject": "mB", "qcsk": { "model": { "method": "d4-PBE-D4"}, "keywords": {}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param("dftd4", {"parent": "eneyne", "subject": "mAgB", "qcsk": { "model": { "method": "d4-PBE-D4"}, "keywords": {}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + + pytest.param("dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # params only + pytest.param("dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "b3lyp-d3"}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # method reinforcing params + pytest.param("dftd3", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "pbe-d3"}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "B3LYP-D3"}, marks=using("dftd3")), # method contradicting params (D3: params win) + + pytest.param("dftd4", {"parent": "eneyne", "subject": "mAgB", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp_2body}}, "lbl": "B3LYP-D4(BJ)-2BODY"}, marks=using("dftd4")), # params only + pytest.param("dftd4", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # params only + pytest.param("dftd4", {"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "b3lyp-d4"}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method reinforcing params + # LAB Nov 2022 - I think early on the D4 logic pattern was method wins when method and params_tweaks contradicted, and this is what the test below relies upon and the dftd4 from psi4 channel does (test passes). + # Later, dftd4 clarified logic by calling contradiction an error, so below with dftd4 from c-f channel returns B3LYP values. Disabling test. + # pytest.param({ "parent": "eneyne", "subject": "dimer", "qcsk": { "model": {"method": "pbe-d4"}, "keywords": {"pair_resolved": True, "level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method contradicting params (D4: method wins) ], # fmt: on ) -def test_dftd3__run_dftd3__2body(inp, subjects, request): +def test_dftd3__run_dftd3__2body(inp, program, subjects, request): subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] gexpected = gref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1726,8 +1736,6 @@ def test_dftd3__run_dftd3__2body(inp, subjects, request): else: mol = subject.to_schema(dtype=2) - program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( molecule=mol, driver="gradient", @@ -1777,7 +1785,8 @@ def test_dftd3__run_dftd3__2body(inp, subjects, request): pytest.param({"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "pbe-d3"}, "keywords": {"level_hint": "d3", "params_tweaks": _d3_b3lyp}}, "lbl": "PBE-D3"}, marks=using("dftd3")), # method contradicting params (D3: params win -> fail) pytest.param({"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": ""}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # wrong ref lbl - pytest.param({"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "pbe-d4"}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method contradicting params (D4: method wins -> fail) + # LAB Nov 2022 - dftd4 from different channels (psi4, c-f) producing contrary behavior. TODO + # pytest.param({"parent": "eneyne", "subject": "dimer", "qcsk": {"model": {"method": "pbe-d4"}, "keywords": {"level_hint": "d4", "params_tweaks": _d4_b3lyp}}, "lbl": "B3LYP-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), # method contradicting params (D4: method wins -> fail) ], # fmt: on ) @@ -1878,13 +1887,21 @@ def test_dftd3__run_dftd3__3body(inp, subjects, request): ids=["qmol", "pmol", "qcmol"], ) @pytest.mark.parametrize( - "inp", + "inp, extrakw, program", [ - pytest.param({"parent": "eneyne", "subject": "dimer", "lbl": "SAPT0-D3M"}, marks=using("dftd3")), - pytest.param({"parent": "eneyne", "subject": "dimer", "lbl": "PBE-D4(BJ,EEQ)ATM"}, marks=using("dftd4")), + pytest.param({"parent": "eneyne", "subject": "dimer", "lbl": "SAPT0-D3M"}, {}, "dftd3", marks=using("dftd3")), + pytest.param( + {"parent": "eneyne", "subject": "dimer", "lbl": "SAPT0-D3M"}, + {"apply_qcengine_aliases": True, "level_hint": "d3m"}, + "s-dftd3", + marks=using("s-dftd3"), + ), + pytest.param( + {"parent": "eneyne", "subject": "dimer", "lbl": "PBE-D4(BJ,EEQ)ATM"}, {}, "dftd4", marks=using("dftd4") + ), ], ) -def test_sapt_pairwise(inp, subjects, request): +def test_sapt_pairwise(inp, program, extrakw, subjects, request): subject = subjects()[inp["parent"]][inp["subject"]] expected = ref[inp["parent"]][inp["lbl"]][inp["subject"]] expected_pairwise = pref[inp["parent"]][inp["lbl"]][inp["subject"]] @@ -1894,14 +1911,13 @@ def test_sapt_pairwise(inp, subjects, request): else: mol = subject.to_schema(dtype=2) - program = "dftd4" if ("D4(BJ" in inp["lbl"]) else "dftd3" - atin = AtomicInput( molecule=mol, driver="energy", model={"method": inp["lbl"]}, keywords={ "pair_resolved": True, + **extrakw, }, ) jrec = qcng.compute(atin, program, raise_error=True) diff --git a/qcengine/programs/tests/test_dftd4.py b/qcengine/programs/tests/test_dftd4.py index fdd88d061..e3ac1f523 100644 --- a/qcengine/programs/tests/test_dftd4.py +++ b/qcengine/programs/tests/test_dftd4.py @@ -36,7 +36,7 @@ def test_dftd4_task_b97m_m01(): @using("dftd4") def test_dftd4_task_tpss_m02(): - thr = 1.0e-8 + thr = 2.0e-8 return_result = np.array( [