Coverage for gpaw/utilities/watermodel.py: 91%
70 statements
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
« prev ^ index » next coverage.py v7.7.1, created at 2025-07-12 00:18 +0000
1from ase.constraints import FixBondLengths
2from ase.calculators.tip3p import TIP3P
3from ase.calculators.tip3p import qH, sigma0, epsilon0
5from gpaw.cgpaw import adjust_positions, adjust_momenta, calculate_forces_H2O
6from ase.calculators.calculator import Calculator, all_changes
8import numpy as np
10A = 4 * epsilon0 * sigma0**12
11B = -4 * epsilon0 * sigma0**6
14class TIP3PWaterModel(TIP3P):
15 def calculate(self, atoms=None,
16 properties=['energy'],
17 system_changes=all_changes):
19 Calculator.calculate(self, atoms, properties, system_changes)
21 R = self.atoms.positions.reshape((-1, 3, 3))
22 Z = self.atoms.numbers
23 pbc = self.atoms.pbc
24 diagcell = self.atoms.cell.diagonal()
25 nh2o = len(R)
27 assert (self.atoms.cell == np.diag(diagcell)).all(), 'not orthorhombic'
28 assert ((diagcell >= 2 * self.rc) | ~pbc).all(), 'cutoff too large'
29 if Z[0] == 8:
30 o = 0
31 else:
32 o = 2
34 assert o == 0
35 assert (Z[o::3] == 8).all()
36 assert (Z[(o + 1) % 3::3] == 1).all()
37 assert (Z[(o + 2) % 3::3] == 1).all()
39 charges = np.array([qH, qH, qH])
40 charges[o] *= -2
42 energy = 0.0
43 forces = np.zeros((3 * nh2o, 3))
45 cellmat = np.zeros((3, 3))
46 np.fill_diagonal(cellmat, diagcell) # c code wants 3x3 cell ...
48 energy += calculate_forces_H2O(np.array(atoms.pbc, dtype=np.uint8),
49 cellmat, A, B, self.rc, self.width,
50 charges, self.atoms.get_positions(),
51 forces)
53 if self.pcpot:
54 e, f = self.pcpot.calculate(np.tile(charges, nh2o),
55 self.atoms.positions)
56 energy += e
57 forces += f
59 self.results['energy'] = energy
60 self.results['forces'] = forces
63class FixBondLengthsWaterModel(FixBondLengths):
65 def __init__(self, pairs, tolerance=1e-13, bondlengths=None,
66 iterations=None):
67 FixBondLengths.__init__(self, pairs, tolerance=tolerance,
68 bondlengths=bondlengths,
69 iterations=iterations)
71 def initialize_bond_lengths(self, atoms):
72 bondlengths = FixBondLengths.initialize_bond_lengths(self, atoms)
73 # Make sure that the constraints are compatible with the C-code
74 assert len(self.pairs) % 3 == 0
75 masses = atoms.get_masses()
77 self.start = self.pairs[0][0]
78 self.end = self.pairs[-3][0]
79 self.NW = (self.end - self.start) // 3 + 1
80 assert (self.end - self.start) % 3 == 0
81 for i in range(self.NW):
82 for j in range(3):
83 assert self.pairs[i * 3 + j][0] == self.start + i * 3 + j
84 assert self.pairs[i * 3 + j][1] == (self.start +
85 i * 3 + (j + 1) % 3)
86 assert abs(bondlengths[i * 3 + j] - bondlengths[j]) < 1e-6
87 assert masses[i * 3 + j + self.start] == masses[j + self.start]
88 return bondlengths
90 def select_indices(self, r):
91 return r[self.start:self.start + self.NW * 3, :]
93 def adjust_positions(self, atoms, new):
94 masses = atoms.get_masses()
96 if self.bondlengths is None:
97 self.bondlengths = self.initialize_bond_lengths(atoms)
99 return adjust_positions(self.bondlengths[:3],
100 masses[self.start:self.start + 3],
101 self.select_indices(atoms.get_positions()),
102 self.select_indices(new))
104 def adjust_momenta(self, atoms, p):
105 masses = atoms.get_masses()
107 if self.bondlengths is None:
108 self.bondlengths = self.initialize_bond_lengths(atoms)
110 return adjust_momenta(masses[self.start:self.start + 3],
111 self.select_indices(atoms.get_positions()),
112 self.select_indices(p))
114 def index_shuffle(self, atoms, ind):
115 raise NotImplementedError