Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ jobs:
matrix:
os: [ubuntu-latest]
gcc_v: [10]
python_v: ['3.8', '3.9', '3.10', '3.11', '3.12']
python_v: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14']

env:
FC: gfortran
Expand All @@ -270,12 +270,21 @@ jobs:
- run: pip3 install coverage

- name: Install dependencies
if: ((matrix.python_v != '3.14') && (matrix.python_v != '3.13'))
uses: mamba-org/setup-micromamba@v1
with:
environment-file: assets/ci/python-env.yaml
create-args: |
python=${{ matrix.python_v }}

- name: Install dependencies
if: ((matrix.python_v == '3.14') || (matrix.python_v == '3.13'))
uses: mamba-org/setup-micromamba@v1
with:
environment-file: assets/ci/python-env-alt.yaml
create-args: |
python=${{ matrix.python_v }}

- name: Install GCC (OSX)
if: ${{ contains(matrix.os, 'macos') }}
run: |
Expand Down
16 changes: 14 additions & 2 deletions .github/workflows/wheel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,19 @@ jobs:
with:
python-version: '3.x'

- name: Set cibuildwheel cache path
run: echo "CIBW_CACHE_PATH=${{ github.workspace }}/.cibw-cache" >> $GITHUB_ENV

- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.cibw-cache
key: cibw-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.github/workflows/*.yml') }}
restore-keys: |
cibw-${{ runner.os }}-${{ runner.arch }}-
cibw-${{ runner.os }}-

- name: Install cibuildwheel
run: python -m pip install cibuildwheel==2.21.1
run: python -m pip install "cibuildwheel==2.23.4"

- name: Download sdist
uses: actions/download-artifact@v4
Expand All @@ -91,9 +102,10 @@ jobs:
- name: Build wheels
run: python -m cibuildwheel --output-dir . *.tar.gz
env:
CIBW_CACHE_PATH: ${{ github.workspace }}/.cibw-cache
CIBW_CONFIG_SETTINGS: setup-args="-Ds-dftd3:openmp=false"
CIBW_ARCHS: auto64
CIBW_SKIP: "{cp38-macosx_arm64,pp37-win_amd64,pp39-win_amd64,pp310-win_amd64}"
CIBW_SKIP: "{cp38-macosx_arm64,cp39-macosx_arm64,pp37-win_amd64,pp39-win_amd64,pp310-win_amd64}"
CIBW_BUILD_VERBOSITY: 1
CIBW_ARCHS_MACOS: arm64
CIBW_ENVIRONMENT_MACOS: >
Expand Down
20 changes: 20 additions & 0 deletions assets/ci/python-env-alt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: python
channels:
- conda-forge
dependencies:
- python
- pip
- python-build
- pkgconfig
- setuptools
- pytest
- pytest-cov
- coverage
- cffi
- numpy
- ase
- conda-forge/label/qcelemental_dev::qcelemental>=0.50.0rc3
- matplotlib-base
- meson >=1.2.3,!=1.8.0
- meson-python
- ninja
87 changes: 58 additions & 29 deletions python/dftd3/qcschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

Integration with the `QCArchive infrastructure <http://docs.qcarchive.molssi.org>`_.

If the QCElemental package is installed the ``dftd3.qcschema`` module becomes
importable and provides the ``run_qcschema`` function supporting QCSchema v1.
If the QCElemental package is >=0.50.0, ``dftd3.qcschema`` supports QCSchema v1
and v2, returning whichever version was submitted. Note that Python 3.14+ only
works with QCSchema v2 due to Pydantic restrictions.

This module provides a way to translate QCSchema or QCElemental Atomic Input
into a format understandable by the ``dftd3`` API which in turn provides the
calculation results in a QCSchema compatible format.
Expand Down Expand Up @@ -173,15 +179,29 @@


def run_qcschema(
input_data: Union[dict, qcel.models.AtomicInput]
) -> qcel.models.AtomicResult:
input_data: Union[dict, qcel.models.AtomicInput, "qcel.models.v2.AtomicInput"]
) -> Union[qcel.models.AtomicResult, "qcel.models.v2.AtomicResult"]:
"""Perform disperson correction based on an atomic inputmodel"""

if not isinstance(input_data, qcel.models.AtomicInput):
atomic_input = qcel.models.AtomicInput(**input_data)
else:
v2_available = hasattr(qcel.models, "v2")

if v2_available and isinstance(input_data, qcel.models.v2.AtomicInput):
atomic_input = input_data
ret_data = atomic_input.dict()
elif isinstance(input_data, qcel.models.AtomicInput):
atomic_input = input_data
elif v2_available and input_data.get("specification"):
atomic_input = qcel.models.v2.AtomicInput(**input_data)
else:
atomic_input = qcel.models.AtomicInput(**input_data)

if (schver := atomic_input.schema_version) == 1:
from qcelemental.models import AtomicInput, AtomicResult, ComputeError

ret_data = atomic_input.dict()
elif schver == 2:
from qcelemental.models.v2 import AtomicInput, AtomicResult, ComputeError, FailedOperation

ret_data = {"input_data": atomic_input, "extras": {}, "molecule": atomic_input.molecule}

provenance = {
"creator": "s-dftd3",
Expand All @@ -194,32 +214,37 @@ def run_qcschema(

# Since it is a level hint we a forgiving if it is not present,
# we are much less forgiving if the wrong level is hinted here.
_level = atomic_input.keywords.get("level_hint", "d3bj")
atin_keywords = atomic_input.keywords if schver == 1 else atomic_input.specification.keywords
_level = atin_keywords.get("level_hint", "d3bj")
if _level.lower() not in _available_levels:
ret_data.update(
provenance=provenance,
success=success,
properties=properties,
return_result=return_result,
error=qcel.models.ComputeError(
error_type="input error",
error_message="Level '{}' is invalid for this dispersion correction".format(
_level
),
error=ComputeError(
error_type="input error",
error_message="Level '{}' is invalid for this dispersion correction".format(
_level
),
)
return qcel.models.AtomicResult(**ret_data)
if schver == 1:
ret_data.update(
provenance=provenance,
success=success,
properties=properties,
return_result=return_result,
error=error,
)
return AtomicResult(**ret_data)
elif schver == 2:
return FailedOperation(input_data=atomic_input, error=error)

# Check if the method is provided and strip the “dashlevel” from the method
_method = atomic_input.model.method.split("-")
_method = atomic_input.model.method.split("-") if schver == 1 else atomic_input.specification.model.method.split("-")
if _method[-1].lower().translate(_clean_dashlevel) == _level.lower():
_method.pop()
_method = "-".join(_method)
if len(_method) == 0:
_method = None

# Obtain the parameters for the damping function
_input_param = atomic_input.keywords.get("params_tweaks", {"method": _method})
_input_param = atin_keywords.get("params_tweaks", {"method": _method})

try:
param = _damping_param[_level](
Expand All @@ -231,13 +256,14 @@ def run_qcschema(
atomic_input.molecule.geometry[atomic_input.molecule.real],
)

driver = atomic_input.driver if schver == 1 else atomic_input.specification.driver
res = disp.get_dispersion(
param=param,
grad=atomic_input.driver == "gradient",
grad=driver == "gradient",
)
extras = {"dftd3": res}

if atomic_input.driver == "gradient":
if driver == "gradient":
if all(atomic_input.molecule.real):
fullgrad = res.get("gradient")
else:
Expand All @@ -247,18 +273,18 @@ def run_qcschema(

properties.update(return_energy=res.get("energy"))

if atomic_input.keywords.get("pair_resolved", False):
if atin_keywords.get("pair_resolved", False):
res = disp.get_pairwise_dispersion(param=param)
extras["dftd3"].update(res)

success = atomic_input.driver in _supported_drivers
if atomic_input.driver == "energy":
success = driver in _supported_drivers
if driver == "energy":
return_result = properties["return_energy"]
elif atomic_input.driver == "gradient":
elif driver == "gradient":
return_result = fullgrad
else:
ret_data.update(
error=qcel.models.ComputeError(
error=ComputeError(
error_type="input error",
error_message="Calculation succeeded but invalid driver request provided",
),
Expand All @@ -268,7 +294,7 @@ def run_qcschema(

except (RuntimeError, TypeError) as e:
ret_data.update(
error=qcel.models.ComputeError(
error=ComputeError(
error_type="input error", error_message=str(e)
),
),
Expand All @@ -280,4 +306,7 @@ def run_qcschema(
return_result=return_result,
)

return qcel.models.AtomicResult(**ret_data)
if schver == 2 and "error" in ret_data:
return FailedOperation(input_data=atomic_input, error=ret_data["error"])

return AtomicResult(**ret_data)
Loading
Loading