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