From 46c74cfe16509d426745d190a6becddce9be7425 Mon Sep 17 00:00:00 2001
From: Marvin Friede <51965259+marvinfriede@users.noreply.github.com>
Date: Sun, 16 Feb 2025 19:01:41 +0100
Subject: [PATCH 1/2] Improve docs (#201)
---
docs/source/01_quickstart/getting_started.rst | 5 +-
docs/source/05_about/literature.rst | 2 +-
docs/source/index.rst | 2 +-
src/dxtb/_src/calculators/types/base.py | 17 ++++---
src/dxtb/_src/calculators/types/decorators.py | 19 ++++++--
src/dxtb/_src/calculators/utils.py | 1 +
src/dxtb/_src/cli/driver.py | 3 +-
src/dxtb/_src/components/base.py | 9 ++--
src/dxtb/_src/components/classicals/base.py | 6 +--
.../components/classicals/dispersion/d3.py | 5 +-
.../components/classicals/dispersion/d4.py | 4 +-
.../_src/components/classicals/halogen/hal.py | 19 +++++---
.../components/classicals/repulsion/base.py | 32 +++++++------
.../components/classicals/repulsion/rep.py | 4 +-
src/dxtb/_src/components/interactions/base.py | 6 +--
.../interactions/coulomb/secondorder.py | 24 +++++++---
.../interactions/coulomb/thirdorder.py | 2 +
.../interactions/dispersion/d4sc.py | 26 ++++++++++
src/dxtb/_src/constants/xtb.py | 48 -------------------
.../_src/integral/driver/pytorch/dipole.py | 3 +-
.../integral/driver/pytorch/quadrupole.py | 3 +-
src/dxtb/_src/param/dispersion.py | 31 ++++++------
src/dxtb/calculators.py | 3 +-
src/dxtb/integrals/__init__.py | 4 +-
24 files changed, 150 insertions(+), 128 deletions(-)
diff --git a/docs/source/01_quickstart/getting_started.rst b/docs/source/01_quickstart/getting_started.rst
index 861f33d2..a81ab500 100644
--- a/docs/source/01_quickstart/getting_started.rst
+++ b/docs/source/01_quickstart/getting_started.rst
@@ -73,7 +73,8 @@ use corresponding getters :meth:`~dxtb.Calculator.get_energy`:
energy = calc.get_energy(positions, charge=0, spin=0)
-We recommend using the getters, as they provide the familiar ASE-like interface.
+We recommend using the getters, as they provide the familiar
+`ASE `_-like interface.
Gradients
@@ -195,4 +196,4 @@ counterpart, e.g., :meth:`~dxtb.Calculator.forces_numerical`.
Hence, a subsequent :meth:`~dxtb.Calculator.get_forces` does not
necessitate an additional calculation.
-For more details, please see the :ref:`here `.
+For more details, please see :ref:`here `.
diff --git a/docs/source/05_about/literature.rst b/docs/source/05_about/literature.rst
index 29dff8ce..91c7ae17 100644
--- a/docs/source/05_about/literature.rst
+++ b/docs/source/05_about/literature.rst
@@ -8,7 +8,7 @@ This is a list of literature that is relevant to the project.
Main Reference
--------------
-- M. Friede, C. Hölzer, S. Ehlert, S. Grimme, *J. Chem. Phys.*, **2024**, 161, 062501. ([DOI](https://doi.org/10.1063/5.0216715))
+- M. Friede, C. Hölzer, S. Ehlert, S. Grimme, *J. Chem. Phys.*, **2024**, 161, 062501. (`DOI: 10.1063/5.0216715 `__)
.. code-block:: bibtex
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 0b80f7a0..c1a62e2e 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -70,7 +70,7 @@ With *dxtb*, we provide a re-implementation of the xTB methods in PyTorch, which
If you use *dxtb* in your research, please cite the following paper:
-- M. Friede, C. Hölzer, S. Ehlert, S. Grimme, *dxtb -- An Efficient and Fully Differentiable Framework for Extended Tight-Binding*, *J. Chem. Phys.*, **2024**
+- M. Friede, C. Hölzer, S. Ehlert, S. Grimme, *dxtb -- An Efficient and Fully Differentiable Framework for Extended Tight-Binding*, *J. Chem. Phys.*, **2024**, 161, 062501. (`DOI: 10.1063/5.0216715 `__)
.. admonition:: BibTeX
:class: toggle
diff --git a/src/dxtb/_src/calculators/types/base.py b/src/dxtb/_src/calculators/types/base.py
index 50154e88..22639e28 100644
--- a/src/dxtb/_src/calculators/types/base.py
+++ b/src/dxtb/_src/calculators/types/base.py
@@ -496,8 +496,6 @@ def __init__(
opts = dict(opts)
OutputHandler.verbosity = opts.pop("verbosity", 1)
- OutputHandler.write_stdout("", v=5)
- OutputHandler.write_stdout("", v=5)
OutputHandler.write_stdout("===========", v=4)
OutputHandler.write_stdout("CALCULATION", v=4)
OutputHandler.write_stdout("===========", v=4)
@@ -523,15 +521,22 @@ def __init__(
# Set integral level based on parametrization. For the tests, we want
# to turn this off. Otherwise, all GFN2-xTB tests will fail without
- # `libcint`, even when integrals are not tested (e.g. D4SC). This is
- # caused by the integral factories in this very constructor.
+ # `libcint`, even when integrals are not tested (e.g. D4SC). This
+ # cannot be avoided becaused this constructor always calls the
+ # integral factories later.
+ # If the user sets the level manually, we will still set it to the
+ # maximum level required for the respective parametrization.
if kwargs.pop("auto_int_level", True):
if par.meta is not None:
if par.meta.name is not None:
if "gfn1" in par.meta.name.casefold():
- self.opts.ints.level = labels.INTLEVEL_HCORE
+ self.opts.ints.level = max(
+ labels.INTLEVEL_HCORE, self.opts.ints.level
+ )
elif "gfn2" in par.meta.name.casefold():
- self.opts.ints.level = labels.INTLEVEL_QUADRUPOLE
+ self.opts.ints.level = max(
+ labels.INTLEVEL_QUADRUPOLE, self.opts.ints.level
+ )
# create cache
self.cache = CalculatorCache(**dd) if cache is None else cache
diff --git a/src/dxtb/_src/calculators/types/decorators.py b/src/dxtb/_src/calculators/types/decorators.py
index 4355c44b..5ff98e21 100644
--- a/src/dxtb/_src/calculators/types/decorators.py
+++ b/src/dxtb/_src/calculators/types/decorators.py
@@ -94,8 +94,13 @@ def wrapper(
) -> Tensor:
if efield.LABEL_EFIELD not in self.interactions.labels:
raise RuntimeError(
- f"{func.__name__} requires an electric field. Add the "
- f"'{efield.LABEL_EFIELD}' interaction to the Calculator."
+ f"'{func.__name__}' requires an electric field."
+ f"\nAdd the '{efield.LABEL_EFIELD}' interaction to the "
+ "Calculator.\n\nExample:\n"
+ "field = torch.tensor([0.0, 0.0, 0.0], **dd)\n"
+ "ef = dxtb.components.field.new_efield(field, **dd)\n"
+ "calc = dxtb.calculators.GFN1Calculator(numbers, "
+ "interaction=ef, **dd)"
)
return func(self, positions, chrg, spin, *args, **kwargs)
@@ -134,9 +139,13 @@ def wrapper(
) -> Tensor:
if efieldgrad.LABEL_EFIELD_GRAD not in self.interactions.labels:
raise RuntimeError(
- f"{func.__name__} requires an electric field. Add the "
- f"'{efieldgrad.LABEL_EFIELD_GRAD}' interaction to the "
- "Calculator."
+ f"'{func.__name__}' requires an electric field gradient."
+ f"\nAdd the '{efieldgrad.LABEL_EFIELD_GRAD}' interaction to "
+ "the Calculator.\n\nExample:\n"
+ "field_grad = torch.zeros((3, 3), **dd)\n"
+ "efg = dxtb.components.field.new_efield_grad(field_grad, **dd)"
+ "\ncalc = dxtb.calculators.GFN1Calculator(numbers, "
+ "interaction=efg, **dd)"
)
return func(self, positions, chrg, spin, *args, **kwargs)
diff --git a/src/dxtb/_src/calculators/utils.py b/src/dxtb/_src/calculators/utils.py
index 8267fcfc..2adefb0d 100644
--- a/src/dxtb/_src/calculators/utils.py
+++ b/src/dxtb/_src/calculators/utils.py
@@ -60,6 +60,7 @@ def shape_checks_chrg(
f"{name.title()} tensor has only one element. Please use a "
"scalar for single structures (e.g., `torch.tensor(1.0)`) and "
"a 1D tensor for batched calculations (e.g., "
+ "`torch.tensor([1.0, 0.0])`)."
)
if ndims != t.ndim + 1:
diff --git a/src/dxtb/_src/cli/driver.py b/src/dxtb/_src/cli/driver.py
index e9357fb4..66117abe 100644
--- a/src/dxtb/_src/cli/driver.py
+++ b/src/dxtb/_src/cli/driver.py
@@ -149,7 +149,8 @@ def singlepoint(self) -> Result | Tensor:
# setup config and write to output
config = Config.from_args(args)
- io.OutputHandler.write(config.info())
+ io.OutputHandler.write(config.info(), v=5)
+ io.OutputHandler.write_stdout("", v=5)
# Broyden is not supported in full SCF mode
if (
diff --git a/src/dxtb/_src/components/base.py b/src/dxtb/_src/components/base.py
index 9d862a13..5edf5ed5 100644
--- a/src/dxtb/_src/components/base.py
+++ b/src/dxtb/_src/components/base.py
@@ -130,7 +130,7 @@ def update(self, **kwargs: Any) -> None:
.. code-block:: python
import torch
- from dxtb._src.components.interactions.field import ElectricField
+ from dxtb.components.field import ElectricField
ef = ElectricField(field=torch.tensor([0.0, 0.0, 0.0]))
ef.update(field=torch.tensor([1.0, 0.0, 0.0]))
@@ -152,8 +152,9 @@ def update(self, **kwargs: Any) -> None:
def reset(self) -> None:
"""
- Reset the tensor attributes of the `Component` instance to their
- original states or to specified values.
+ Reset the tensor attributes of the
+ :class:`dxtb.components.base.Component` instance to their original
+ states or to specified values.
This method iterates through the attributes defined in ``__slots__`` and
resets any tensor attributes to a detached clone of their original
@@ -165,7 +166,7 @@ def reset(self) -> None:
.. code-block:: python
import torch
- from dxtb._src.components.interactions.external.field import ElectricField
+ from dxtb.components.base.field import ElectricField
ef = ElectricField(field=torch.tensor([0.0, 0.0, 0.0]))
ef.reset()
diff --git a/src/dxtb/_src/components/classicals/base.py b/src/dxtb/_src/components/classicals/base.py
index 00d66dd7..d3494970 100644
--- a/src/dxtb/_src/components/classicals/base.py
+++ b/src/dxtb/_src/components/classicals/base.py
@@ -21,9 +21,9 @@
This module contains the abstract base class for all classical (i.e., non-
selfconsistent or non-density-dependent) energy terms.
-Every contribution contains a class:`dxtb.components.ComponentCache` that holds
-position-independent variables. Therefore, the positions must always be
-supplied to the ``get_energy`` (or ``get_grad``) method.
+Every contribution contains a :class:`dxtb.components.base.ComponentCache` that
+holds position-independent variables. Therefore, the positions must always be
+supplied to the :meth:`get_energy` (or :meth:`get_grad``) method.
"""
from __future__ import annotations
diff --git a/src/dxtb/_src/components/classicals/dispersion/d3.py b/src/dxtb/_src/components/classicals/dispersion/d3.py
index 6dcc4a9b..3f3cedbc 100644
--- a/src/dxtb/_src/components/classicals/dispersion/d3.py
+++ b/src/dxtb/_src/components/classicals/dispersion/d3.py
@@ -80,7 +80,10 @@ def __init__(
class DispersionD3(Dispersion):
- """Representation of the DFT-D3(BJ) dispersion correction."""
+ """
+ Representation of the DFT-D3(BJ) dispersion correction
+ (:class:`.DispersionD3`).
+ """
@override
def get_cache(
diff --git a/src/dxtb/_src/components/classicals/dispersion/d4.py b/src/dxtb/_src/components/classicals/dispersion/d4.py
index af42778f..6c66087a 100644
--- a/src/dxtb/_src/components/classicals/dispersion/d4.py
+++ b/src/dxtb/_src/components/classicals/dispersion/d4.py
@@ -74,7 +74,9 @@ def __init__(
class DispersionD4(Dispersion):
- """Representation of the DFT-D4 dispersion correction."""
+ """
+ Representation of the DFT-D4 dispersion correction (:class:`.DispersionD4`).
+ """
charge: Tensor
"""Total charge of the system."""
diff --git a/src/dxtb/_src/components/classicals/halogen/hal.py b/src/dxtb/_src/components/classicals/halogen/hal.py
index c5661d64..d1b9cf13 100644
--- a/src/dxtb/_src/components/classicals/halogen/hal.py
+++ b/src/dxtb/_src/components/classicals/halogen/hal.py
@@ -27,6 +27,7 @@
import torch
from tad_mctc.batch import pack
+from tad_mctc.convert import any_to_tensor
from tad_mctc.data.radii import ATOMIC as ATOMIC_RADII
from dxtb import IndexHelper
@@ -87,8 +88,12 @@ class Halogen(Classical):
bond_strength: Tensor
"""Halogen bond strengths for unique species."""
- cutoff: Tensor
- """Real space cutoff for halogen bonding interactions (default: 20.0)."""
+ cutoff: Tensor | float | int
+ """
+ Real space cutoff for halogen bonding interactions.
+
+ :default: :data:`xtb.DEFAULT_XB_CUTOFF`
+ """
__slots__ = ["damp", "rscale", "bond_strength", "cutoff"]
@@ -97,16 +102,16 @@ def __init__(
damp: Tensor,
rscale: Tensor,
bond_strength: Tensor,
- cutoff: Tensor = torch.tensor(xtb.DEFAULT_XB_CUTOFF),
+ cutoff: Tensor | float | int = xtb.DEFAULT_XB_CUTOFF,
device: torch.device | None = None,
dtype: torch.dtype | None = None,
) -> None:
super().__init__(device, dtype)
- self.damp = damp.to(self.device).type(self.dtype)
- self.rscale = rscale.to(self.device).type(self.dtype)
- self.bond_strength = bond_strength.to(self.device).type(self.dtype)
- self.cutoff = cutoff.to(self.device).type(self.dtype)
+ self.damp = damp.to(**self.dd)
+ self.rscale = rscale.to(**self.dd)
+ self.bond_strength = bond_strength.to(**self.dd)
+ self.cutoff = any_to_tensor(cutoff, **self.dd)
# element numbers of halogens and bases
self.halogens = [17, 35, 53, 85]
diff --git a/src/dxtb/_src/components/classicals/repulsion/base.py b/src/dxtb/_src/components/classicals/repulsion/base.py
index c7821151..58c7548b 100644
--- a/src/dxtb/_src/components/classicals/repulsion/base.py
+++ b/src/dxtb/_src/components/classicals/repulsion/base.py
@@ -62,6 +62,7 @@
import torch
from tad_mctc import storch
from tad_mctc.batch import real_pairs
+from tad_mctc.convert import any_to_tensor
from dxtb import IndexHelper
from dxtb._src.constants import xtb
@@ -141,8 +142,12 @@ class BaseRepulsion(Classical):
the repulsion energy for light elements, i.e., H and He (only GFN2).
"""
- cutoff: float
- """Real space cutoff for repulsion interactions (default: 25.0)."""
+ cutoff: Tensor | float | int
+ """
+ Real space cutoff for repulsion interactions.
+
+ :default: :data:`xtb.DEFAULT_REPULSION_CUTOFF`
+ """
__slots__ = ["arep", "zeff", "kexp", "klight", "cutoff"]
@@ -152,20 +157,17 @@ def __init__(
zeff: Tensor,
kexp: Tensor,
klight: Tensor | None = None,
- cutoff: float = xtb.DEFAULT_REPULSION_CUTOFF,
+ cutoff: Tensor | float | int = xtb.DEFAULT_REPULSION_CUTOFF,
device: torch.device | None = None,
dtype: torch.dtype | None = None,
) -> None:
super().__init__(device, dtype)
- self.arep = arep.to(self.device).type(self.dtype)
- self.zeff = zeff.to(self.device).type(self.dtype)
- self.kexp = kexp.to(self.device).type(self.dtype)
- self.cutoff = cutoff
-
- if klight is not None:
- klight = klight.to(self.device).type(self.dtype)
- self.klight = klight
+ self.arep = arep.to(**self.dd)
+ self.zeff = zeff.to(**self.dd)
+ self.kexp = kexp.to(**self.dd)
+ self.cutoff = any_to_tensor(cutoff, **self.dd)
+ self.klight = None if klight is None else klight.to(**self.dd)
@override
def get_cache(
@@ -270,7 +272,7 @@ def repulsion_energy(
arep: Tensor,
kexp: Tensor,
zeff: Tensor,
- cutoff: float = xtb.DEFAULT_REPULSION_CUTOFF,
+ cutoff: Tensor | float | int = xtb.DEFAULT_REPULSION_CUTOFF,
) -> Tensor:
"""
Clasical repulsion energy.
@@ -288,8 +290,8 @@ def repulsion_energy(
of the repulsion energy.
zeff : Tensor
Effective nuclear charges.
- cutoff : float, optional
- Real-space cutoff. Defaults to `xtb.DEFAULT_REPULSION_CUTOFF`.
+ cutoff : Tensor | float | int, optional
+ Real-space cutoff. Defaults to :data:`xtb.DEFAULT_REPULSION_CUTOFF`.
Returns
-------
@@ -306,7 +308,7 @@ def repulsion_energy(
dtype=positions.dtype,
device=positions.device,
)
- _cutoff = torch.tensor(
+ _cutoff = any_to_tensor(
cutoff,
dtype=positions.dtype,
device=positions.device,
diff --git a/src/dxtb/_src/components/classicals/repulsion/rep.py b/src/dxtb/_src/components/classicals/repulsion/rep.py
index f5594f0f..6bad81c9 100644
--- a/src/dxtb/_src/components/classicals/repulsion/rep.py
+++ b/src/dxtb/_src/components/classicals/repulsion/rep.py
@@ -52,7 +52,9 @@
LABEL_REPULSION = "Repulsion"
-"""Label for the 'Repulsion' component, coinciding with the class name."""
+"""
+Label for the :class:`.Repulsion` component, coinciding with the class name.
+"""
class Repulsion(BaseRepulsion):
diff --git a/src/dxtb/_src/components/interactions/base.py b/src/dxtb/_src/components/interactions/base.py
index 69d0b9de..b64ca77b 100644
--- a/src/dxtb/_src/components/interactions/base.py
+++ b/src/dxtb/_src/components/interactions/base.py
@@ -274,9 +274,9 @@ def get_energy(
Note
----
- The subclasses of `Interaction` should implement the `get__energy`
- methods. If they are not implemented in the subclass, they will
- evaluate to zero.
+ The subclasses of :class:`dxtb.components.base.Interaction` should
+ implement the `get__energy` methods. If they are not implemented
+ in the subclass, they will evaluate to zero.
"""
if charges.mono is None:
raise RuntimeError(
diff --git a/src/dxtb/_src/components/interactions/coulomb/secondorder.py b/src/dxtb/_src/components/interactions/coulomb/secondorder.py
index 1bb5b3f4..8c711fa6 100644
--- a/src/dxtb/_src/components/interactions/coulomb/secondorder.py
+++ b/src/dxtb/_src/components/interactions/coulomb/secondorder.py
@@ -157,21 +157,31 @@ class ES2(Interaction):
lhubbard: Tensor | None
"""
- Shell-resolved scaling factors for Hubbard parameters (default: ``None``,
- i.e., no shell resolution).
+ Shell-resolved scaling factors for Hubbard parameters.
+
+ :default: ``None`` (i.e., no shell resolution).
"""
average: AveragingFunction
"""
- Function to use for averaging the Hubbard parameters (default:
- :func:`dxtb.components.interactions.coulomb.average.harmonic_average`).
+ Function to use for averaging the Hubbard parameters.
+
+ :default: :func:`dxtb._src.components.interactions.average.harmonic_average`
"""
gexp: Tensor
- """Exponent of the second-order Coulomb interaction (default: 2.0)."""
+ """
+ Exponent of the second-order Coulomb interaction.
+
+ :default: 2.0
+ """
shell_resolved: bool
- """Electrostatics is shell-resolved (default: ``True``)."""
+ """
+ Whether electrostatics is shell-resolved.
+
+ :default: ``True``
+ """
__slots__ = [
"hubbard",
@@ -792,7 +802,7 @@ def coulomb_matrix_shell(
Exponent of the second-order Coulomb interaction (default: 2.0).
average: AveragingFunction
Function to use for averaging the Hubbard parameters (default:
- :func:`dxtb.components.interactions.coulomb.average.harmonic_average`).
+ :func:`dxtb._src.components.interactions.average.harmonic_average`).
Returns
-------
diff --git a/src/dxtb/_src/components/interactions/coulomb/thirdorder.py b/src/dxtb/_src/components/interactions/coulomb/thirdorder.py
index 11d79f77..f97d5d4a 100644
--- a/src/dxtb/_src/components/interactions/coulomb/thirdorder.py
+++ b/src/dxtb/_src/components/interactions/coulomb/thirdorder.py
@@ -155,6 +155,8 @@ class ES3(Interaction):
In GFN2-xTB, this is a tensor of shape ``(3,)`` containing the scaling
factors for the s, p, and d shells.
+
+ :default: ``None``
"""
__slots__ = ["hubbard_derivs", "shell_scale"]
diff --git a/src/dxtb/_src/components/interactions/dispersion/d4sc.py b/src/dxtb/_src/components/interactions/dispersion/d4sc.py
index fa2d72fa..896fac8f 100644
--- a/src/dxtb/_src/components/interactions/dispersion/d4sc.py
+++ b/src/dxtb/_src/components/interactions/dispersion/d4sc.py
@@ -135,6 +135,32 @@ class DispersionD4SC(Interaction):
param: dict[str, Tensor]
"""Dispersion parameters."""
+ model: d4.model.D4Model
+ """Model for the D4 dispersion correction."""
+
+ rcov: Tensor
+ """Covalent radii of all atoms."""
+
+ r4r2: Tensor
+ """R4/R2 ratio of all atoms."""
+
+ cutoff: d4.cutoff.Cutoff
+ """Real-space cutoff for the D4 dispersion correction."""
+
+ counting_function: d4.typing.CountingFunction
+ """
+ Counting function for the coordination number.
+
+ :default: :func:`d4.ncoord.erf_count`
+ """
+
+ damping_function: d4.typing.DampingFunction
+ """
+ Damping function for the dispersion correction.
+
+ :default: :func:`d4.damping.rational_damping`
+ """
+
__slots__ = [
"param",
"model",
diff --git a/src/dxtb/_src/constants/xtb.py b/src/dxtb/_src/constants/xtb.py
index 2f7ea82f..28ebfe69 100644
--- a/src/dxtb/_src/constants/xtb.py
+++ b/src/dxtb/_src/constants/xtb.py
@@ -22,60 +22,12 @@
in the parametrization file. Furthermore, `xtb`'s default values are stored here.
"""
-
-# Coordination number
-
-KCN_EEQ = 7.5
-"""Steepness of counting function in EEQ model (7.5)."""
-
-KCN = 16.0
-"""GFN1: Steepness of counting function."""
-
-KA = 10.0
-"""GFN2: Steepness of first counting function."""
-
-KB = 20.0
-"""GFN2: Steepness of second counting function."""
-
-R_SHIFT = 2.0
-"""GFN2: Offset of the second counting function."""
-
-NCOORD_DEFAULT_CUTOFF = 25.0
-"""Default cutoff used for determining coordination number (25.0)."""
-
-
# Electrostatics
DEFAULT_ES2_GEXP: float = 2.0
"""Default exponent of the second-order Coulomb interaction (2.0)."""
-# Dispersion
-
-DEFAULT_DISP_A1 = 0.4
-"""Scaling for the C8 / C6 ratio in the critical radius (0.4)."""
-
-DEFAULT_DISP_A2 = 5.0
-"""Offset parameter for the critical radius (5.0)."""
-
-DEFAULT_DISP_S6 = 1.0
-"""Default scaling of dipole-dipole term (1.0 to retain correct limit)."""
-
-DEFAULT_DISP_S8 = 1.0
-"""Default scaling of dipole-quadrupole term (1.0)."""
-
-DEFAULT_DISP_S9 = 1.0
-"""Default scaling of three-body term (1.0)."""
-
-DEFAULT_DISP_S10 = 0.0
-"""Default scaling of quadrupole-quadrupole term (0.0)."""
-
-DEFAULT_DISP_RS9 = 4.0 / 3.0
-"""Scaling for van-der-Waals radii in damping function (4.0/3.0)."""
-
-DEFAULT_DISP_ALP = 16.0
-"""Exponent of zero damping function (16.0)."""
-
# Classical contributions
DEFAULT_XB_CUTOFF: float = 20.0
diff --git a/src/dxtb/_src/integral/driver/pytorch/dipole.py b/src/dxtb/_src/integral/driver/pytorch/dipole.py
index 53ef142a..01032f90 100644
--- a/src/dxtb/_src/integral/driver/pytorch/dipole.py
+++ b/src/dxtb/_src/integral/driver/pytorch/dipole.py
@@ -69,7 +69,8 @@ def __init__(
raise NotImplementedError(
"PyTorch versions of multipole moments are not implemented. "
- "Use `libcint` as integral driver."
+ "Use `libcint` as integral driver. Install `tad-libcint` via: "
+ "`pip install tad-libcint`."
)
def build(self, driver: BaseIntDriverPytorch) -> Tensor:
diff --git a/src/dxtb/_src/integral/driver/pytorch/quadrupole.py b/src/dxtb/_src/integral/driver/pytorch/quadrupole.py
index db9a06db..799554e1 100644
--- a/src/dxtb/_src/integral/driver/pytorch/quadrupole.py
+++ b/src/dxtb/_src/integral/driver/pytorch/quadrupole.py
@@ -69,7 +69,8 @@ def __init__(
raise NotImplementedError(
"PyTorch versions of multipole moments are not implemented. "
- "Use `libcint` as integral driver."
+ "Use `libcint` as integral driver. Install `tad-libcint` via: "
+ "`pip install tad-libcint`."
)
def build(self, driver: BaseIntDriverPytorch) -> Tensor:
diff --git a/src/dxtb/_src/param/dispersion.py b/src/dxtb/_src/param/dispersion.py
index 04185ba6..92dec472 100644
--- a/src/dxtb/_src/param/dispersion.py
+++ b/src/dxtb/_src/param/dispersion.py
@@ -34,11 +34,11 @@
from typing import Optional, Union
from pydantic import BaseModel, ConfigDict
+from tad_dftd3 import defaults as d3_defaults
+from tad_dftd4 import defaults as d4_defaults
from dxtb._src.typing import Tensor
-from ..constants import xtb
-
__all__ = ["D3Model", "D4Model", "Dispersion"]
@@ -49,19 +49,19 @@ class D3Model(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
- s6: Union[float, Tensor] = xtb.DEFAULT_DISP_S6
+ s6: Union[float, Tensor] = d3_defaults.S6
"""Scaling factor for multipolar (dipole-dipole contribution) terms."""
- s8: Union[float, Tensor] = xtb.DEFAULT_DISP_S8
+ s8: Union[float, Tensor] = d3_defaults.S8
"""Scaling factor for multipolar (dipole-quadrupole contribution) terms."""
- s9: Union[float, Tensor] = xtb.DEFAULT_DISP_S9
+ s9: Union[float, Tensor] = d3_defaults.S9
"""Scaling factor for the many-body dispersion term (ATM/RPA-like)."""
- a1: Union[float, Tensor] = xtb.DEFAULT_DISP_A1
+ a1: Union[float, Tensor] = d3_defaults.A1
"""Becke-Johnson damping parameter."""
- a2: Union[float, Tensor] = xtb.DEFAULT_DISP_A2
+ a2: Union[float, Tensor] = d3_defaults.A2
"""Becke-Johnson damping parameter."""
@@ -75,28 +75,25 @@ class D4Model(BaseModel):
sc: bool = False
"""Whether the dispersion correctio is used self-consistently or not."""
- s6: Union[float, Tensor] = xtb.DEFAULT_DISP_S6
+ s6: Union[float, Tensor] = d4_defaults.S6
"""Scaling factor for multipolar (dipole-dipole contribution) terms"""
- s8: Union[float, Tensor] = xtb.DEFAULT_DISP_S8
+ s8: Union[float, Tensor] = d4_defaults.S8
"""Scaling factor for multipolar (dipole-quadrupole contribution) terms"""
- s9: Union[float, Tensor] = xtb.DEFAULT_DISP_S9
+ s9: Union[float, Tensor] = d4_defaults.S9
"""Scaling factor for the many-body dispersion term (ATM/RPA-like)."""
- s10: Union[float, Tensor] = xtb.DEFAULT_DISP_S10
+ s10: Union[float, Tensor] = d4_defaults.S10
"""Scaling factor for quadrupole-quadrupole term."""
- s10: Union[float, Tensor] = xtb.DEFAULT_DISP_S10
- """Scaling factor for quadrupole-quadrupole contribution."""
-
- alp: Union[float, Tensor] = xtb.DEFAULT_DISP_ALP
+ alp: Union[float, Tensor] = d4_defaults.ALP
"""Exponent of zero damping function in the ATM term."""
- a1: Union[float, Tensor] = xtb.DEFAULT_DISP_A1
+ a1: Union[float, Tensor] = d4_defaults.A1
"""Becke-Johnson damping parameter."""
- a2: Union[float, Tensor] = xtb.DEFAULT_DISP_A2
+ a2: Union[float, Tensor] = d4_defaults.A2
"""Becke-Johnson damping parameter."""
diff --git a/src/dxtb/calculators.py b/src/dxtb/calculators.py
index 0856df1c..06e9a519 100644
--- a/src/dxtb/calculators.py
+++ b/src/dxtb/calculators.py
@@ -93,7 +93,8 @@
The calculator can be used to compute energies, forces, dipole moments and
other properties. The properties are computed by calling the respective
-:meth:`get_` method, just as in ASE.
+:meth:`get_` method, just as in
+`ASE `_.
Depending on which calculator you choose, the properties are calculated using
analytical, autograd, or numerical derivatives. The default uses automatic
diff --git a/src/dxtb/integrals/__init__.py b/src/dxtb/integrals/__init__.py
index 2d430ed1..567c94fb 100644
--- a/src/dxtb/integrals/__init__.py
+++ b/src/dxtb/integrals/__init__.py
@@ -30,8 +30,8 @@
Fundametally, there are two drivers (backends) for the integral computation:
- *PyTorch*: pure PyTorch implementation, only overlap integral
-- *libcint*: Python interface with custom backward functions for derivatives;
- arbitrary integrals and derivatives
+- *`libcint `_*: Python interface with custom
+ backward functions for derivatives; arbitrary integrals and derivatives
We generally recommend the *libcint* driver as it is much faster, especially
for derivatives.
From 949ea9804b3fbe95026d986dbcfe96a0046702a6 Mon Sep 17 00:00:00 2001
From: Marvin Friede <51965259+marvinfriede@users.noreply.github.com>
Date: Sun, 16 Feb 2025 20:39:23 +0100
Subject: [PATCH 2/2] GFN2: Non-self-consistent D4 ATM term (#200)
---
.github/workflows/release.yaml | 95 ++++++++++++++++---
setup.cfg | 2 +-
.../components/classicals/dispersion/base.py | 14 ++-
.../components/classicals/dispersion/d4.py | 61 ++++++------
.../classicals/dispersion/factory.py | 23 ++++-
.../interactions/dispersion/d4sc.py | 5 +-
.../test_classical/test_dispersion/samples.py | 79 +++++++++++++++
.../test_classical/test_dispersion/test_d4.py | 11 ++-
.../test_dispersion/test_d4sc.py | 28 +++++-
.../test_dispersion/test_general.py | 30 ++++++
test/test_interaction/test_list.py | 4 +-
11 files changed, 297 insertions(+), 55 deletions(-)
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 5bb2b303..ebe79be2 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -20,18 +20,52 @@ on:
push:
branches:
- main
+ - master
tags:
- "v*"
+ paths-ignore:
+ - "doc*/**"
+ - "./*.ya?ml"
+ - "**/*.md"
+ - "**/*.rst"
pull_request:
- branches:
- - main
+ paths-ignore:
+ - "doc*/**"
+ - "./*.ya?ml"
+ - "**/*.md"
+ - "**/*.rst"
workflow_dispatch:
jobs:
+ sdist:
+ permissions:
+ contents: read
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Build source distribution (sdist)
+ run: pipx run build --sdist
+
+ - name: Upload source distribution as artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ github.event.repository.name }}-sdist
+ path: dist
+
wheel:
+ permissions:
+ contents: read
+
runs-on: ubuntu-latest
+
steps:
- name: Checkout code
uses: actions/checkout@v4
@@ -44,38 +78,68 @@ jobs:
- name: Upload wheel as artifact
uses: actions/upload-artifact@v4
with:
- name: dxtb-wheel
- path: dist/*.whl
+ name: ${{ github.event.repository.name }}-wheel
+ path: dist
+
+ install_wheel:
+ needs: [wheel]
+
+ permissions:
+ contents: read
- sdist:
runs-on: ubuntu-latest
+
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- - name: Build source distribution (sdist)
- run: pipx run build --sdist
-
- - name: Upload source distribution as artifact
- uses: actions/upload-artifact@v4
+ - name: Download build artifacts
+ uses: actions/download-artifact@v4
with:
- name: dxtb-sdist
- path: dist/*.tar.gz
+ path: dist
+ merge-multiple: true
+
+ - name: Show downloaded artifacts
+ run: ls -lcahFR --color=auto dist
+
+ - name: Install wheel
+ run: |
+ pip install torch --index-url https://download.pytorch.org/whl/cpu
+ pip install dist/*.whl
+
+ - name: Determine package name
+ run: |
+ name=$(echo "${REPO_NAME}" | tr '-' '_')
+ echo "PKG_NAME=$name" >> "$GITHUB_ENV"
+ echo "PKG_NAME is set to '${name}'."
+ env:
+ REPO_NAME: ${{ github.event.repository.name }}
+
+ - name: Test import
+ run: python -c "import ${PKG_NAME}; print(${PKG_NAME}.__version__)"
+ env:
+ PKG_NAME: ${{ env.PKG_NAME }}
upload_test_pypi:
needs: [sdist, wheel]
+
runs-on: ubuntu-latest
+
environment: release
+
permissions:
+ contents: read
id-token: write
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: dist
+ merge-multiple: true
- name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
@@ -84,17 +148,22 @@ jobs:
upload_pypi:
needs: [sdist, wheel, upload_test_pypi]
+
runs-on: ubuntu-latest
+
environment: release
+
permissions:
+ contents: read
id-token: write
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
- name: artifact
path: dist
+ merge-multiple: true
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/setup.cfg b/setup.cfg
index 92751510..27676711 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -46,7 +46,7 @@ install_requires =
pydantic>=2.0.0
scipy
tad-dftd3>=0.3.0
- tad-dftd4>=0.5.1
+ tad-dftd4>=0.5.2
tad-mctc>=0.3.2
tad-multicharge
tomli
diff --git a/src/dxtb/_src/components/classicals/dispersion/base.py b/src/dxtb/_src/components/classicals/dispersion/base.py
index b2e5df23..72fcad95 100644
--- a/src/dxtb/_src/components/classicals/dispersion/base.py
+++ b/src/dxtb/_src/components/classicals/dispersion/base.py
@@ -28,7 +28,7 @@
import torch
from dxtb import IndexHelper
-from dxtb._src.typing import Any, Tensor
+from dxtb._src.typing import Any, Literal, Tensor
from ...classicals import Classical, ClassicalCache
@@ -49,13 +49,22 @@ class Dispersion(Classical):
charge: Tensor | None
"""Total charge of the system."""
- __slots__ = ["numbers", "param", "charge"]
+ ref_charges: Literal["eeq", "gfn2"]
+ """
+ Reference charges for the dispersion model.
+ This is only required for charge-dependent models.
+
+ :default: ``"eeq"``
+ """
+
+ __slots__ = ["numbers", "param", "charge", "ref_charges"]
def __init__(
self,
numbers: Tensor,
param: dict[str, Tensor],
charge: Tensor | None = None,
+ ref_charges: Literal["eeq", "gfn2"] = "eeq",
device: torch.device | None = None,
dtype: torch.dtype | None = None,
) -> None:
@@ -63,6 +72,7 @@ def __init__(
self.numbers = numbers
self.param = param
self.charge = charge
+ self.ref_charges = ref_charges
@abstractmethod
def get_cache(
diff --git a/src/dxtb/_src/components/classicals/dispersion/d4.py b/src/dxtb/_src/components/classicals/dispersion/d4.py
index 6c66087a..9f2f5b25 100644
--- a/src/dxtb/_src/components/classicals/dispersion/d4.py
+++ b/src/dxtb/_src/components/classicals/dispersion/d4.py
@@ -118,36 +118,41 @@ def get_cache(
self._cachevars = cachvars
- model: d4.model.D4Model = (
- kwargs.pop(
- "model",
- d4.model.D4Model(numbers, **self.dd),
+ model = kwargs.pop("model", None)
+ if model is not None and not isinstance(model, d4.model.D4Model):
+ raise TypeError("D4: Model is not of type 'd4.model.D4Model'.")
+ if model is None:
+ model = d4.model.D4Model(
+ numbers, ref_charges=self.ref_charges, **self.dd
)
- .type(self.dtype)
- .to(self.device)
- )
- rcov: Tensor = (
- kwargs.pop(
- "rcov",
- d4.data.COV_D3.to(**self.dd)[numbers],
- )
- .type(self.dtype)
- .to(self.device)
- )
+ else:
+ model = model.type(self.dtype).to(self.device)
+
+ rcov = kwargs.pop("rcov", None)
+ if rcov is not None and not isinstance(rcov, Tensor):
+ raise TypeError("D4: 'rcov' is not of type 'Tensor'.")
+ if rcov is None:
+ rcov = d4.data.COV_D3.to(**self.dd)[numbers]
+ else:
+ rcov = rcov.to(**self.dd)
+
+ r4r2 = kwargs.pop("r4r2", None)
+ if r4r2 is not None and not isinstance(r4r2, Tensor):
+ raise TypeError("D4: 'r4r2' is not of type 'Tensor'.")
+ if r4r2 is None:
+ r4r2 = d4.data.R4R2.to(**self.dd)[numbers]
+ else:
+ r4r2 = r4r2.to(**self.dd)
+
+ cutoff = kwargs.pop("cutoff", None)
+ if cutoff is not None and not isinstance(cutoff, d4.cutoff.Cutoff):
+ raise TypeError("D4: 'cutoff' is not of type 'd4.cutoff.Cutoff'.")
+ if cutoff is None:
+ cutoff = d4.cutoff.Cutoff(**self.dd)
+ else:
+ cutoff = cutoff.type(self.dtype).to(self.device)
+
q = kwargs.pop("q", None)
- r4r2: Tensor = (
- kwargs.pop(
- "r4r2",
- d4.data.R4R2.to(**self.dd)[numbers],
- )
- .type(self.dtype)
- .to(self.device)
- )
- cutoff: d4.cutoff.Cutoff = (
- (kwargs.pop("cutoff", d4.cutoff.Cutoff(**self.dd)))
- .type(self.dtype)
- .to(self.device)
- )
cf = kwargs.pop("counting_function", d4.ncoord.erf_count)
df = kwargs.pop("damping_function", d4.damping.rational_damping)
diff --git a/src/dxtb/_src/components/classicals/dispersion/factory.py b/src/dxtb/_src/components/classicals/dispersion/factory.py
index 8bca30a3..faf20b29 100644
--- a/src/dxtb/_src/components/classicals/dispersion/factory.py
+++ b/src/dxtb/_src/components/classicals/dispersion/factory.py
@@ -28,7 +28,7 @@
import torch
from dxtb._src.param import Param
-from dxtb._src.typing import DD, Tensor, get_default_dtype
+from dxtb._src.typing import DD, Literal, Tensor, get_default_dtype
from dxtb._src.typing.exceptions import ParameterWarning
from dxtb._src.utils import convert_float_tensor
@@ -43,6 +43,7 @@ def new_dispersion(
numbers: Tensor,
par: Param,
charge: Tensor | None = None,
+ ref_charges: Literal["eeq", "gfn2"] = "eeq",
device: torch.device | None = None,
dtype: torch.dtype | None = None,
) -> Dispersion | None:
@@ -55,6 +56,9 @@ def new_dispersion(
Atomic numbers for all atoms in the system (shape: ``(..., nat)``).
par : Param
Representation of an extended tight-binding model.
+ ref_charges : Literal["eeq", "gfn2"], optional
+ Reference charges for the dispersion model. This is only required for
+ charge-dependent models. Default is ``"eeq"``.
device : torch.device | None, optional
Device to store the tensor on. If ``None`` (default), the default
device is used.
@@ -109,15 +113,30 @@ def new_dispersion(
**dd,
)
+ if isinstance(par.dispersion.d4.sc, bool) is False:
+ raise ValueError("D4 self-consistency flag is not a boolean.")
+
# only non-self-consistent D4 is a classical component
if par.dispersion.d4.sc is False:
if charge is None:
raise ValueError("The total charge is required for DFT-D4.")
return DispersionD4(
- numbers, param, charge=charge, device=device, dtype=dtype
+ numbers,
+ param,
+ ref_charges=ref_charges,
+ charge=charge,
+ device=device,
+ dtype=dtype,
)
+ # Classical part of self-consistent D4 is only ATM term
+ param["s6"] = torch.tensor(0.0, **dd)
+ param["s8"] = torch.tensor(0.0, **dd)
+ return DispersionD4(
+ numbers, param, ref_charges="gfn2", device=device, dtype=dtype
+ )
+
if par.dispersion.d3 is not None and par.dispersion.d4 is not None:
raise ValueError("Parameters for both D3 and D4 found. Please decide.")
diff --git a/src/dxtb/_src/components/interactions/dispersion/d4sc.py b/src/dxtb/_src/components/interactions/dispersion/d4sc.py
index 896fac8f..23d3c6a5 100644
--- a/src/dxtb/_src/components/interactions/dispersion/d4sc.py
+++ b/src/dxtb/_src/components/interactions/dispersion/d4sc.py
@@ -71,10 +71,7 @@ class constructor.
except for multiplication with C6 and C8.
"""
- __slots__ = [
- "__store",
- "cn",
- ]
+ __slots__ = ["__store", "cn", "dispmat"]
def __init__(
self,
diff --git a/test/test_classical/test_dispersion/samples.py b/test/test_classical/test_dispersion/samples.py
index 6bd324ce..dd1b5c10 100644
--- a/test/test_classical/test_dispersion/samples.py
+++ b/test/test_classical/test_dispersion/samples.py
@@ -53,6 +53,9 @@ class Refs(TypedDict):
DFT-D4. Values taken from the last SCF iteration.
"""
+ edisp_d4atm: Tensor
+ """Reference values for ATM term for GFN2 dispersion energy."""
+
class Record(Molecule, Refs):
"""Store for molecular information and reference values"""
@@ -133,6 +136,13 @@ class Record(Molecule, Refs):
],
dtype=torch.double,
),
+ "edisp_d4atm": torch.tensor(
+ [
+ 0.0,
+ 0.0,
+ ],
+ dtype=torch.double,
+ ),
},
"SiH4": {
"c6": torch.tensor([]),
@@ -418,6 +428,16 @@ class Record(Molecule, Refs):
],
dtype=torch.double,
),
+ "edisp_d4atm": torch.tensor(
+ [
+ +8.0008766266703584e-010,
+ +1.2493752290242430e-008,
+ +1.2493752290242430e-008,
+ +1.2493752290242430e-008,
+ +1.2493752290242430e-008,
+ ],
+ dtype=torch.double,
+ ),
},
"PbH4-BiH3": {
"c6": torch.tensor(
@@ -1425,6 +1445,20 @@ class Record(Molecule, Refs):
],
dtype=torch.double,
),
+ "edisp_d4atm": torch.tensor(
+ [
+ +7.7272938795645151e-007,
+ +4.5218856694292583e-007,
+ +4.5218808808158126e-007,
+ +4.5218808808158132e-007,
+ -8.8399588605272466e-007,
+ -1.2463032891628143e-007,
+ +4.5225074134004390e-007,
+ +4.5225038127102465e-007,
+ +4.5225074134004390e-007,
+ ],
+ dtype=torch.double,
+ ),
},
"C6H5I-CH3SH": {
"c6": torch.tensor(
@@ -1990,6 +2024,29 @@ class Record(Molecule, Refs):
],
dtype=torch.double,
),
+ "edisp_d4atm": torch.tensor(
+ [
+ -4.3331775880901064e-006,
+ -5.6316208478235120e-006,
+ -4.4151430669390974e-006,
+ -1.6931177749450845e-006,
+ -4.7713787681583763e-006,
+ -1.5607720660631233e-006,
+ -1.8632309079933387e-005,
+ -2.8164711895015901e-006,
+ +2.7102583626633188e-006,
+ -2.5108585274845472e-007,
+ +2.7920901437202392e-006,
+ -2.1573908644281799e-007,
+ -5.0386629554654083e-006,
+ -2.4953148278505736e-007,
+ -7.7491344231322255e-007,
+ +1.5730549096827175e-006,
+ -5.7850125917478828e-007,
+ -3.0838272315118466e-006,
+ ],
+ dtype=torch.double,
+ ),
},
"C4H5NCS": {
"c6": torch.tensor([]),
@@ -1999,6 +2056,7 @@ class Record(Molecule, Refs):
"grad": torch.tensor([]),
"hessian": torch.tensor([]),
"edisp_d4sc": torch.tensor([]),
+ "edisp_d4atm": torch.tensor([]),
},
"MB16_43_01": {
"c6": torch.tensor([]),
@@ -4440,6 +4498,27 @@ class Record(Molecule, Refs):
],
dtype=torch.double,
),
+ "edisp_d4atm": torch.tensor(
+ [
+ +2.6641513639325338e-006,
+ +2.1572240939211525e-006,
+ +7.8245896602553751e-006,
+ +4.0851780542176583e-006,
+ +3.3646778087774743e-006,
+ +4.0789733648551431e-006,
+ +2.4916938264030234e-006,
+ +7.4604172210352487e-006,
+ +7.2845174002515587e-007,
+ +5.4365690689378601e-006,
+ +3.4379003750222701e-006,
+ +8.8943374149859947e-006,
+ +3.9770329797870254e-006,
+ +3.8660863869851540e-006,
+ +4.5818599027940245e-006,
+ +3.0961426597595972e-007,
+ ],
+ dtype=torch.double,
+ ),
},
}
diff --git a/test/test_classical/test_dispersion/test_d4.py b/test/test_classical/test_dispersion/test_d4.py
index 358dbc0b..7dff1771 100644
--- a/test/test_classical/test_dispersion/test_d4.py
+++ b/test/test_classical/test_dispersion/test_d4.py
@@ -89,7 +89,16 @@ def test_batch(dtype: torch.dtype) -> None:
if disp is None:
assert False
- cache = disp.get_cache(numbers)
+ # Add kwargs explicitly for coverage
+ model = d4.model.D4Model(numbers, **dd)
+ rcov = d4.data.COV_D3.to(**dd)[numbers]
+ r4r2 = d4.data.R4R2.to(**dd)[numbers]
+ cutoff = d4.cutoff.Cutoff(**dd)
+
+ cache = disp.get_cache(
+ numbers, model=model, rcov=rcov, r4r2=r4r2, cutoff=cutoff
+ )
+
edisp = disp.get_energy(positions, cache)
assert edisp.dtype == dtype
assert pytest.approx(edisp.cpu()) == energy.cpu()
diff --git a/test/test_classical/test_dispersion/test_d4sc.py b/test/test_classical/test_dispersion/test_d4sc.py
index 6615c3ef..46071fb3 100644
--- a/test/test_classical/test_dispersion/test_d4sc.py
+++ b/test/test_classical/test_dispersion/test_d4sc.py
@@ -30,6 +30,7 @@
from dxtb import GFN2_XTB, Calculator
from dxtb._src.constants import labels
from dxtb._src.typing import DD
+from dxtb.components.dispersion import new_dispersion
from ...conftest import DEVICE
from .samples import samples
@@ -48,7 +49,7 @@
@pytest.mark.filterwarnings("ignore")
@pytest.mark.parametrize("dtype", [torch.float, torch.double])
@pytest.mark.parametrize("name", slist)
-def test_single(dtype: torch.dtype, name: str):
+def test_single(dtype: torch.dtype, name: str) -> None:
tol = sqrt(torch.finfo(dtype).eps) * 10
dd: DD = {"device": DEVICE, "dtype": dtype}
@@ -72,7 +73,7 @@ def test_single(dtype: torch.dtype, name: str):
@pytest.mark.parametrize("dtype", [torch.float, torch.double])
@pytest.mark.parametrize("name1", slist)
@pytest.mark.parametrize("name2", ["LiH", "MB16_43_01"])
-def test_batch(dtype: torch.dtype, name1: str, name2: str):
+def test_batch(dtype: torch.dtype, name1: str, name2: str) -> None:
tol = sqrt(torch.finfo(dtype).eps) * 10
dd: DD = {"device": DEVICE, "dtype": dtype}
@@ -104,3 +105,26 @@ def test_batch(dtype: torch.dtype, name1: str, name2: str):
edisp = d4sc.get_energy(result.charges, cache, calc.ihelp)
assert pytest.approx(ref.cpu(), abs=10 * tol, rel=tol) == edisp.cpu()
+
+
+@pytest.mark.parametrize("dtype", [torch.float, torch.double])
+@pytest.mark.parametrize("name", slist)
+def test_classical(dtype: torch.dtype, name: str) -> None:
+ dd: DD = {"dtype": dtype, "device": DEVICE}
+ tol = sqrt(torch.finfo(dtype).eps)
+
+ sample = samples[name]
+ numbers = sample["numbers"].to(DEVICE)
+ positions = sample["positions"].to(**dd)
+ ref = sample["edisp_d4atm"].to(**dd)
+ charges = torch.tensor(0.0, **dd)
+
+ disp = new_dispersion(
+ numbers, GFN2_XTB, charge=charges, ref_charges="gfn2", **dd
+ )
+ assert disp is not None
+
+ cache = disp.get_cache(numbers=numbers, ihelp=None)
+ e = disp.get_energy(positions, cache)
+
+ assert pytest.approx(ref.cpu(), abs=tol) == e.cpu()
diff --git a/test/test_classical/test_dispersion/test_general.py b/test/test_classical/test_dispersion/test_general.py
index b55620b5..345b3d15 100644
--- a/test/test_classical/test_dispersion/test_general.py
+++ b/test/test_classical/test_dispersion/test_general.py
@@ -66,6 +66,15 @@ def test_fail_no_dispersion() -> None:
assert new_dispersion(torch.tensor(0.0), _par) is None
+def test_fail_wrong_sc_value() -> None:
+ _par = GFN2_XTB.model_copy(deep=True)
+ assert _par.dispersion is not None
+
+ _par.dispersion.d4.sc = None # type: ignore
+ with pytest.raises(ValueError):
+ new_dispersion(torch.tensor(0.0), _par)
+
+
def test_fail_too_many_parameters() -> None:
_par = GFN1_XTB.model_copy(deep=True)
_par2 = GFN2_XTB.model_copy(deep=True)
@@ -78,6 +87,27 @@ def test_fail_too_many_parameters() -> None:
new_dispersion(torch.tensor(0.0), _par)
+def test_fail_d4_cache() -> None:
+ numbers = torch.tensor([3, 1])
+
+ _par = GFN2_XTB.model_copy(deep=True)
+
+ disp = new_dispersion(numbers, _par, torch.tensor(0.0))
+ assert disp is not None
+
+ with pytest.raises(TypeError):
+ _ = disp.get_cache(numbers=numbers, model=0)
+
+ with pytest.raises(TypeError):
+ _ = disp.get_cache(numbers=numbers, rcov=0)
+
+ with pytest.raises(TypeError):
+ _ = disp.get_cache(numbers=numbers, r4r2=0)
+
+ with pytest.raises(TypeError):
+ _ = disp.get_cache(numbers=numbers, cutoff=0)
+
+
def test_d4_cache() -> None:
numbers = torch.tensor([3, 1])
diff --git a/test/test_interaction/test_list.py b/test/test_interaction/test_list.py
index 5d02975c..1f7397f5 100644
--- a/test/test_interaction/test_list.py
+++ b/test/test_interaction/test_list.py
@@ -81,7 +81,7 @@ def test_reset() -> None:
assert ef is not None and ef.cache is not None
assert efg is not None and efg.cache is not None
- assert len(d4sc.cache) == 1
+ assert len(d4sc.cache) == 2
ilist.reset_d4sc()
assert d4sc.cache is None
@@ -124,7 +124,7 @@ def test_reset_all() -> None:
assert ef is not None and ef.cache is not None
assert efg is not None and efg.cache is not None
- assert len(d4sc.cache) == 1
+ assert len(d4sc.cache) == 2
assert len(es2.cache) == 2
assert len(es3.cache) == 1
assert len(ef.cache) == 2