diff --git a/README.md b/README.md index 34d0b01e..8730842e 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,11 @@ Test Status Ubuntu - - Test Status macOS - - Test Status macOS (x86) + Test Status macOS (x86) - Test Status macOS (ARM) + Test Status macOS (ARM) Test Status Windows @@ -124,6 +121,8 @@ forces = calc.get_forces(pos) assert torch.equal(forces, -g) ``` +All quantities are in atomic units. + For more examples and details, check out [the documentation](https://dxtb.readthedocs.io). ## Compatibility @@ -142,13 +141,13 @@ For more examples and details, check out [the documentation](https://dxtb.readth Note that only the latest bug fix version is listed, but all preceding bug fix minor versions are supported. For example, although only version 2.2.2 is listed, version 2.2.0 and 2.2.1 are also supported. +**Restriction for macOS and Windows:** + On macOS and Windows, PyTorch<2.0.0 does only support Python<3.11. -**Restriction for macOS and Windows:** The libcint interface is **not** available for macOS and Windows. Correspondingly, the integral evaluation can be considerably slower. Moreover, higher-order multipole integrals (dipole, quadrupole, ...) are not implemented. - While macOS support may be considered in the future, native Windows support is not possible, because the underlying [libcint](https://github.com/sunqm/libcint) library does not work under Windows. diff --git a/docs/source/01_quickstart/getting_started.rst b/docs/source/01_quickstart/getting_started.rst index fcd5d506..861f33d2 100644 --- a/docs/source/01_quickstart/getting_started.rst +++ b/docs/source/01_quickstart/getting_started.rst @@ -6,6 +6,8 @@ Getting Started The main object of *dxtb* is the :class:`~dxtb.Calculator` class, which is used to perform calculations on a given system. +Note that all quantities are in atomic units. + Creating a Calculator --------------------- diff --git a/docs/source/01_quickstart/installation.rst b/docs/source/01_quickstart/installation.rst index 6eecadb1..82edf04c 100644 --- a/docs/source/01_quickstart/installation.rst +++ b/docs/source/01_quickstart/installation.rst @@ -75,8 +75,8 @@ If you want to install the package without pip, start by cloning the repository. DEST=/opt/software git clone https://github.com/grimme-lab/dxtb $DEST/dxtb -Next, add /dxtb/src to your `$PYTHONPATH` environment variable. -For the command line interface, add /dxtb/bin to your `$PATH` environment variable. +Next, add ``/dxtb/src`` to your ``$PYTHONPATH`` environment variable. +For the command line interface, add ``/dxtb/bin`` to your ``$PATH`` environment variable. .. code-block:: shell @@ -97,9 +97,11 @@ The following dependencies are required - `tad-multicharge `__ - `tad-dftd3 `__ - `tad-dftd4 `__ +- `tad-libcint `__ - `torch `__ For tests, we also require - `pytest `__ +- `pyscf `__ - `tox `__ diff --git a/docs/source/03_for_developers/errors.rst b/docs/source/03_for_developers/errors.rst index 4f186fce..fe7b5f51 100644 --- a/docs/source/03_for_developers/errors.rst +++ b/docs/source/03_for_developers/errors.rst @@ -56,8 +56,8 @@ RuntimeError: clone is not supported by NestedIntSymNode -------------------------------------------------------- This is a bug in PyTorch 2.3.0 and 2.3.1 (see -`PyTorch #128607 <`__). -To avoid this error, manually import `torch._dynamo` in the code. For example: +`PyTorch #128607 `__). +To avoid this error, manually import ``torch._dynamo`` in the code. For example: .. code-block:: python diff --git a/docs/source/index.rst b/docs/source/index.rst index 39f87cbc..dc7700c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -71,14 +71,18 @@ If you use *dxtb* in your research, please cite the following paper: .. code-block:: bibtex - @article{dxtb, + @article{dxtb.2024, title = {dxtb -- An Efficient and Fully Differentiable Framework for Extended Tight-Binding}, - author = {Friede, Marvin and Hölzer, Christian and Ehlert, Sebastian and Grimme, Stefan}, - journal = {J. Chem. Phys.}, - volume = {}, - number = {}, - pages = {}, + author = {Friede, Marvin and H{\"o}lzer, Christian and Ehlert, Sebastian and Grimme, Stefan}, + journal = {The Journal of Chemical Physics}, + volume = {161}, + number = {6}, + pages = {062501}, year = {2024}, + month = {08}, + issn = {0021-9606}, + doi = {10.1063/5.0216715}, + url = {https://doi.org/10.1063/5.0216715}, } diff --git a/examples/batch.py b/examples/batch-1.py similarity index 100% rename from examples/batch.py rename to examples/batch-1.py diff --git a/examples/batch-2.py b/examples/batch-2.py new file mode 100644 index 00000000..0e6a0a1f --- /dev/null +++ b/examples/batch-2.py @@ -0,0 +1,86 @@ +# This file is part of dxtb. +# +# SPDX-Identifier: Apache-2.0 +# Copyright (C) 2024 Grimme Group +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Obtaining the interaction energy of a dimer from a batched calculation. +""" +import tad_mctc as mctc +import torch + +import dxtb + +torch.set_printoptions(precision=10) + + +# S22 system 4: formamide dimer +numbers = mctc.batch.pack( + ( + mctc.convert.symbol_to_number("C C N N H H H H H H O O".split()), + mctc.convert.symbol_to_number("C O N H H H".split()), + ) +) + +# coordinates in Bohr +positions = mctc.batch.pack( + ( + torch.tensor( + [ + [-3.81469488143921, +0.09993441402912, 0.00000000000000], + [+3.81469488143921, -0.09993441402912, 0.00000000000000], + [-2.66030049324036, -2.15898251533508, 0.00000000000000], + [+2.66030049324036, +2.15898251533508, 0.00000000000000], + [-0.73178529739380, -2.28237795829773, 0.00000000000000], + [-5.89039325714111, -0.02589114569128, 0.00000000000000], + [-3.71254944801331, -3.73605775833130, 0.00000000000000], + [+3.71254944801331, +3.73605775833130, 0.00000000000000], + [+0.73178529739380, +2.28237795829773, 0.00000000000000], + [+5.89039325714111, +0.02589114569128, 0.00000000000000], + [-2.74426102638245, +2.16115570068359, 0.00000000000000], + [+2.74426102638245, -2.16115570068359, 0.00000000000000], + ], + dtype=torch.double, + ), + torch.tensor( + [ + [-0.55569743203406, +1.09030425468557, 0.00000000000000], + [+0.51473634678469, +3.15152550263611, 0.00000000000000], + [+0.59869690244446, -1.16861263789477, 0.00000000000000], + [-0.45355203669134, -2.74568780438064, 0.00000000000000], + [+2.52721209544999, -1.29200800956867, 0.00000000000000], + [-2.63139587595376, +0.96447869452240, 0.00000000000000], + ], + dtype=torch.double, + ), + ) +) + +# total charge of both system +charge = torch.tensor([0.0, 0.0], dtype=torch.double) + + +# instantiate calculator and calculate GFN1 energy in Hartree +calc = dxtb.calculators.GFN1Calculator(numbers, dtype=torch.double) +energy = calc.get_energy(positions, charge) + + +print(energy) +# tensor([-23.2835232516, -11.6302093800], dtype=torch.float64) + +e = energy[0] - 2 * energy[1] +# tensor(-0.0231044917, dtype=torch.float64) + +print(e * mctc.units.AU2KCALMOL) +# tensor(-14.4982874136, dtype=torch.float64) diff --git a/src/dxtb/_src/calculators/types/base.py b/src/dxtb/_src/calculators/types/base.py index a3b92aed..e47c6b4c 100644 --- a/src/dxtb/_src/calculators/types/base.py +++ b/src/dxtb/_src/calculators/types/base.py @@ -28,7 +28,7 @@ class and implement the :meth:`calculate` method and the corresponding methods from abc import abstractmethod import torch -from tad_mctc.exceptions import DtypeError +from tad_mctc.exceptions import DeviceError, DtypeError from dxtb import IndexHelper, OutputHandler from dxtb import integrals as ints @@ -730,6 +730,40 @@ def get_property( if allow_calculation is False and name not in self.cache: return None + # Before calculating, let's do some device and dtype checks. + if self.device != positions.device: + raise DeviceError( + f"Device mismatch: Calculator is on '{self.device}', but " + f"positions are on '{positions.device}'." + ) + if self.dtype != positions.dtype: + raise DtypeError( + f"Dtype mismatch: Calculator is of type '{self.dtype}', but " + f"positions are of type '{positions.dtype}'." + ) + if isinstance(chrg, Tensor): + if self.dtype != chrg.dtype: + raise DtypeError( + f"Dtype mismatch: Calculator is of type '{self.dtype}', " + f"but charge is of type '{chrg.dtype}'." + ) + if self.device != chrg.device: + raise DeviceError( + f"Device mismatch: Calculator is on '{self.device}', but " + f"charge is on '{chrg.device}'." + ) + if isinstance(spin, Tensor): + if self.dtype != spin.dtype: + raise DtypeError( + f"Dtype mismatch: Calculator is of type '{self.dtype}', " + f"but spin is of type '{spin.dtype}'." + ) + if self.device != spin.device: + raise DeviceError( + f"Device mismatch: Calculator is on '{self.device}', but " + f"spin is on '{spin.device}'." + ) + # All the cache checks are handled deep within `calculate`. No need to # do it here as well. self.calculate([name], positions, chrg=chrg, spin=spin, **kwargs) diff --git a/test/test_calculator/test_dd.py b/test/test_calculator/test_dd.py new file mode 100644 index 00000000..a436e16b --- /dev/null +++ b/test/test_calculator/test_dd.py @@ -0,0 +1,80 @@ +# This file is part of dxtb. +# +# SPDX-Identifier: Apache-2.0 +# Copyright (C) 2024 Grimme Group +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Test Calculator dtype and device consistency. +""" + +from __future__ import annotations + +import pytest +import torch +from tad_mctc.exceptions import DeviceError, DtypeError + +from dxtb import GFN1_XTB as par +from dxtb import Calculator +from dxtb._src.typing import MockTensor, Tensor + + +def test_fail_dtype() -> None: + numbers = torch.tensor([3, 1]) + positions = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) + charge = torch.tensor(0.0) + spin = torch.tensor(0.0) + + calc = Calculator(numbers, par, opts={"verbosity": 0}) + + # same dtype works + e = calc.get_energy(positions, charge, spin) + assert isinstance(e, Tensor) + + with pytest.raises(DtypeError): + calc.get_energy(positions.type(torch.double), charge, spin) + + with pytest.raises(DtypeError): + calc.get_energy(positions, charge.type(torch.double), spin) + + with pytest.raises(DtypeError): + calc.get_energy(positions, charge, spin.type(torch.double)) + + +def test_fail_device() -> None: + numbers = torch.tensor([3, 1]) + + _positions = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]) + _charge = torch.tensor(0.0) + _spin = torch.tensor(0.0) + + calc = Calculator(numbers, par, opts={"verbosity": 0}, dtype=torch.float) + + # same device works + e = calc.get_energy(_positions, _charge, _spin) + assert isinstance(e, Tensor) + + with pytest.raises(DeviceError): + positions = MockTensor(_positions) + positions.device = torch.device("cuda") + calc.get_energy(positions, _charge, _spin) + + with pytest.raises(DeviceError): + charge = MockTensor(_charge) + charge.device = torch.device("cuda") + calc.get_energy(_positions, charge, _spin) + + with pytest.raises(DeviceError): + spin = MockTensor(_spin) + spin.device = torch.device("cuda") + calc.get_energy(_positions, _charge, spin) diff --git a/test/test_calculator/test_general.py b/test/test_calculator/test_general.py index 60ac971d..81adbc19 100644 --- a/test/test_calculator/test_general.py +++ b/test/test_calculator/test_general.py @@ -33,7 +33,7 @@ def test_fail() -> None: numbers = torch.tensor([6, 1, 1, 1, 1], dtype=torch.double) with pytest.raises(DtypeError): - Calculator(numbers, par, opts={"vebosity": 0}) + Calculator(numbers, par, opts={"verbosity": 0}) # because of the exception, the timer for the setup is never stopped timer.reset()