root / ase / optimize / optimize.py
Historique | Voir | Annoter | Télécharger (5,59 ko)
1 |
"""Structure optimization. """
|
---|---|
2 |
|
3 |
import sys |
4 |
import pickle |
5 |
import time |
6 |
from math import sqrt |
7 |
from os.path import isfile |
8 |
|
9 |
import numpy as np |
10 |
|
11 |
from ase.parallel import rank, barrier |
12 |
from ase.io.trajectory import PickleTrajectory |
13 |
|
14 |
|
15 |
class Dynamics: |
16 |
"""Base-class for all MD and structure optimization classes.
|
17 |
|
18 |
Dynamics(atoms, logfile)
|
19 |
|
20 |
atoms: Atoms object
|
21 |
The Atoms object to operate on
|
22 |
logfile: file object or str
|
23 |
If *logfile* is a string, a file with that name will be opened.
|
24 |
Use '-' for stdout.
|
25 |
trajectory: Trajectory object or str
|
26 |
Attach trajectory object. If *trajectory* is a string a
|
27 |
PickleTrajectory will be constructed. Use *None* for no
|
28 |
trajectory.
|
29 |
"""
|
30 |
def __init__(self, atoms, logfile, trajectory): |
31 |
self.atoms = atoms
|
32 |
|
33 |
if rank != 0: |
34 |
logfile = None
|
35 |
elif isinstance(logfile, str): |
36 |
if logfile == '-': |
37 |
logfile = sys.stdout |
38 |
else:
|
39 |
logfile = open(logfile, 'a') |
40 |
self.logfile = logfile
|
41 |
|
42 |
self.observers = []
|
43 |
self.nsteps = 0 |
44 |
|
45 |
if trajectory is not None: |
46 |
if isinstance(trajectory, str): |
47 |
trajectory = PickleTrajectory(trajectory, 'w', atoms)
|
48 |
self.attach(trajectory)
|
49 |
|
50 |
def get_number_of_steps(self): |
51 |
return self.nsteps |
52 |
|
53 |
def insert_observer(self, function, position=0, interval=1, |
54 |
*args, **kwargs): |
55 |
"""Insert an observer."""
|
56 |
if not callable(function): |
57 |
function = function.write |
58 |
self.observers.insert(position, (function, interval, args, kwargs))
|
59 |
|
60 |
def attach(self, function, interval=1, *args, **kwargs): |
61 |
"""Attach callback function.
|
62 |
|
63 |
At every *interval* steps, call *function* with arguments
|
64 |
*args* and keyword arguments *kwargs*."""
|
65 |
|
66 |
if not hasattr(function, '__call__'): |
67 |
function = function.write |
68 |
self.observers.append((function, interval, args, kwargs))
|
69 |
|
70 |
def call_observers(self): |
71 |
for function, interval, args, kwargs in self.observers: |
72 |
if self.nsteps % interval == 0: |
73 |
function(*args, **kwargs) |
74 |
|
75 |
|
76 |
class Optimizer(Dynamics): |
77 |
"""Base-class for all structure optimization classes."""
|
78 |
def __init__(self, atoms, restart, logfile, trajectory): |
79 |
"""Structure optimizer object.
|
80 |
|
81 |
atoms: Atoms object
|
82 |
The Atoms object to relax.
|
83 |
restart: str
|
84 |
Filename for restart file. Default value is *None*.
|
85 |
logfile: file object or str
|
86 |
If *logfile* is a string, a file with that name will be opened.
|
87 |
Use '-' for stdout.
|
88 |
trajectory: Trajectory object or str
|
89 |
Attach trajectory object. If *trajectory* is a string a
|
90 |
PickleTrajectory will be constructed. Use *None* for no
|
91 |
trajectory.
|
92 |
"""
|
93 |
Dynamics.__init__(self, atoms, logfile, trajectory)
|
94 |
self.restart = restart
|
95 |
|
96 |
if restart is None or not isfile(restart): |
97 |
self.initialize()
|
98 |
else:
|
99 |
self.read()
|
100 |
barrier() |
101 |
def initialize(self): |
102 |
pass
|
103 |
|
104 |
def run(self, fmax=0.05, steps=100000000): |
105 |
"""Run structure optimization algorithm.
|
106 |
|
107 |
This method will return when the forces on all individual
|
108 |
atoms are less than *fmax* or when the number of steps exceeds
|
109 |
*steps*."""
|
110 |
|
111 |
self.fmax = fmax
|
112 |
step = 0
|
113 |
while step < steps:
|
114 |
f = self.atoms.get_forces()
|
115 |
self.log(f)
|
116 |
self.call_observers()
|
117 |
if self.converged(f): |
118 |
return
|
119 |
self.step(f)
|
120 |
self.nsteps += 1 |
121 |
step += 1
|
122 |
|
123 |
def converged(self, forces=None): |
124 |
"""Did the optimization converge?"""
|
125 |
if forces is None: |
126 |
forces = self.atoms.get_forces()
|
127 |
return (forces**2).sum(axis=1).max() < self.fmax**2 |
128 |
|
129 |
def log(self, forces): |
130 |
fmax = sqrt((forces**2).sum(axis=1).max()) |
131 |
e = self.atoms.get_potential_energy()
|
132 |
T = time.localtime() |
133 |
if self.logfile is not None: |
134 |
name = self.__class__.__name__
|
135 |
self.logfile.write('%s: %3d %02d:%02d:%02d %15.6f %12.4f\n' % |
136 |
(name, self.nsteps, T[3], T[4], T[5], e, fmax)) |
137 |
self.logfile.flush()
|
138 |
|
139 |
def dump(self, data): |
140 |
if rank == 0 and self.restart is not None: |
141 |
pickle.dump(data, open(self.restart, 'wb'), protocol=2) |
142 |
|
143 |
def load(self): |
144 |
return pickle.load(open(self.restart)) |
145 |
|
146 |
|
147 |
class NDPoly: |
148 |
def __init__(self, ndims=1, order=3): |
149 |
"""Multivariate polynomium.
|
150 |
|
151 |
ndims: int
|
152 |
Number of dimensions.
|
153 |
order: int
|
154 |
Order of polynomium."""
|
155 |
|
156 |
if ndims == 0: |
157 |
exponents = [()] |
158 |
else:
|
159 |
exponents = [] |
160 |
for i in range(order + 1): |
161 |
E = NDPoly(ndims - 1, order - i).exponents
|
162 |
exponents += [(i,) + tuple(e) for e in E] |
163 |
self.exponents = np.array(exponents)
|
164 |
self.c = None |
165 |
|
166 |
def __call__(self, *x): |
167 |
"""Evaluate polynomial at x."""
|
168 |
return np.dot(self.c, (x**self.exponents).prod(1)) |
169 |
|
170 |
def fit(self, x, y): |
171 |
"""Fit polynomium at points in x to values in y."""
|
172 |
A = (x**self.exponents[:, np.newaxis]).prod(2) |
173 |
self.c = np.linalg.solve(np.inner(A, A), np.dot(A, y))
|
174 |
|
175 |
|
176 |
def polyfit(x, y, order=3): |
177 |
"""Fit polynomium at points in x to values in y.
|
178 |
|
179 |
With D dimensions and N points, x must have shape (N, D) and y
|
180 |
must have length N."""
|
181 |
|
182 |
p = NDPoly(len(x[0]), order) |
183 |
p.fit(x, y) |
184 |
return p
|