Skip to content

Commit 96d8b5f

Browse files
loriabLori A. Burns
andauthored
dftd3 upstream atm & xtb harness updates (#500)
* classic dftd3 * adcc: older psi for newer qcmb * fixes for s-dftd3 atm default change * fix psi4 dftd3/version * atm=False * adapt xtb --------- Co-authored-by: Lori A. Burns <lori.burns7@gmail.com>
1 parent 9cac98f commit 96d8b5f

7 files changed

Lines changed: 203 additions & 54 deletions

File tree

.github/workflows/CI.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ jobs:
134134
runs-on: windows-latest
135135
pytest: "-k 'not (hes2 or qchem or test_config)'"
136136

137+
- conda-env: opt-disp-v1
138+
python-version: 3.12
139+
label: dftd-v1
140+
runs-on: ubuntu-latest
141+
pytest: ""
142+
137143
# 3.13
138144
#- conda-env: disp-win
139145
# python-version: 3.12

devtools/conda-envs/opt-disp-cf.yaml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ dependencies:
77
- rdkit
88
# - rdkit<=2025.03.4 # Windows+psi4 error with 03.5 # sometimes fine, sometimes flaky even w/exactly same packages in env
99
- mopac
10-
- libint=2.9.0=*_4 # old (1.10=*_2) psi4 to release qcmb constraint to use new qcnb (0.7) with new qcel (rc4)
1110

1211
# Mixed Tests
13-
- dftd3-python
14-
- dftd4-python
12+
- dftd3-python>=1.3
13+
- dftd4-python>=4.1
1514
- gcp-correction
1615
- geometric=1.0
1716
- optking
1817
- pymdi
19-
- qcmanybody>=0.7.0
2018
- pyberny
2119
- setuptools <82.0 # for berny
2220

@@ -35,3 +33,7 @@ dependencies:
3533
- pytest-cov
3634
- codecov
3735

36+
- pip
37+
- pip:
38+
- qcmanybody==0.7.0
39+
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: test
2+
channels:
3+
- conda-forge
4+
- defaults
5+
dependencies:
6+
- psi4=1.9.1
7+
- libint=2.9.0=*_4
8+
- rdkit
9+
- mopac
10+
- pydantic-settings
11+
12+
# Mixed Tests
13+
- dftd3-python<1.3
14+
- dftd4-python<4.1
15+
- gcp-correction
16+
- geometric
17+
- optking
18+
- pymdi
19+
- setuptools <82.0 # for berny
20+
21+
# Core
22+
- python
23+
- pyyaml
24+
- py-cpuinfo
25+
- psutil
26+
- conda-forge/label/qcelemental_dev::qcelemental
27+
- msgpack-python
28+
29+
# Testing
30+
- pytest
31+
- pytest-cov
32+
- codecov
33+
34+
- pip
35+
- pip:
36+
- pyberny

docs/source/changelog.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,40 @@ Changelog
3232
.. - UNSOLVED (:issue:`397`) extras failed
3333
3434
35+
.. _`sec:cl0500rc4`:
36+
37+
v0.50.0rc4 / 2026-MM-DD (Prerelease) (Unreleased)
38+
--------------------
39+
40+
:docs:`v0.50.0rc4` for current. :docs:`v0.34.1` for QCSchema v1.
41+
42+
Breaking Changes
43+
++++++++++++++++
44+
- (:pr:`500`) s-dftd3 - For simple-dftd3 >=1.3.0, 3-body is now off by default
45+
(s9=0.0) through the python interface used by the QCSchema interface and by
46+
QCEngine. Results will change if you're not passing ``params_tweaks`` or using
47+
``apply_qcengine_aliases=True`` (see below). Pass "atm=True" and "method" to
48+
re-impose s9=1.0 as in test case ``test_dftd3_task_pbe_m02``. @loriab
49+
50+
New Features
51+
++++++++++++
52+
53+
Enhancements
54+
++++++++++++
55+
- (:pr:`500`) s-dftd3 - When ``apply_qcengine_aliases=True`` for a 3-body-including
56+
dispersion correction (which includes unspecified, e.g. d3bj) the parameters are
57+
now explicitly passed as params_tweaks. There is no change in results, but this
58+
compensates for a change in default in the simple-dftd3 v1.3.0 project. Also,
59+
for 2-body-only, the returned "info" parameters (namely, s9) will more accurately
60+
match those used. @loriab
61+
62+
Bug Fixes
63+
+++++++++
64+
65+
Misc.
66+
+++++
67+
68+
3569
.. _`sec:cl0500rc3`:
3670

3771
v0.50.0rc3 / 2026-04-02 (Prerelease)

qcengine/programs/dftd_ng.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult:
265265
qcvkey = method.upper() if method is not None else None
266266

267267
# send `from_arrays` the s-dftd3 behavior of functional specification overrides explicit parameters specification
268-
# * differs from dftd3 harness behavior where parameters extend or override functional
268+
# * differs from classic-dftd3 harness behavior where parameters extend or override functional
269269
# * stash the resolved plan in extras or, if errored, leave it for the proper dftd3 api to reject
270270
param_tweaks = None if method else input_model.specification.keywords.get("params_tweaks", None)
271271
try:
@@ -294,13 +294,16 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult:
294294
level_hint = get_dispersion_aliases()[level_hint.lower()]
295295
if level_hint.endswith("atm"):
296296
level_hint = level_hint[:-3]
297+
# re-route through params_tweaks needed for >=1.3.0 where atm=False became default
298+
input_data["specification"]["keywords"]["params_tweaks"] = {**planinfo["dashparams"]}
297299
if level_hint.endswith("2b"):
298300
level_hint = level_hint[:-2]
299301
input_data["specification"]["keywords"]["params_tweaks"] = {**planinfo["dashparams"], "s9": 0.0}
302+
input_data["specification"]["extras"]["info"]["dashparams"]["s9"] = 0.0
300303
input_data["specification"]["keywords"]["level_hint"] = level_hint
301304

302305
if parse_version(self.get_version()) < parse_version("1.3.0"):
303-
# sdftd3 speaks qcsk.v1
306+
# s-dftd3 speaks qcsk.v1
304307
input_model_v1 = qcelemental.models.v2.AtomicInput(**input_data).convert_v(1)
305308

306309
# Run the Harness
@@ -316,7 +319,7 @@ def compute(self, input_model: AtomicInput, config: TaskConfig) -> AtomicResult:
316319
output = output_v1.convert_v(2, external_input_data=input_data)
317320

318321
else:
319-
# sdftd3 >1.3.0??? speaks qcsk.v1 or qcsk.v2
322+
# s-dftd3 >1.3.0 speaks qcsk.v1 or qcsk.v2
320323
input_model_v2 = qcelemental.models.v2.AtomicInput(**input_data)
321324

322325
# Run the Harness

qcengine/programs/tests/test_sdftd3.py

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717

1818
@uusing("s-dftd3")
1919
def test_dftd3_task_b97m_m01(schema_versions, request):
20+
import dftd3
21+
2022
models, retver, _ = schema_versions
2123

2224
thr = 1.0e-8
2325

2426
return_result = -0.05879001214961249
2527

28+
if qcel.util.parse_version(dftd3.__version__) >= qcel.util.parse_version("1.3.0"):
29+
atm_correction = 6.48732559e-05
30+
return_result -= atm_correction
31+
2632
if from_v2(request.node.name):
2733
atomic_input = models.AtomicInput(
2834
molecule=models.Molecule(**qcng.get_molecule("mindless-01", return_dict=True)),
@@ -45,6 +51,7 @@ def test_dftd3_task_b97m_m01(schema_versions, request):
4551

4652

4753
@uusing("s-dftd3")
54+
@pytest.mark.parametrize("atm", ["nil", True, False])
4855
@pytest.mark.parametrize(
4956
"inp",
5057
[
@@ -56,29 +63,44 @@ def test_dftd3_task_b97m_m01(schema_versions, request):
5663
],
5764
ids=["d3bj", "d3zero", "d3mbj", "d3mzero", "d3op"],
5865
)
59-
def test_dftd3_task_pbe_m02(inp, schema_versions, request):
66+
def test_dftd3_task_pbe_m02(inp, schema_versions, request, atm):
6067
models, retver, _ = schema_versions
6168

6269
# return to 1.0e-8 after https://github.com/MolSSI/QCEngine/issues/370
6370
thr = 1.0e-7
6471

6572
return_result = inp["return_result"]
6673

74+
# s-dftd3 v1.3 changed the 3-body default to False to match the executable interface.
75+
# Toggle is keywords["params_tweaks"]["atm"] which isn't handled by the harness.
76+
# Here, demonstrating pass-through to s-dftd3 1st-class QCSchema interface. Requires repeating method from model.
77+
atm_correction = 8.94882569e-05
78+
if atm == "nil":
79+
import dftd3
80+
81+
xtra_kw = {}
82+
if qcel.util.parse_version(dftd3.__version__) >= qcel.util.parse_version("1.3.0"):
83+
return_result -= atm_correction
84+
else:
85+
xtra_kw = {"params_tweaks": {"method": "pbe", "atm": atm}}
86+
if atm is False:
87+
return_result -= atm_correction
88+
89+
spec = {
90+
"model": {"method": "pbe"},
91+
"keywords": {"level_hint": inp["level_hint"], **xtra_kw},
92+
"driver": "energy",
93+
}
94+
6795
if from_v2(request.node.name):
6896
atomic_input = models.AtomicInput(
6997
molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)),
70-
specification={
71-
"model": {"method": "pbe"},
72-
"keywords": {"level_hint": inp["level_hint"]},
73-
"driver": "energy",
74-
},
98+
specification=spec,
7599
)
76100
else:
77101
atomic_input = models.AtomicInput(
78102
molecule=models.Molecule(**qcng.get_molecule("mindless-02", return_dict=True)),
79-
model={"method": "pbe"},
80-
keywords={"level_hint": inp["level_hint"]},
81-
driver="energy",
103+
**spec,
82104
)
83105

