root / ase / calculators / tip3p.py @ 4
Historique | Voir | Annoter | Télécharger (5,83 ko)
1 |
"""TIP3P potential, constraints and dynamics."""
|
---|---|
2 |
from math import pi, sin, cos |
3 |
|
4 |
import numpy as np |
5 |
|
6 |
import ase.units as units |
7 |
from ase.parallel import world |
8 |
from ase.md.md import MolecularDynamics |
9 |
|
10 |
qH = 0.417
|
11 |
sigma0 = 3.15061
|
12 |
epsilon0 = 0.1521 * units.kcal / units.mol
|
13 |
rOH = 0.9572
|
14 |
thetaHOH = 104.52 / 180 * pi |
15 |
|
16 |
|
17 |
class TIP3P: |
18 |
def __init__(self, rc=9.0, width=1.0): |
19 |
self.energy = None |
20 |
self.forces = None |
21 |
|
22 |
self.rc1 = rc - width
|
23 |
self.rc2 = rc
|
24 |
|
25 |
def get_spin_polarized(self): |
26 |
return False |
27 |
|
28 |
def update(self, atoms): |
29 |
if (self.energy is None or |
30 |
len(self.numbers) != len(atoms) or |
31 |
(self.numbers != atoms.get_atomic_numbers()).any()):
|
32 |
self.calculate(atoms)
|
33 |
elif ((self.positions != atoms.get_positions()).any() or |
34 |
(self.pbc != atoms.get_pbc()).any() or |
35 |
(self.cell != atoms.get_cell()).any()):
|
36 |
self.calculate(atoms)
|
37 |
|
38 |
def calculation_required(self, atoms, quantities): |
39 |
if len(quantities) == 0: |
40 |
return False |
41 |
|
42 |
return (self.energy is None or |
43 |
len(self.numbers) != len(atoms) or |
44 |
(self.numbers != atoms.get_atomic_numbers()).any() or |
45 |
(self.positions != atoms.get_positions()).any() or |
46 |
(self.pbc != atoms.get_pbc()).any() or |
47 |
(self.cell != atoms.get_cell()).any())
|
48 |
|
49 |
def get_potential_energy(self, atoms): |
50 |
self.update(atoms)
|
51 |
return self.energy |
52 |
|
53 |
def get_forces(self, atoms): |
54 |
self.update(atoms)
|
55 |
return self.forces.copy() |
56 |
|
57 |
def get_stress(self, atoms): |
58 |
raise NotImplementedError |
59 |
|
60 |
def calculate(self, atoms): |
61 |
self.positions = atoms.get_positions().copy()
|
62 |
self.cell = atoms.get_cell().copy()
|
63 |
self.pbc = atoms.get_pbc().copy()
|
64 |
natoms = len(atoms)
|
65 |
nH2O = natoms // 3
|
66 |
|
67 |
assert self.pbc.all() |
68 |
C = self.cell.diagonal()
|
69 |
assert not (self.cell - np.diag(C)).any() |
70 |
assert (C >= 2 * self.rc2).all() |
71 |
self.numbers = atoms.get_atomic_numbers()
|
72 |
Z = self.numbers.reshape((-1, 3)) |
73 |
assert (Z[:, 1:] == 1).all() and (Z[:, 0] == 8).all() |
74 |
|
75 |
R = self.positions.reshape((nH2O, 3, 3)) |
76 |
RO = R[:, 0]
|
77 |
|
78 |
self.energy = 0.0 |
79 |
self.forces = np.zeros((natoms, 3)) |
80 |
|
81 |
if world is None: |
82 |
mya = range(nH2O - 1) |
83 |
else:
|
84 |
rank = world.rank |
85 |
size = world.size |
86 |
assert nH2O // (2 * size) == 0 |
87 |
mynH2O = nH2O // 2 // size
|
88 |
mya = (range(rank * n, (rank + 1) * n) + |
89 |
range((size - rank - 1) * n, (size - rank) * n)) |
90 |
|
91 |
q = np.empty(3)
|
92 |
q[:] = qH * (units.Hartree * units.Bohr)**0.5
|
93 |
q[0] *= -2 |
94 |
|
95 |
for a in mya: |
96 |
DOO = (RO[a + 1:] - RO[a] + 0.5 * C) % C - 0.5 * C |
97 |
dOO = (DOO**2).sum(axis=1)**0.5 |
98 |
x1 = dOO > self.rc1
|
99 |
x2 = dOO < self.rc2
|
100 |
f = np.zeros(nH2O - a - 1)
|
101 |
f[x2] = 1.0
|
102 |
dfdd = np.zeros(nH2O - a - 1)
|
103 |
x12 = np.logical_and(x1, x2) |
104 |
d = (dOO[x12] - self.rc1) / (self.rc2 - self.rc1) |
105 |
f[x12] -= d**2 * (3.0 - 2.0 * d) |
106 |
dfdd[x12] -= 6.0 / (self.rc2 - self.rc1) * d * (1.0 - d) |
107 |
|
108 |
y = (sigma0 / dOO)**6
|
109 |
y2 = y**2
|
110 |
e = 4 * epsilon0 * (y2 - y)
|
111 |
self.energy += np.dot(e, f)
|
112 |
dedd = 24 * epsilon0 * (2 * y2 - y) / dOO * f - e * dfdd |
113 |
F = (dedd / dOO)[:, np.newaxis] * DOO |
114 |
self.forces[(a + 1) * 3::3] += F |
115 |
self.forces[a * 3] -= F.sum(axis=0) |
116 |
|
117 |
for i in range(3): |
118 |
D = (R[a + 1:] - R[a, i] + 0.5 * C) % C - 0.5 * C |
119 |
d = (D**2).sum(axis=2)**0.5 |
120 |
e = q[i] * q / d |
121 |
self.energy += np.dot(f, e).sum()
|
122 |
F = (e / d**2 * f[:, np.newaxis])[:, :, np.newaxis] * D
|
123 |
F[:, 0] -= (e.sum(axis=1) * dfdd / dOO)[:, np.newaxis] * DOO |
124 |
self.forces[(a + 1) * 3:] += F.reshape((-1, 3)) |
125 |
self.forces[a * 3 + i] -= F.sum(axis=0).sum(axis=0) |
126 |
|
127 |
if world is not None: |
128 |
self.energy = world.sum(self.energy) |
129 |
world.sum(self.forces)
|
130 |
|
131 |
|
132 |
class H2OConstraint: |
133 |
"""Constraint object for a rigid H2O molecule."""
|
134 |
def __init__(self, r=rOH, theta=thetaHOH, iterations=23, masses=None): |
135 |
self.r = r
|
136 |
self.theta = theta
|
137 |
self.iterations = iterations
|
138 |
self.m = masses
|
139 |
|
140 |
def set_masses(self, masses): |
141 |
self.m = masses
|
142 |
|
143 |
def adjust_positions(self, old, new): |
144 |
bonds = [(0, 1, self.r), (0, 2, self.r)] |
145 |
if self.theta: |
146 |
bonds.append((1, 2, sin(self.theta / 2) * self.r * 2)) |
147 |
for iter in range(self.iterations): |
148 |
for i, j, r in bonds: |
149 |
D = old[i::3] - old[j::3] |
150 |
m1 = self.m[i]
|
151 |
m2 = self.m[j]
|
152 |
a = new[i::3]
|
153 |
b = new[j::3]
|
154 |
B = a - b |
155 |
x = (D**2).sum(axis=1) |
156 |
y = (D * B).sum(axis=1)
|
157 |
z = (B**2).sum(axis=1) - r**2 |
158 |
k = m1 * m2 / (m1 + m2) * ((y**2 - x * z)**0.5 - y) / x |
159 |
k.shape = (-1, 1) |
160 |
a += k / m1 * D |
161 |
b -= k / m2 * D |
162 |
|
163 |
def adjust_forces(self, positions, forces): |
164 |
pass
|
165 |
|
166 |
def copy(self): |
167 |
return H2OConstraint(self.r, self.theta, self.iterations, self.m) |
168 |
|
169 |
|
170 |
class Verlet(MolecularDynamics): |
171 |
def step(self, f): |
172 |
atoms = self.atoms
|
173 |
m = atoms.get_masses()[:, np.newaxis] |
174 |
v = self.atoms.get_velocities()
|
175 |
r0 = atoms.get_positions() |
176 |
r = r0 + self.dt * v + self.dt**2 * f / m |
177 |
atoms.set_positions(r) |
178 |
r = atoms.get_positions() |
179 |
v = (r - r0) / self.dt
|
180 |
self.atoms.set_velocities(v)
|
181 |
return atoms.get_forces()
|