Coverage for /builds/ase/ase/ase/calculators/ff.py: 78.57%
140 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import numpy as np
5from ase.calculators.calculator import Calculator
6from ase.utils import ff
9class ForceField(Calculator):
10 implemented_properties = ['energy', 'forces']
11 nolabel = True
13 def __init__(self, morses=None, bonds=None, angles=None, dihedrals=None,
14 vdws=None, coulombs=None, **kwargs):
15 Calculator.__init__(self, **kwargs)
16 if (morses is None and
17 bonds is None and
18 angles is None and
19 dihedrals is None and
20 vdws is None and
21 coulombs is None):
22 raise ImportError(
23 "At least one of morses, bonds, angles, dihedrals,"
24 "vdws or coulombs lists must be defined!")
25 if morses is None:
26 self.morses = []
27 else:
28 self.morses = morses
29 if bonds is None:
30 self.bonds = []
31 else:
32 self.bonds = bonds
33 if angles is None:
34 self.angles = []
35 else:
36 self.angles = angles
37 if dihedrals is None:
38 self.dihedrals = []
39 else:
40 self.dihedrals = dihedrals
41 if vdws is None:
42 self.vdws = []
43 else:
44 self.vdws = vdws
45 if coulombs is None:
46 self.coulombs = []
47 else:
48 self.coulombs = coulombs
50 def calculate(self, atoms, properties, system_changes):
51 Calculator.calculate(self, atoms, properties, system_changes)
52 if system_changes:
53 for name in ['energy', 'forces', 'hessian']:
54 self.results.pop(name, None)
55 if 'energy' not in self.results:
56 energy = 0.0
57 for morse in self.morses:
58 i, j, e = ff.get_morse_potential_value(atoms, morse)
59 energy += e
60 for bond in self.bonds:
61 i, j, e = ff.get_bond_potential_value(atoms, bond)
62 energy += e
63 for angle in self.angles:
64 i, j, k, e = ff.get_angle_potential_value(atoms, angle)
65 energy += e
66 for dihedral in self.dihedrals:
67 i, j, k, l, e = ff.get_dihedral_potential_value(
68 atoms, dihedral)
69 energy += e
70 for vdw in self.vdws:
71 i, j, e = ff.get_vdw_potential_value(atoms, vdw)
72 energy += e
73 for coulomb in self.coulombs:
74 i, j, e = ff.get_coulomb_potential_value(atoms, coulomb)
75 energy += e
76 self.results['energy'] = energy
77 if 'forces' not in self.results:
78 forces = np.zeros(3 * len(atoms))
79 for morse in self.morses:
80 i, j, g = ff.get_morse_potential_gradient(atoms, morse)
81 limits = get_limits([i, j])
82 for gb, ge, lb, le in limits:
83 forces[gb:ge] -= g[lb:le]
84 for bond in self.bonds:
85 i, j, g = ff.get_bond_potential_gradient(atoms, bond)
86 limits = get_limits([i, j])
87 for gb, ge, lb, le in limits:
88 forces[gb:ge] -= g[lb:le]
89 for angle in self.angles:
90 i, j, k, g = ff.get_angle_potential_gradient(atoms, angle)
91 limits = get_limits([i, j, k])
92 for gb, ge, lb, le in limits:
93 forces[gb:ge] -= g[lb:le]
94 for dihedral in self.dihedrals:
95 i, j, k, l, g = ff.get_dihedral_potential_gradient(
96 atoms, dihedral)
97 limits = get_limits([i, j, k, l])
98 for gb, ge, lb, le in limits:
99 forces[gb:ge] -= g[lb:le]
100 for vdw in self.vdws:
101 i, j, g = ff.get_vdw_potential_gradient(atoms, vdw)
102 limits = get_limits([i, j])
103 for gb, ge, lb, le in limits:
104 forces[gb:ge] -= g[lb:le]
105 for coulomb in self.coulombs:
106 i, j, g = ff.get_coulomb_potential_gradient(atoms, coulomb)
107 limits = get_limits([i, j])
108 for gb, ge, lb, le in limits:
109 forces[gb:ge] -= g[lb:le]
110 self.results['forces'] = np.reshape(forces, (len(atoms), 3))
111 if 'hessian' not in self.results:
112 hessian = np.zeros((3 * len(atoms), 3 * len(atoms)))
113 for morse in self.morses:
114 i, j, h = ff.get_morse_potential_hessian(atoms, morse)
115 limits = get_limits([i, j])
116 for gb1, ge1, lb1, le1 in limits:
117 for gb2, ge2, lb2, le2 in limits:
118 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
119 for bond in self.bonds:
120 i, j, h = ff.get_bond_potential_hessian(atoms, bond)
121 limits = get_limits([i, j])
122 for gb1, ge1, lb1, le1 in limits:
123 for gb2, ge2, lb2, le2 in limits:
124 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
125 for angle in self.angles:
126 i, j, k, h = ff.get_angle_potential_hessian(atoms, angle)
127 limits = get_limits([i, j, k])
128 for gb1, ge1, lb1, le1 in limits:
129 for gb2, ge2, lb2, le2 in limits:
130 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
131 for dihedral in self.dihedrals:
132 i, j, k, l, h = ff.get_dihedral_potential_hessian(
133 atoms, dihedral)
134 limits = get_limits([i, j, k, l])
135 for gb1, ge1, lb1, le1 in limits:
136 for gb2, ge2, lb2, le2 in limits:
137 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
138 for vdw in self.vdws:
139 i, j, h = ff.get_vdw_potential_hessian(atoms, vdw)
140 limits = get_limits([i, j])
141 for gb1, ge1, lb1, le1 in limits:
142 for gb2, ge2, lb2, le2 in limits:
143 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
144 for coulomb in self.coulombs:
145 i, j, h = ff.get_coulomb_potential_hessian(atoms, coulomb)
146 limits = get_limits([i, j])
147 for gb1, ge1, lb1, le1 in limits:
148 for gb2, ge2, lb2, le2 in limits:
149 hessian[gb1:ge1, gb2:ge2] += h[lb1:le1, lb2:le2]
150 self.results['hessian'] = hessian
152 def get_hessian(self, atoms=None):
153 return self.get_property('hessian', atoms)
156def get_limits(indices):
157 gstarts = []
158 gstops = []
159 lstarts = []
160 lstops = []
161 for l, g in enumerate(indices):
162 g3, l3 = 3 * g, 3 * l
163 gstarts.append(g3)
164 gstops.append(g3 + 3)
165 lstarts.append(l3)
166 lstops.append(l3 + 3)
167 return zip(gstarts, gstops, lstarts, lstops)