84106
atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre")
@@ -156,47 +178,81 @@ def test_dftd3_task_tpss_m02(schema_versions, request):
156178

157179

158180
@uusing("s-dftd3")
159-
def test_dftd3_task_r2scan_m03(schema_versions, request):
181+
@pytest.mark.parametrize("atm", ["nil", True, False])
182+
def test_dftd3_task_r2scan_m03(schema_versions, request, atm):
160183
models, retver, _ = schema_versions
161184

162185
thr = 1.0e-8
163186

164-
return_result = np.array(
165-
[
166-
[-9.15721221e-06, -8.18139252e-06, -6.04628002e-05],
167-
[+2.63353857e-05, -5.74347326e-05, +6.43925910e-05],
168-
[-2.99422439e-05, +1.88531187e-05, +3.80203831e-05],
169-
[-1.05590494e-05, -6.00459729e-05, +3.93481945e-05],
170-
[+1.59600548e-05, +3.66166973e-05, -2.69939628e-05],
171-
[-7.21060928e-05, +1.35991320e-05, -3.71000739e-05],
172-
[-9.01933781e-06, -3.41989101e-05, -4.92317946e-05],
173-
[-2.38625512e-06, -4.42678339e-05, -1.95513968e-05],
174-
[-3.09663159e-05, +3.41418638e-05, +2.51926884e-05],
175-
[+5.60572318e-06, -1.06356845e-05, -3.91159008e-05],
176-
[+7.73090102e-05, +2.75681060e-05, +2.02933984e-05],
177-
[-3.23109274e-05, +5.39319105e-05, -1.44309772e-04],
178-
[-4.05785623e-05, +5.03251549e-05, +3.92193348e-05],
179-
[+8.46365844e-06, -3.35282588e-05, +3.76937643e-05],
180-
[+1.04861677e-04, -2.99444252e-05, +8.20822571e-05],
181-
[-1.50951292e-06, +4.32012272e-05, +3.05230899e-05],
182-
]
183-
)
187+
return_result = {
188+
# s9 = 0.0
189+
False: np.array(
190+
[
191+
[-9.35558196e-06, -7.34067064e-06, -5.91238608e-05],
192+
[2.53304403e-05, -5.73248858e-05, 6.46059022e-05],
193+
[-2.85980097e-05, 1.93908141e-05, 3.67399397e-05],
194+
[-7.30494324e-06, -5.48095855e-05, 3.76154049e-05],
195+
[1.07471133e-05, 3.03306726e-05, -2.46209798e-05],
196+
[-6.64928435e-05, 1.44200457e-05, -3.64523447e-05],
197+
[-7.88327389e-06, -3.03833956e-05, -4.75808422e-05],
198+
[-1.90420772e-06, -4.31459317e-05, -2.02338533e-05],
199+
[-3.27860042e-05, 3.68088235e-05, 2.55372601e-05],
200+
[4.41178314e-06, -1.11489999e-05, -4.32651442e-05],
201+
[7.61947667e-05, 2.64655830e-05, 2.10390818e-05],
202+
[-3.38080082e-05, 5.29154102e-05, -1.44995850e-04],
203+
[-3.85124417e-05, 4.82248838e-05, 3.75602755e-05],
204+
[1.01670679e-05, -3.64563841e-05, 4.13102437e-05],
205+
[1.02281493e-04, -2.98543913e-05, 8.22232550e-05],
206+
[-2.48735050e-06, 4.19080117e-05, 2.96415119e-05],
207+
]
208+
),
209+
# s9 = 1.0
210+
True: np.array(
211+
[
212+
[-9.15721221e-06, -8.18139252e-06, -6.04628002e-05],
213+
[+2.63353857e-05, -5.74347326e-05, +6.43925910e-05],
214+
[-2.99422439e-05, +1.88531187e-05, +3.80203831e-05],
215+
[-1.05590494e-05, -6.00459729e-05, +3.93481945e-05],
216+
[+1.59600548e-05, +3.66166973e-05, -2.69939628e-05],
217+
[-7.21060928e-05, +1.35991320e-05, -3.71000739e-05],
218+
[-9.01933781e-06, -3.41989101e-05, -4.92317946e-05],
219+
[-2.38625512e-06, -4.42678339e-05, -1.95513968e-05],
220+
[-3.09663159e-05, +3.41418638e-05, +2.51926884e-05],
221+
[+5.60572318e-06, -1.06356845e-05, -3.91159008e-05],
222+
[+7.73090102e-05, +2.75681060e-05, +2.02933984e-05],
223+
[-3.23109274e-05, +5.39319105e-05, -1.44309772e-04],
224+
[-4.05785623e-05, +5.03251549e-05, +3.92193348e-05],
225+
[+8.46365844e-06, -3.35282588e-05, +3.76937643e-05],
226+
[+1.04861677e-04, -2.99444252e-05, +8.20822571e-05],
227+
[-1.50951292e-06, +4.32012272e-05, +3.05230899e-05],
228+
]
229+
),
230+
}
231+
232+
if atm == "nil":
233+
import dftd3
234+
235+
xtra_kw = {}
236+
return_result = return_result[qcel.util.parse_version(dftd3.__version__) < qcel.util.parse_version("1.3.0")]
237+
else:
238+
xtra_kw = {"params_tweaks": {"method": "r2scan", "atm": atm}}
239+
return_result = return_result[atm]
240+
241+
spec = {
242+
"keywords": {"level_hint": "d3bj", **xtra_kw},
243+
"driver": "gradient",
244+
"model": {"method": "r2scan"},
245+
}
184246

