Getting Started#

The main object of dxtb is the Calculator class, which is used to perform calculations on a given system.

Note that all quantities are in atomic units.

Creating a Calculator#

The constructor always requires the atomic numbers of the system(s) and a tight-binding parametrization. Currently, we provide the GFN1_XTB and GFN2_XTB parametrizations out of the box. If you directly use the corresponding GFN1Calculator, only the atomic numbers are required.

import torch
import dxtb

numbers = torch.tensor([3, 1])  # LiH
calc1 = dxtb.calculators.GFN1Calculator(numbers)

# equivalent
calc2 = dxtb.Calculator(numbers, dxtb.GFN1_XTB)

We recommend to always pass the (floating point) dtype and device arguments to the constructor to ensure consistency.

import torch
import dxtb

dd = {"dtype": torch.double, "device": torch.device("cpu")}

numbers = torch.tensor([3, 1], device=dd["device"])
calc = dxtb.calculators.GFN1Calculator(numbers, **dd)

Using the Calculator#

Now, you can request various properties of the system. The most common one is the total energy.

import torch
import dxtb

dd = {"dtype": torch.double, "device": torch.device("cpu")}

numbers = torch.tensor([3, 1], device=dd["device"])
calc = dxtb.calculators.GFN1Calculator(numbers, **dd)

positions = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], **dd)
energy = calc.energy(positions)

print(energy)

If your system is charged or has unpaired electrons, you need to supply both quantities as optional keyword arguments to energy().

energy = calc.energy(positions, charge=0, spin=0)

Instead of calling the energy() method, you can also use corresponding getters get_energy():

energy = calc.get_energy(positions, charge=0, spin=0)

We recommend using the getters, as they provide the familiar ASE-like interface.

Gradients#

To calculate the gradients of the energy with respect to the atomic positions, you can use the standard torch.autograd.grad() function. Remember to set the requires_grad attribute of the positions tensor to True.

import torch
import dxtb

dd = {"dtype": torch.double, "device": torch.device("cpu")}

numbers = torch.tensor([3, 1], device=dd["device"])
calc = dxtb.calculators.GFN1Calculator(numbers, **dd)

positions = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], **dd)
positions.requires_grad_(True)

energy = calc.energy(positions)
(g,) = torch.autograd.grad(energy, positions)

print(g)

For convenience, you can use the forces() or get_forces() method directly.

forces = calc.forces(positions)
forces = calc.get_forces(positions)

The equivalency of the two methods (except for the sign) can be verified by the example here.

Warning

If you supply the same inputs to the calculator multiple times with gradient tracking enabled, you have to reset the calculator in between with reset(). Otherwise, the gradients will be wrong.

Example

import torch
import dxtb

dd = {"dtype": torch.double, "device": torch.device("cpu")}

numbers = torch.tensor([3, 1], device=dd["device"])
positions = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], **dd)

calc = dxtb.calculators.GFN1Calculator(numbers, **dd)

pos = positions.clone().requires_grad_(True)
energy = calc.energy(pos)
(g1,) = torch.autograd.grad(energy, pos)

# wrong gradients without reset here
calc.reset()

pos = positions.clone().requires_grad_(True)
energy = calc.energy(pos)
(g2,) = torch.autograd.grad(energy, pos)

assert torch.allclose(g1, g2)

More Properties#

Besides get_energy() / energy() and get_forces() / forces(), the Calculator class provides methods to calculate various other quantities. The full list is given below:

Each method has a corresponding getter and some additional properties are also accessible via getters:

Note that all methods (except energy()) utilize automatic derivatives. For comparison, each method also has a numerical counterpart, e.g., forces_numerical().

Note

Caching

These methods only calculate the requested property. To also store associated properties, turn on caching by passing {"cache_enabled": True} to the calculator options. This avoids redundant calculations. For example, with caching, get_hessian() also stores the forces and the energy. Hence, a subsequent get_forces() does not necessitate an additional calculation.

For more details, please see here.