diff --git a/README.md b/README.md
index 34d0b01e..8730842e 100644
--- a/README.md
+++ b/README.md
@@ -16,14 +16,11 @@
-
-
-
-
+
-
+
@@ -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()