Coverage for hiphive/calculators/zbl.py: 66%
73 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-11-28 11:20 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-11-28 11:20 +0000
1from ase.calculators.calculator import Calculator, all_changes
2from ase.neighborlist import NeighborList
3import numpy as np
5import numba
6from numba.typed import List
9alpha = 1 / 137.035999046 # no unit
10hbar = 6.582119569e-16 # eV * s
11c = 299792458 * 1e10 # m/s * A/m
12prefactor = alpha * hbar * c # eV * A
15@numba.njit
16def a(Z_i, Z_j):
17 return 0.46850 / (Z_i**0.23 + Z_j**0.23)
20@numba.njit
21def phi(x):
22 return (0.18175 * np.exp(-3.19980 * x)
23 + 0.50986 * np.exp(-0.94229 * x)
24 + 0.28022 * np.exp(-0.40290 * x)
25 + 0.02817 * np.exp(-0.20162 * x))
28@numba.njit
29def phi_derivative(x):
30 return (- 3.19980 * 0.18175 * np.exp(-3.19980 * x)
31 - 0.94229 * 0.50986 * np.exp(-0.94229 * x)
32 - 0.40290 * 0.28022 * np.exp(-0.40290 * x)
33 - 0.20162 * 0.02817 * np.exp(-0.20162 * x))
36@numba.njit
37def zbl_energy(Z_i, Z_j, r_ij):
38 return prefactor * Z_i * Z_j / r_ij * phi(r_ij / a(Z_i, Z_j))
41@numba.njit
42def zbl_force(Z_i, Z_j, r_ij):
43 return (-prefactor
44 * (Z_i * Z_j)
45 * (phi_derivative(r_ij / a(Z_i, Z_j)) / (r_ij * a(Z_i, Z_j))
46 - phi(r_ij / a(Z_i, Z_j)) / r_ij**2))
49@numba.njit
50def np_linalg_norm_axis1(mat):
51 norm = np.empty(len(mat), dtype=np.float64)
52 for i in range(len(mat)):
53 norm[i] = np.linalg.norm(mat[i])
54 return norm
57@numba.njit
58def inner_loop(ai, positions, cell, offsets, neighbors, forces, energy, numbers):
59 cells = np.dot(offsets, cell)
60 v_ij = positions[neighbors] + cells - positions[ai]
61 r_ij = np_linalg_norm_axis1(v_ij)
62 for aj, v, r in zip(neighbors, v_ij, r_ij):
63 energy[0] += zbl_energy(numbers[ai], numbers[aj], r)
64 force_j = v / r * zbl_force(numbers[ai], numbers[aj], r)
65 forces[aj] += force_j
66 forces[ai] -= force_j
69@numba.njit
70def outer_loop(positions, cell, offsets_list, neighbors_list, forces, energy, numbers):
71 for ai in range(len(positions)):
72 neighbors = neighbors_list[ai]
73 offsets = offsets_list[ai]
74 inner_loop(ai, positions, cell, offsets, neighbors, forces, energy, numbers)
77class ZBLCalculator(Calculator):
78 implemented_properties = ['energy', 'forces']
80 def __init__(self, cutoff, skin=1.0, **kwargs):
81 self._cutoff = cutoff
82 self._skin = skin
83 super().__init__(**kwargs)
85 def calculate(self, atoms=None, properties=['energy'],
86 system_changes=all_changes):
87 Calculator.calculate(self, atoms, properties, system_changes)
89 if not ('forces' in properties or 'energy' in properties): 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true
90 return
92 n_atoms = len(self.atoms)
94 if 'numbers' in system_changes:
95 self.nl = NeighborList([self._cutoff / 2] * n_atoms,
96 self_interaction=False,
97 skin=self._skin)
99 self.nl.update(self.atoms)
101 positions = self.atoms.positions
102 numbers = self.atoms.numbers
103 cell = self.atoms.cell.array
105 neighbors_list, offsets_list = List(), List()
106 for ai in range(n_atoms):
107 neighbors, offsets = self.nl.get_neighbors(ai)
108 neighbors_list.append(neighbors)
109 offsets_list.append(offsets.astype(np.float64))
111 energy = np.array([0.0])
112 forces = np.zeros((n_atoms, 3))
114 outer_loop(positions, cell, offsets_list, neighbors_list, forces, energy, numbers)
116 self.results['energy'] = energy[0]
117 self.results['forces'] = forces