Statistiques
| Révision :

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