root / ase / calculators / emt.py @ 4
Historique | Voir | Annoter | Télécharger (11,07 ko)
1 |
"""Effective medium theory potential."""
|
---|---|
2 |
|
3 |
from math import sqrt, exp, log, pi |
4 |
|
5 |
import numpy as np |
6 |
import sys |
7 |
|
8 |
from ase.data import atomic_numbers, chemical_symbols |
9 |
from ase.units import Bohr |
10 |
|
11 |
|
12 |
parameters = { |
13 |
# E0 s0 V0 eta2 kappa lambda n0
|
14 |
# eV bohr eV bohr^-1 bohr^-1 bohr^-1 bohr^-3
|
15 |
'H': (-2.21, 0.71, 2.132, 1.652, 2.790, 1.892, 0.00547, 'dimer'), |
16 |
'Al': (-3.28, 3.00, 1.493, 1.240, 2.000, 1.169, 0.00700, 'fcc'), |
17 |
'Cu': (-3.51, 2.67, 2.476, 1.652, 2.740, 1.906, 0.00910, 'fcc'), |
18 |
'Ag': (-2.96, 3.01, 2.132, 1.652, 2.790, 1.892, 0.00547, 'fcc'), |
19 |
'Au': (-3.80, 3.00, 2.321, 1.674, 2.873, 2.182, 0.00703, 'fcc'), |
20 |
'Ni': (-4.44, 2.60, 3.673, 1.669, 2.757, 1.948, 0.01030, 'fcc'), |
21 |
'Pd': (-3.90, 2.87, 2.773, 1.818, 3.107, 2.155, 0.00688, 'fcc'), |
22 |
'Pt': (-5.85, 2.90, 4.067, 1.812, 3.145, 2.192, 0.00802, 'fcc'), |
23 |
'C': (-1.97, 1.18, 0.132, 3.652, 5.790, 2.892, 0.01322, 'dimer'), |
24 |
'N': (-4.97, 1.18, 0.132, 2.652, 3.790, 2.892, 0.01222, 'dimer'), |
25 |
'O': (-2.97, 1.25, 2.132, 3.652, 5.790, 4.892, 0.00850, 'dimer')} |
26 |
|
27 |
beta = 1.809 #(16 * pi / 3)**(1.0 / 3) / 2**0.5 |
28 |
# but preserve historical rounding.
|
29 |
|
30 |
#eta1 = 0.5 / Bohr
|
31 |
|
32 |
class EMT: |
33 |
disabled = False # Set to True to disable (asap does this). |
34 |
|
35 |
def __init__(self, fix_alloys=False): |
36 |
self.energy = None |
37 |
self.fix_alloys = fix_alloys
|
38 |
if self.disabled: |
39 |
print >>sys.stderr, """ |
40 |
ase.EMT has been disabled by Asap. Most likely, you
|
41 |
intended to use Asap's EMT calculator, but accidentally
|
42 |
imported ase's EMT calculator after Asap's. This could
|
43 |
happen if your script contains the lines
|
44 |
|
45 |
from asap3 import *
|
46 |
from ase.calculators.emt import EMT
|
47 |
Swap the two lines to solve the problem.
|
48 |
|
49 |
(or 'from ase import *' in older versions of ASE.) Swap
|
50 |
the two lines to solve the problem.
|
51 |
|
52 |
In the UNLIKELY event that you actually wanted to use
|
53 |
ase.calculators.emt.EMT although asap3 is loaded into memory, please
|
54 |
reactivate it with the command
|
55 |
ase.calculators.emt.EMT.disabled = False
|
56 |
"""
|
57 |
raise RuntimeError('ase.EMT has been disabled. ' + |
58 |
'See message printed above.')
|
59 |
|
60 |
def get_spin_polarized(self): |
61 |
return False |
62 |
|
63 |
def initialize(self, atoms): |
64 |
self.par = {}
|
65 |
self.rc = 0.0 |
66 |
self.numbers = atoms.get_atomic_numbers()
|
67 |
maxseq = 0.0
|
68 |
seen = {} |
69 |
for Z in self.numbers: |
70 |
if Z not in seen: |
71 |
seen[Z] = True
|
72 |
ss = parameters[chemical_symbols[Z]][1] * Bohr
|
73 |
if maxseq < ss:
|
74 |
maxseq = ss |
75 |
rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4)) |
76 |
rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4)) |
77 |
self.acut = np.log(9999.0) / (rr - rc) |
78 |
for Z in self.numbers: |
79 |
if Z not in self.par: |
80 |
p = parameters[chemical_symbols[Z]] |
81 |
s0 = p[1] * Bohr
|
82 |
eta2 = p[3] / Bohr
|
83 |
kappa = p[4] / Bohr
|
84 |
x = eta2 * beta * s0 |
85 |
gamma1 = 0.0
|
86 |
gamma2 = 0.0
|
87 |
if p[7] == 'fcc': |
88 |
for i, n in enumerate([12, 6, 24, 12]): |
89 |
r = s0 * beta * sqrt(i + 1)
|
90 |
x = n / (12 * (1.0 + exp(self.acut * (r - rc)))) |
91 |
gamma1 += x * exp(-eta2 * (r - beta * s0)) |
92 |
gamma2 += x * exp(-kappa / beta * (r - beta * s0)) |
93 |
elif p[7] == 'dimer': |
94 |
r = s0 * beta |
95 |
n = 1
|
96 |
x = n / (12 * (1.0 + exp(self.acut * (r - rc)))) |
97 |
gamma1 += x * exp(-eta2 * (r - beta * s0)) |
98 |
gamma2 += x * exp(-kappa / beta * (r - beta * s0)) |
99 |
else:
|
100 |
raise RuntimeError |
101 |
|
102 |
self.par[Z] = {'E0': p[0], |
103 |
's0': s0,
|
104 |
'V0': p[2], |
105 |
'eta2': eta2,
|
106 |
'kappa': kappa,
|
107 |
'lambda': p[5] / Bohr, |
108 |
'n0': p[6] / Bohr**3, |
109 |
'rc': rc,
|
110 |
'gamma1': gamma1,
|
111 |
'gamma2': gamma2}
|
112 |
#if rc + 0.5 > self.rc:
|
113 |
# self.rc = rc + 0.5
|
114 |
|
115 |
self.ksi = {}
|
116 |
for s1, p1 in self.par.items(): |
117 |
self.ksi[s1] = {}
|
118 |
for s2, p2 in self.par.items(): |
119 |
#self.ksi[s1][s2] = (p2['n0'] / p1['n0'] *
|
120 |
# exp(eta1 * (p1['s0'] - p2['s0'])))
|
121 |
self.ksi[s1][s2] = p2['n0'] / p1['n0'] |
122 |
|
123 |
self.forces = np.empty((len(atoms), 3)) |
124 |
self.sigma1 = np.empty(len(atoms)) |
125 |
self.deds = np.empty(len(atoms)) |
126 |
|
127 |
def update(self, atoms): |
128 |
if (self.energy is None or |
129 |
len(self.numbers) != len(atoms) or |
130 |
(self.numbers != atoms.get_atomic_numbers()).any()):
|
131 |
self.initialize(atoms)
|
132 |
self.calculate(atoms)
|
133 |
elif ((self.positions != atoms.get_positions()).any() or |
134 |
(self.pbc != atoms.get_pbc()).any() or |
135 |
(self.cell != atoms.get_cell()).any()):
|
136 |
self.calculate(atoms)
|
137 |
|
138 |
def calculation_required(self, atoms, quantities): |
139 |
if len(quantities) == 0: |
140 |
return False |
141 |
|
142 |
return (self.energy is None or |
143 |
len(self.numbers) != len(atoms) or |
144 |
(self.numbers != atoms.get_atomic_numbers()).any() or |
145 |
(self.positions != atoms.get_positions()).any() or |
146 |
(self.pbc != atoms.get_pbc()).any() or |
147 |
(self.cell != atoms.get_cell()).any())
|
148 |
|
149 |
def get_potential_energy(self, atoms): |
150 |
self.update(atoms)
|
151 |
return self.energy |
152 |
|
153 |
def get_numeric_forces(self, atoms): |
154 |
self.update(atoms)
|
155 |
p = atoms.positions |
156 |
p0 = p.copy() |
157 |
forces = np.empty_like(p) |
158 |
eps = 0.0001
|
159 |
for a in range(len(p)): |
160 |
for c in range(3): |
161 |
p[a, c] += eps |
162 |
self.calculate(atoms)
|
163 |
de = self.energy
|
164 |
p[a, c] -= 2 * eps
|
165 |
self.calculate(atoms)
|
166 |
de -= self.energy
|
167 |
p[a, c] += eps |
168 |
forces[a, c] = -de / (2 * eps)
|
169 |
p[:] = p0 |
170 |
return forces
|
171 |
|
172 |
def get_forces(self, atoms): |
173 |
self.update(atoms)
|
174 |
return self.forces.copy() |
175 |
|
176 |
def get_stress(self, atoms): |
177 |
raise NotImplementedError |
178 |
|
179 |
def calculate(self, atoms): |
180 |
self.positions = atoms.get_positions().copy()
|
181 |
self.cell = atoms.get_cell().copy()
|
182 |
self.pbc = atoms.get_pbc().copy()
|
183 |
|
184 |
icell = np.linalg.inv(self.cell)
|
185 |
scaled = np.dot(self.positions, icell)
|
186 |
N = [] |
187 |
for i in range(3): |
188 |
if self.pbc[i]: |
189 |
scaled[:, i] %= 1.0
|
190 |
v = icell[:, i] |
191 |
h = 1 / sqrt(np.dot(v, v))
|
192 |
N.append(int(self.rc / h) + 1) |
193 |
else:
|
194 |
N.append(0)
|
195 |
|
196 |
R = np.dot(scaled, self.cell)
|
197 |
|
198 |
self.energy = 0.0 |
199 |
self.sigma1[:] = 0.0 |
200 |
self.forces[:] = 0.0 |
201 |
|
202 |
N1, N2, N3 = N |
203 |
natoms = len(atoms)
|
204 |
for i1 in range(-N1, N1 + 1): |
205 |
for i2 in range(-N2, N2 + 1): |
206 |
for i3 in range(-N3, N3 + 1): |
207 |
C = np.dot((i1, i2, i3), self.cell)
|
208 |
Q = R + C |
209 |
c = (i1 == 0 and i2 == 0 and i3 == 0) |
210 |
for a1 in range(natoms): |
211 |
Z1 = self.numbers[a1]
|
212 |
p1 = self.par[Z1]
|
213 |
ksi = self.ksi[Z1]
|
214 |
for a2 in range(natoms): |
215 |
if c and a2 == a1: |
216 |
continue
|
217 |
d = Q[a2] - R[a1] |
218 |
r = sqrt(np.dot(d, d)) |
219 |
if r < self.rc + 0.5: |
220 |
Z2 = self.numbers[a2]
|
221 |
if self.fix_alloys: |
222 |
p2 = self.par[Z2]
|
223 |
else:
|
224 |
p2 = p1 # Old buggy behaviour
|
225 |
self.interact1(a1, a2, d, r, p1, p2, ksi[Z2])
|
226 |
|
227 |
for a in range(natoms): |
228 |
Z = self.numbers[a]
|
229 |
p = self.par[Z]
|
230 |
try:
|
231 |
ds = -log(self.sigma1[a] / 12) / (beta * p['eta2']) |
232 |
except (OverflowError, ValueError): |
233 |
self.deds[a] = 0.0 |
234 |
self.energy -= p['E0'] |
235 |
continue
|
236 |
x = p['lambda'] * ds
|
237 |
y = exp(-x) |
238 |
z = 6 * p['V0'] * exp(-p['kappa'] * ds) |
239 |
self.deds[a] = ((x * y * p['E0'] * p['lambda'] + p['kappa'] * z) / |
240 |
(self.sigma1[a] * beta * p['eta2'])) |
241 |
e = p['E0'] * ((1 + x) * y - 1) + z |
242 |
self.energy += p['E0'] * ((1 + x) * y - 1) + z |
243 |
|
244 |
for i1 in range(-N1, N1 + 1): |
245 |
for i2 in range(-N2, N2 + 1): |
246 |
for i3 in range(-N3, N3 + 1): |
247 |
C = np.dot((i1, i2, i3), self.cell)
|
248 |
Q = R + C |
249 |
c = (i1 == 0 and i2 == 0 and i3 == 0) |
250 |
for a1 in range(natoms): |
251 |
Z1 = self.numbers[a1]
|
252 |
p1 = self.par[Z1]
|
253 |
ksi = self.ksi[Z1]
|
254 |
for a2 in range(natoms): |
255 |
if c and a2 == a1: |
256 |
continue
|
257 |
d = Q[a2] - R[a1] |
258 |
r = sqrt(np.dot(d, d)) |
259 |
if r < self.rc + 0.5: |
260 |
Z2 = self.numbers[a2]
|
261 |
if self.fix_alloys: |
262 |
p2 = self.par[Z2]
|
263 |
else:
|
264 |
p2 = p1 # Old buggy behaviour
|
265 |
self.interact2(a1, a2, d, r, p1, p2, ksi[Z2])
|
266 |
|
267 |
def interact1(self, a1, a2, d, r, p1, p2, ksi): |
268 |
x = exp(self.acut * (r - self.rc)) |
269 |
theta = 1.0 / (1.0 + x) |
270 |
y = (0.5 * p1['V0'] * exp(-p2['kappa'] * (r / beta - p2['s0'])) * |
271 |
ksi / p1['gamma2'] * theta)
|
272 |
self.energy -= y
|
273 |
f = y * (p2['kappa'] / beta + self.acut * theta * x) * d / r |
274 |
self.forces[a1] += f
|
275 |
self.forces[a2] -= f
|
276 |
self.sigma1[a1] += (exp(-p2['eta2'] * (r - beta * p2['s0'])) * |
277 |
ksi * theta / p1['gamma1'])
|
278 |
|
279 |
def interact2(self, a1, a2, d, r, p1, p2, ksi): |
280 |
x = exp(self.acut * (r - self.rc)) |
281 |
theta = 1.0 / (1.0 + x) |
282 |
y = (exp(-p2['eta2'] * (r - beta * p2['s0'])) * |
283 |
ksi / p1['gamma1'] * theta * self.deds[a1]) |
284 |
f = y * (p2['eta2'] + self.acut * theta * x) * d / r |
285 |
self.forces[a1] -= f
|
286 |
self.forces[a2] += f
|
287 |
|