How to run periodic calculations?#
For a periodic system the two-body dispersion energy is a lattice sum, which has to be either truncated in real space or transformed into reciprocal space. This page shows the three available setups on the same system, the cubic phase of ammonia with r²SCAN-D3(BJ).
Note
The background on the truncation error and how to size a cutoff for a target accuracy is covered in Periodic systems: cutoffs and summation accuracy.
To test the examples you can install the dependencies with
mamba create d3 simple-dftd3 fortran-compiler pkg-config
mamba activate d3
mamba create d3 simple-dftd3 c-compiler pkg-config
mamba activate d3
mamba create d3 dftd3-python
mamba activate d3
Remove the truncation error with reciprocal space summation#
Enabling the low-rank expansion of the reference \(C_6\) coefficients makes the two-body energy separable, so it can be summed over the reciprocal lattice instead. The damped pair potential is bounded at the origin, which lets the reciprocal sum converge exponentially without a real space complement, and the two-body cutoff drops out of the calculation entirely.
This requires three-dimensional periodic boundary conditions and a damping function with a known reciprocal space representation, currently the rational and the zero damping function.
program pbc_ewald
use, intrinsic :: iso_fortran_env, only : r8 => real64
use dftd3, only: d3_model, d3_param, rational_damping_param, get_rational_damping, &
& new_rational_damping, new_d3_model, get_dispersion, realspace_cutoff, &
& d3_lowrank_config
use mctc_env, only: error_type
use mctc_io, only: structure_type, new
implicit none
character(len=:), allocatable :: method
type(structure_type) :: mol
type(error_type), allocatable :: error
integer, allocatable :: num(:)
real(r8), allocatable :: xyz(:, :)
real(r8) :: lattice(3, 3), energy
type(d3_model) :: disp
type(d3_param) :: inp
type(rational_damping_param) :: param
method = "r2SCAN"
num = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7]
xyz = reshape([ & ! coordinates in Bohr
& 3.5141339317522400_r8, 2.5620901186651399_r8, 0.9887044918760900_r8, &
& 2.5620901186651399_r8, 0.9887044918760900_r8, 3.5141339317522400_r8, &
& 0.9887044918760900_r8, 3.5141339317522400_r8, 2.5620901186651399_r8, &
& 8.2027323927494997_r8, 2.1265083423321101_r8, 8.3884924301184203_r8, &
& 7.2504996070913199_r8, 3.6998939691211699_r8, 5.8628740176711904_r8, &
& 5.6773029528733403_r8, 1.1742755566739300_r8, 6.8151068033293702_r8, &
& 6.8151068033293702_r8, 5.6773029528733403_r8, 1.1742755566739300_r8, &
& 8.3884924301184203_r8, 8.2027323927494997_r8, 2.1265083423321101_r8, &
& 3.6998939691211699_r8, 5.8628740176711904_r8, 7.2504996070913199_r8, &
& 1.1742755566739300_r8, 6.8151068033293702_r8, 5.6773029528733403_r8, &
& 2.1265083423321101_r8, 8.3884924301184203_r8, 8.2027323927494997_r8, &
& 5.8628740176711904_r8, 7.2504996070913199_r8, 3.6998939691211699_r8, &
& 1.9543543300807500_r8, 1.9543543300807500_r8, 1.9543543300807500_r8, &
& 6.6427638185069302_r8, 2.7342441309165002_r8, 7.4228425919137502_r8, &
& 7.4228425919137502_r8, 6.6427638185069302_r8, 2.7342441309165002_r8, &
& 2.7342441309165002_r8, 7.4228425919137502_r8, 6.6427638185069302_r8], &
& [3, size(num)])
lattice = reshape([ & ! lattice vectors in Bohr
& 9.3771283249512098_r8, 0.0000000000000000_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 9.3771283249512098_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 0.0000000000000000_r8, 9.3771283249512098_r8], &
& [3, 3])
call new(mol, num, xyz, lattice=lattice, charge=0.0_r8, uhf=0)
call get_rational_damping(inp, method, error, s9=1.0_r8)
if (allocated(error)) then
print "(2a)", "Error: ", error%message
return
end if
call new_rational_damping(param, inp)
! the low-rank expansion of the reference C6 coefficients enables the
! reciprocal space summation, an empty config uses the defaults
call new_d3_model(disp, mol, lowrank=d3_lowrank_config())
! the two-body cutoff is unused here, the remaining cutoffs still apply
call get_dispersion(error, mol, disp, param, realspace_cutoff(), energy)
if (allocated(error)) then
print "(2a)", "Error: ", error%message
return
end if
print "(3a, f13.10, a)", "Dispersion energy for ", method, "-D3(BJ) is ", energy, " Hartree"
end program pbc_ewald
#include <stdio.h>
#include <stdbool.h>
#include "dftd3.h"
int main(void)
{
dftd3_error error = dftd3_new_error();
dftd3_structure mol = NULL;
dftd3_model d3 = NULL;
dftd3_param param = NULL;
int nat = 16;
int num[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7};
double xyz[48] = { // coordinates in Bohr
3.5141339317522400, 2.5620901186651399, 0.9887044918760900,
2.5620901186651399, 0.9887044918760900, 3.5141339317522400,
0.9887044918760900, 3.5141339317522400, 2.5620901186651399,
8.2027323927494997, 2.1265083423321101, 8.3884924301184203,
7.2504996070913199, 3.6998939691211699, 5.8628740176711904,
5.6773029528733403, 1.1742755566739300, 6.8151068033293702,
6.8151068033293702, 5.6773029528733403, 1.1742755566739300,
8.3884924301184203, 8.2027323927494997, 2.1265083423321101,
3.6998939691211699, 5.8628740176711904, 7.2504996070913199,
1.1742755566739300, 6.8151068033293702, 5.6773029528733403,
2.1265083423321101, 8.3884924301184203, 8.2027323927494997,
5.8628740176711904, 7.2504996070913199, 3.6998939691211699,
1.9543543300807500, 1.9543543300807500, 1.9543543300807500,
6.6427638185069302, 2.7342441309165002, 7.4228425919137502,
7.4228425919137502, 6.6427638185069302, 2.7342441309165002,
2.7342441309165002, 7.4228425919137502, 6.6427638185069302};
double lattice[9] = { // lattice vectors in Bohr
9.3771283249512098, 0.0000000000000000, 0.0000000000000000,
0.0000000000000000, 9.3771283249512098, 0.0000000000000000,
0.0000000000000000, 0.0000000000000000, 9.3771283249512098};
mol = dftd3_new_structure(error, nat, num, xyz, lattice, NULL);
if (dftd3_check_error(error)) goto handle_error;
char method[7] = "r2SCAN";
param = dftd3_load_rational_damping(error, method, true);
if (dftd3_check_error(error)) goto handle_error;
d3 = dftd3_new_d3_model(error, mol);
if (dftd3_check_error(error)) goto handle_error;
// the zero arguments select the default rank and reciprocal space cutoff,
// the trailing zero sums over the reciprocal lattice directly
dftd3_set_model_ewald(error, d3, 0, 0.0, 0.0, 0);
if (dftd3_check_error(error)) goto handle_error;
double energy;
dftd3_get_dispersion(error, mol, d3, param, &energy, NULL, NULL);
if (dftd3_check_error(error)) goto handle_error;
printf("Dispersion energy for %s-D3(BJ) is %13.10lf Hartree\n", method, energy);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 0;
handle_error: {
char msg[512];
dftd3_get_error(error, msg, NULL);
printf("Error: %s\n", msg);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 1;
}
}
import numpy as np
from dftd3.interface import RationalDampingParam, DispersionModel
num = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7])
xyz = np.array( # coordinates in Bohr
[
[3.5141339317522400, 2.5620901186651399, 0.9887044918760900],
[2.5620901186651399, 0.9887044918760900, 3.5141339317522400],
[0.9887044918760900, 3.5141339317522400, 2.5620901186651399],
[8.2027323927494997, 2.1265083423321101, 8.3884924301184203],
[7.2504996070913199, 3.6998939691211699, 5.8628740176711904],
[5.6773029528733403, 1.1742755566739300, 6.8151068033293702],
[6.8151068033293702, 5.6773029528733403, 1.1742755566739300],
[8.3884924301184203, 8.2027323927494997, 2.1265083423321101],
[3.6998939691211699, 5.8628740176711904, 7.2504996070913199],
[1.1742755566739300, 6.8151068033293702, 5.6773029528733403],
[2.1265083423321101, 8.3884924301184203, 8.2027323927494997],
[5.8628740176711904, 7.2504996070913199, 3.6998939691211699],
[1.9543543300807500, 1.9543543300807500, 1.9543543300807500],
[6.6427638185069302, 2.7342441309165002, 7.4228425919137502],
[7.4228425919137502, 6.6427638185069302, 2.7342441309165002],
[2.7342441309165002, 7.4228425919137502, 6.6427638185069302],
]
)
lattice = np.array( # lattice vectors in Bohr
[
[9.3771283249512098, 0.0000000000000000, 0.0000000000000000],
[0.0000000000000000, 9.3771283249512098, 0.0000000000000000],
[0.0000000000000000, 0.0000000000000000, 9.3771283249512098],
]
)
method = "r2SCAN"
model = DispersionModel(num, xyz, lattice)
model.set_ewald_summation()
res = model.get_dispersion(RationalDampingParam(method=method, atm=True), grad=False)
print(f"Dispersion energy for {method}-D3(BJ) is {res['energy']:13.10f} Hartree")
❯ $FC ewald.f90 $(pkg-config s-dftd3 mctc-lib --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122603293 Hartree
❯ $CC ewald.c $(pkg-config s-dftd3 --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122603293 Hartree
❯ python ewald.py
Dispersion energy for r2SCAN-D3(BJ) is -0.0122603293 Hartree
The same is available from the command line with
❯ s-dftd3 run --ewald --bj r2scan structure.poscar
Note that the pairwise decomposition and the analytical Hessian are only implemented for the real space summation and are rejected for such a model.
Choose how the reciprocal sum is evaluated#
By default the structure factors are interpolated on a mesh with cardinal B-splines and the sum is evaluated with a fast Fourier transform, which costs \(O(N \log N)\). Summing over the reciprocal lattice directly instead visits every atom at every wave vector and costs \(O(N^2)\).
The mesh is derived from the reciprocal cutoff unless it is given explicitly:
! explicit mesh, rounded up to a product of powers of two and three
call new_d3_model(disp, mol, lowrank=d3_lowrank_config(mesh=32))
! sum over the reciprocal lattice directly
call new_d3_model(disp, mol, lowrank=d3_lowrank_config(mesh=-1))
/* explicit mesh, rounded up to a product of powers of two and three */
dftd3_set_model_ewald(error, d3, 0, 0.0, 0.0, 32);
/* sum over the reciprocal lattice directly */
dftd3_set_model_ewald(error, d3, 0, 0.0, 0.0, -1);
# explicit mesh, rounded up to a product of powers of two and three
model.set_ewald_summation(mesh=32)
# sum over the reciprocal lattice directly
model.set_ewald_summation(mesh=-1)
Both reach the same energy for the cell above
Dispersion energy for r2SCAN-D3(BJ) is -0.0122603293 Hartree
From the command line the mesh is selected with --ewald-mesh and the direct summation with --ewald-direct
❯ s-dftd3 run --ewald-mesh 32 --bj r2scan structure.poscar
❯ s-dftd3 run --ewald-direct --bj r2scan structure.poscar
See Periodic systems: cutoffs and summation accuracy for how to choose the mesh.
Smooth the real space cutoff#
A hard cutoff makes the energy jump whenever an atom pair crosses it, which shows up as noise in a geometry optimization or a lattice scan. Giving the cutoff a smoothing width switches the pair contributions off over the last fraction of a Bohr instead, so the energy stays continuous. The width is the trailing argument for the two- and three-body cutoff and only changes the energy by the size of the contributions it damps.
program pbc_smooth
use, intrinsic :: iso_fortran_env, only : r8 => real64
use dftd3, only: d3_model, d3_param, rational_damping_param, get_rational_damping, &
& new_rational_damping, new_d3_model, get_dispersion, realspace_cutoff
use mctc_env, only: error_type
use mctc_io, only: structure_type, new
implicit none
character(len=:), allocatable :: method
type(structure_type) :: mol
type(error_type), allocatable :: error
integer, allocatable :: num(:)
real(r8), allocatable :: xyz(:, :)
real(r8) :: lattice(3, 3), energy
type(d3_model) :: disp
type(d3_param) :: inp
type(rational_damping_param) :: param
method = "r2SCAN"
num = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7]
xyz = reshape([ & ! coordinates in Bohr
& 3.5141339317522400_r8, 2.5620901186651399_r8, 0.9887044918760900_r8, &
& 2.5620901186651399_r8, 0.9887044918760900_r8, 3.5141339317522400_r8, &
& 0.9887044918760900_r8, 3.5141339317522400_r8, 2.5620901186651399_r8, &
& 8.2027323927494997_r8, 2.1265083423321101_r8, 8.3884924301184203_r8, &
& 7.2504996070913199_r8, 3.6998939691211699_r8, 5.8628740176711904_r8, &
& 5.6773029528733403_r8, 1.1742755566739300_r8, 6.8151068033293702_r8, &
& 6.8151068033293702_r8, 5.6773029528733403_r8, 1.1742755566739300_r8, &
& 8.3884924301184203_r8, 8.2027323927494997_r8, 2.1265083423321101_r8, &
& 3.6998939691211699_r8, 5.8628740176711904_r8, 7.2504996070913199_r8, &
& 1.1742755566739300_r8, 6.8151068033293702_r8, 5.6773029528733403_r8, &
& 2.1265083423321101_r8, 8.3884924301184203_r8, 8.2027323927494997_r8, &
& 5.8628740176711904_r8, 7.2504996070913199_r8, 3.6998939691211699_r8, &
& 1.9543543300807500_r8, 1.9543543300807500_r8, 1.9543543300807500_r8, &
& 6.6427638185069302_r8, 2.7342441309165002_r8, 7.4228425919137502_r8, &
& 7.4228425919137502_r8, 6.6427638185069302_r8, 2.7342441309165002_r8, &
& 2.7342441309165002_r8, 7.4228425919137502_r8, 6.6427638185069302_r8], &
& [3, size(num)])
lattice = reshape([ & ! lattice vectors in Bohr
& 9.3771283249512098_r8, 0.0000000000000000_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 9.3771283249512098_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 0.0000000000000000_r8, 9.3771283249512098_r8], &
& [3, 3])
call new(mol, num, xyz, lattice=lattice, charge=0.0_r8, uhf=0)
call get_rational_damping(inp, method, error, s9=1.0_r8)
if (allocated(error)) then
print "(2a)", "Error: ", error%message
return
end if
call new_rational_damping(param, inp)
call new_d3_model(disp, mol)
! the widths switch the pair contributions off over the last 0.05 Bohr
! before the respective cutoff
call get_dispersion(mol, disp, param, &
& realspace_cutoff(width2=0.05_r8, width3=0.05_r8), energy)
print "(3a, f13.10, a)", "Dispersion energy for ", method, "-D3(BJ) is ", energy, " Hartree"
end program pbc_smooth
#include <stdio.h>
#include <stdbool.h>
#include "dftd3.h"
int main(void)
{
dftd3_error error = dftd3_new_error();
dftd3_structure mol = NULL;
dftd3_model d3 = NULL;
dftd3_param param = NULL;
int nat = 16;
int num[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7};
double xyz[48] = { // coordinates in Bohr
3.5141339317522400, 2.5620901186651399, 0.9887044918760900,
2.5620901186651399, 0.9887044918760900, 3.5141339317522400,
0.9887044918760900, 3.5141339317522400, 2.5620901186651399,
8.2027323927494997, 2.1265083423321101, 8.3884924301184203,
7.2504996070913199, 3.6998939691211699, 5.8628740176711904,
5.6773029528733403, 1.1742755566739300, 6.8151068033293702,
6.8151068033293702, 5.6773029528733403, 1.1742755566739300,
8.3884924301184203, 8.2027323927494997, 2.1265083423321101,
3.6998939691211699, 5.8628740176711904, 7.2504996070913199,
1.1742755566739300, 6.8151068033293702, 5.6773029528733403,
2.1265083423321101, 8.3884924301184203, 8.2027323927494997,
5.8628740176711904, 7.2504996070913199, 3.6998939691211699,
1.9543543300807500, 1.9543543300807500, 1.9543543300807500,
6.6427638185069302, 2.7342441309165002, 7.4228425919137502,
7.4228425919137502, 6.6427638185069302, 2.7342441309165002,
2.7342441309165002, 7.4228425919137502, 6.6427638185069302};
double lattice[9] = { // lattice vectors in Bohr
9.3771283249512098, 0.0000000000000000, 0.0000000000000000,
0.0000000000000000, 9.3771283249512098, 0.0000000000000000,
0.0000000000000000, 0.0000000000000000, 9.3771283249512098};
mol = dftd3_new_structure(error, nat, num, xyz, lattice, NULL);
if (dftd3_check_error(error)) goto handle_error;
char method[7] = "r2SCAN";
param = dftd3_load_rational_damping(error, method, true);
if (dftd3_check_error(error)) goto handle_error;
d3 = dftd3_new_d3_model(error, mol);
if (dftd3_check_error(error)) goto handle_error;
// the two trailing arguments are the smoothing widths, all values in Bohr
dftd3_set_model_realspace_cutoff_smooth(error, d3, 60.0, 40.0, 40.0, 0.05, 0.05);
if (dftd3_check_error(error)) goto handle_error;
double energy;
dftd3_get_dispersion(error, mol, d3, param, &energy, NULL, NULL);
if (dftd3_check_error(error)) goto handle_error;
printf("Dispersion energy for %s-D3(BJ) is %13.10lf Hartree\n", method, energy);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 0;
handle_error: {
char msg[512];
dftd3_get_error(error, msg, NULL);
printf("Error: %s\n", msg);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 1;
}
}
import numpy as np
from dftd3.interface import RationalDampingParam, DispersionModel
num = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7])
xyz = np.array( # coordinates in Bohr
[
[3.5141339317522400, 2.5620901186651399, 0.9887044918760900],
[2.5620901186651399, 0.9887044918760900, 3.5141339317522400],
[0.9887044918760900, 3.5141339317522400, 2.5620901186651399],
[8.2027323927494997, 2.1265083423321101, 8.3884924301184203],
[7.2504996070913199, 3.6998939691211699, 5.8628740176711904],
[5.6773029528733403, 1.1742755566739300, 6.8151068033293702],
[6.8151068033293702, 5.6773029528733403, 1.1742755566739300],
[8.3884924301184203, 8.2027323927494997, 2.1265083423321101],
[3.6998939691211699, 5.8628740176711904, 7.2504996070913199],
[1.1742755566739300, 6.8151068033293702, 5.6773029528733403],
[2.1265083423321101, 8.3884924301184203, 8.2027323927494997],
[5.8628740176711904, 7.2504996070913199, 3.6998939691211699],
[1.9543543300807500, 1.9543543300807500, 1.9543543300807500],
[6.6427638185069302, 2.7342441309165002, 7.4228425919137502],
[7.4228425919137502, 6.6427638185069302, 2.7342441309165002],
[2.7342441309165002, 7.4228425919137502, 6.6427638185069302],
]
)
lattice = np.array( # lattice vectors in Bohr
[
[9.3771283249512098, 0.0000000000000000, 0.0000000000000000],
[0.0000000000000000, 9.3771283249512098, 0.0000000000000000],
[0.0000000000000000, 0.0000000000000000, 9.3771283249512098],
]
)
method = "r2SCAN"
model = DispersionModel(num, xyz, lattice)
model.set_realspace_cutoff(60.0, 40.0, 40.0, width2=0.05, width3=0.05)
res = model.get_dispersion(RationalDampingParam(method=method, atm=True), grad=False)
print(f"Dispersion energy for {method}-D3(BJ) is {res['energy']:13.10f} Hartree")
❯ $FC smooth.f90 $(pkg-config s-dftd3 mctc-lib --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446362 Hartree
❯ $CC smooth.c $(pkg-config s-dftd3 --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446362 Hartree
❯ python smooth.py
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446362 Hartree
Smoothing removes the discontinuity, it does not remove the truncation error. The result still differs from the reciprocal space value by about 1.6 · 10⁻⁵ Hartree for this cell.
Truncate the real space sum#
Without any further setup the lattice sum is truncated at the default cutoffs, 60 Bohr for the two-body term and 40 Bohr for the three-body term and the coordination number. This is the cheapest option and the one used by every non-periodic interface.
program pbc_energy
use, intrinsic :: iso_fortran_env, only : r8 => real64
use dftd3, only: d3_model, d3_param, rational_damping_param, get_rational_damping, &
& new_rational_damping, new_d3_model, get_dispersion, realspace_cutoff
use mctc_env, only: error_type
use mctc_io, only: structure_type, new
implicit none
character(len=:), allocatable :: method
type(structure_type) :: mol
type(error_type), allocatable :: error
integer, allocatable :: num(:)
real(r8), allocatable :: xyz(:, :)
real(r8) :: lattice(3, 3), energy
type(d3_model) :: disp
type(d3_param) :: inp
type(rational_damping_param) :: param
method = "r2SCAN"
num = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7]
xyz = reshape([ & ! coordinates in Bohr
& 3.5141339317522400_r8, 2.5620901186651399_r8, 0.9887044918760900_r8, &
& 2.5620901186651399_r8, 0.9887044918760900_r8, 3.5141339317522400_r8, &
& 0.9887044918760900_r8, 3.5141339317522400_r8, 2.5620901186651399_r8, &
& 8.2027323927494997_r8, 2.1265083423321101_r8, 8.3884924301184203_r8, &
& 7.2504996070913199_r8, 3.6998939691211699_r8, 5.8628740176711904_r8, &
& 5.6773029528733403_r8, 1.1742755566739300_r8, 6.8151068033293702_r8, &
& 6.8151068033293702_r8, 5.6773029528733403_r8, 1.1742755566739300_r8, &
& 8.3884924301184203_r8, 8.2027323927494997_r8, 2.1265083423321101_r8, &
& 3.6998939691211699_r8, 5.8628740176711904_r8, 7.2504996070913199_r8, &
& 1.1742755566739300_r8, 6.8151068033293702_r8, 5.6773029528733403_r8, &
& 2.1265083423321101_r8, 8.3884924301184203_r8, 8.2027323927494997_r8, &
& 5.8628740176711904_r8, 7.2504996070913199_r8, 3.6998939691211699_r8, &
& 1.9543543300807500_r8, 1.9543543300807500_r8, 1.9543543300807500_r8, &
& 6.6427638185069302_r8, 2.7342441309165002_r8, 7.4228425919137502_r8, &
& 7.4228425919137502_r8, 6.6427638185069302_r8, 2.7342441309165002_r8, &
& 2.7342441309165002_r8, 7.4228425919137502_r8, 6.6427638185069302_r8], &
& [3, size(num)])
lattice = reshape([ & ! lattice vectors in Bohr
& 9.3771283249512098_r8, 0.0000000000000000_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 9.3771283249512098_r8, 0.0000000000000000_r8, &
& 0.0000000000000000_r8, 0.0000000000000000_r8, 9.3771283249512098_r8], &
& [3, 3])
call new(mol, num, xyz, lattice=lattice, charge=0.0_r8, uhf=0)
call get_rational_damping(inp, method, error, s9=1.0_r8)
if (allocated(error)) then
print "(2a)", "Error: ", error%message
return
end if
call new_rational_damping(param, inp)
call new_d3_model(disp, mol)
! the defaults truncate the two-body sum at 60 Bohr and the coordination
! number at 40 Bohr, all cutoffs are given in Bohr
call get_dispersion(mol, disp, param, realspace_cutoff(), energy)
print "(3a, f13.10, a)", "Dispersion energy for ", method, "-D3(BJ) is ", energy, " Hartree"
end program pbc_energy
#include <stdio.h>
#include <stdbool.h>
#include "dftd3.h"
int main(void)
{
dftd3_error error = dftd3_new_error();
dftd3_structure mol = NULL;
dftd3_model d3 = NULL;
dftd3_param param = NULL;
int nat = 16;
int num[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7};
double xyz[48] = { // coordinates in Bohr
3.5141339317522400, 2.5620901186651399, 0.9887044918760900,
2.5620901186651399, 0.9887044918760900, 3.5141339317522400,
0.9887044918760900, 3.5141339317522400, 2.5620901186651399,
8.2027323927494997, 2.1265083423321101, 8.3884924301184203,
7.2504996070913199, 3.6998939691211699, 5.8628740176711904,
5.6773029528733403, 1.1742755566739300, 6.8151068033293702,
6.8151068033293702, 5.6773029528733403, 1.1742755566739300,
8.3884924301184203, 8.2027323927494997, 2.1265083423321101,
3.6998939691211699, 5.8628740176711904, 7.2504996070913199,
1.1742755566739300, 6.8151068033293702, 5.6773029528733403,
2.1265083423321101, 8.3884924301184203, 8.2027323927494997,
5.8628740176711904, 7.2504996070913199, 3.6998939691211699,
1.9543543300807500, 1.9543543300807500, 1.9543543300807500,
6.6427638185069302, 2.7342441309165002, 7.4228425919137502,
7.4228425919137502, 6.6427638185069302, 2.7342441309165002,
2.7342441309165002, 7.4228425919137502, 6.6427638185069302};
double lattice[9] = { // lattice vectors in Bohr
9.3771283249512098, 0.0000000000000000, 0.0000000000000000,
0.0000000000000000, 9.3771283249512098, 0.0000000000000000,
0.0000000000000000, 0.0000000000000000, 9.3771283249512098};
mol = dftd3_new_structure(error, nat, num, xyz, lattice, NULL);
if (dftd3_check_error(error)) goto handle_error;
char method[7] = "r2SCAN";
param = dftd3_load_rational_damping(error, method, true);
if (dftd3_check_error(error)) goto handle_error;
d3 = dftd3_new_d3_model(error, mol);
if (dftd3_check_error(error)) goto handle_error;
// the defaults truncate the two-body sum at 60 Bohr and the coordination
// number at 40 Bohr, all cutoffs are given in Bohr
dftd3_set_model_realspace_cutoff(error, d3, 60.0, 40.0, 40.0);
if (dftd3_check_error(error)) goto handle_error;
// to get the gradient and virial, provide arrays for the output
double energy, gradient[48], virial[9];
dftd3_get_dispersion(error, mol, d3, param, &energy, gradient, virial);
if (dftd3_check_error(error)) goto handle_error;
printf("Dispersion energy for %s-D3(BJ) is %13.10lf Hartree\n", method, energy);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 0;
handle_error: {
char msg[512];
dftd3_get_error(error, msg, NULL);
printf("Error: %s\n", msg);
dftd3_delete(error);
dftd3_delete(mol);
dftd3_delete(d3);
dftd3_delete(param);
return 1;
}
}
import numpy as np
from dftd3.interface import RationalDampingParam, DispersionModel
num = np.array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7])
xyz = np.array( # coordinates in Bohr
[
[3.5141339317522400, 2.5620901186651399, 0.9887044918760900],
[2.5620901186651399, 0.9887044918760900, 3.5141339317522400],
[0.9887044918760900, 3.5141339317522400, 2.5620901186651399],
[8.2027323927494997, 2.1265083423321101, 8.3884924301184203],
[7.2504996070913199, 3.6998939691211699, 5.8628740176711904],
[5.6773029528733403, 1.1742755566739300, 6.8151068033293702],
[6.8151068033293702, 5.6773029528733403, 1.1742755566739300],
[8.3884924301184203, 8.2027323927494997, 2.1265083423321101],
[3.6998939691211699, 5.8628740176711904, 7.2504996070913199],
[1.1742755566739300, 6.8151068033293702, 5.6773029528733403],
[2.1265083423321101, 8.3884924301184203, 8.2027323927494997],
[5.8628740176711904, 7.2504996070913199, 3.6998939691211699],
[1.9543543300807500, 1.9543543300807500, 1.9543543300807500],
[6.6427638185069302, 2.7342441309165002, 7.4228425919137502],
[7.4228425919137502, 6.6427638185069302, 2.7342441309165002],
[2.7342441309165002, 7.4228425919137502, 6.6427638185069302],
]
)
lattice = np.array(
[
[9.3771283249512098, 0.0000000000000000, 0.0000000000000000],
[0.0000000000000000, 9.3771283249512098, 0.0000000000000000],
[0.0000000000000000, 0.0000000000000000, 9.3771283249512098],
]
)
method = "r2SCAN"
model = DispersionModel(num, xyz, lattice)
res = model.get_dispersion(RationalDampingParam(method=method, atm=True), grad=False)
print(f"Dispersion energy for {method}-D3(BJ) is {res['energy']:13.10f} Hartree")
❯ $FC energy.f90 $(pkg-config s-dftd3 mctc-lib --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446595 Hartree
❯ $CC energy.c $(pkg-config s-dftd3 --cflags --libs) && ./a.out
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446595 Hartree
❯ python energy.py
Dispersion energy for r2SCAN-D3(BJ) is -0.0122446595 Hartree
Raising the two-body cutoff reduces the remaining error, see Periodic systems: cutoffs and summation accuracy for choosing a cutoff for a target accuracy.