185247
if from_v2(request.node.name):
186248
atomic_input = models.AtomicInput(
187249
molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)),
188-
specification={
189-
"keywords": {"level_hint": "d3bj"},
190-
"driver": "gradient",
191-
"model": {"method": "r2scan"},
192-
},
250+
specification=spec,
193251
)
194252
else:
195253
atomic_input = models.AtomicInput(
196254
molecule=models.Molecule(**qcng.get_molecule("mindless-03", return_dict=True)),
197-
keywords={"level_hint": "d3bj"},
198-
driver="gradient",
199-
model={"method": "r2scan"},
255+
**spec,
200256
)
201257

202258
atomic_input = checkver_and_convert(atomic_input, request.node.name, "pre")

qcengine/programs/xtb.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from typing import Any, ClassVar, Dict
1313

1414
from qcelemental.models.v2 import AtomicInput, AtomicResult, FailedOperation
15-
from qcelemental.util import safe_version, which_import
15+
from qcelemental.util import parse_version, safe_version, which_import
1616

1717
from ..config import TaskConfig
1818
from .model import ProgramHarness
@@ -65,13 +65,25 @@ def compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult:
6565
import xtb
6666
from xtb.qcschema.harness import run_qcschema
6767

68-
# Run the Harness
69-
input_data_v1 = input_data.convert_v(1)
70-
output_v1 = run_qcschema(input_data_v1)
68+
if parse_version(self.get_version()) < parse_version("26.1"):
69+
# xtb-python speaks qcsk.v1
7170

72-
# xtb qcschema interface stores error in Result model
73-
if not output_v1.success:
74-
return FailedOperation(input_data=input_data, error=output_v1.error.model_dump())
71+
# Run the Harness
72+
input_model_v1 = input_data.convert_v(1)
73+
output_v1 = run_qcschema(input_model_v1)
7574

76-
output = output_v1.convert_v(2, external_input_data=input_data)
77-
return output
75+
# xtb qcschema interface stores error in Result model
76+
if not output_v1.success:
77+
return FailedOperation(input_data=input_data, error=output_v1.error.model_dump())
78+
79+
output = output_v1.convert_v(2, external_input_data=input_data)
80+
return output
81+
82+
else:
83+
# xtb-python >26.1 speaks qcsk.v1 or qcsk.v2
84+
85+
# Run the Harness
86+
input_model_v2 = input_data
87+
output_v2 = run_qcschema(input_model_v2)
88+
89+
return output_v2

0 commit comments

Comments
 (0)