Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2import warnings
4from scipy.optimize import minimize
5from ase.parallel import world
6from ase.io.jsonio import write_json
7from ase.optimize.optimize import Optimizer
8from ase.optimize.gpmin.gp import GaussianProcess
9from ase.optimize.gpmin.kernel import SquaredExponential
10from ase.optimize.gpmin.prior import ConstantPrior
13class GPMin(Optimizer, GaussianProcess):
14 def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
15 prior=None, kernel=None, master=None, noise=None, weight=None,
16 scale=None, force_consistent=None, batch_size=None,
17 bounds=None, update_prior_strategy="maximum",
18 update_hyperparams=False):
19 """Optimize atomic positions using GPMin algorithm, which uses both
20 potential energies and forces information to build a PES via Gaussian
21 Process (GP) regression and then minimizes it.
23 Default behaviour:
24 --------------------
25 The default values of the scale, noise, weight, batch_size and bounds
26 parameters depend on the value of update_hyperparams. In order to get
27 the default value of any of them, they should be set up to None.
28 Default values are:
30 update_hyperparams = True
31 scale : 0.3
32 noise : 0.004
33 weight: 2.
34 bounds: 0.1
35 batch_size: 1
37 update_hyperparams = False
38 scale : 0.4
39 noise : 0.005
40 weight: 1.
41 bounds: irrelevant
42 batch_size: irrelevant
44 Parameters:
45 ------------------
47 atoms: Atoms object
48 The Atoms object to relax.
50 restart: string
51 JSON file used to store the training set. If set, file with
52 such a name will be searched and the data in the file incorporated
53 to the new training set, if the file exists.
55 logfile: file object or str
56 If *logfile* is a string, a file with that name will be opened.
57 Use '-' for stdout
59 trajectory: string
60 File used to store trajectory of atomic movement.
62 master: boolean
63 Defaults to None, which causes only rank 0 to save files. If
64 set to True, this rank will save files.
66 force_consistent: boolean or None
67 Use force-consistent energy calls (as opposed to the energy
68 extrapolated to 0 K). By default (force_consistent=None) uses
69 force-consistent energies if available in the calculator, but
70 falls back to force_consistent=False if not.
72 prior: Prior object or None
73 Prior for the GP regression of the PES surface
74 See ase.optimize.gpmin.prior
75 If *prior* is None, then it is set as the
76 ConstantPrior with the constant being updated
77 using the update_prior_strategy specified as a parameter
79 kernel: Kernel object or None
80 Kernel for the GP regression of the PES surface
81 See ase.optimize.gpmin.kernel
82 If *kernel* is None the SquaredExponential kernel is used.
83 Note: It needs to be a kernel with derivatives!!!!!
85 noise: float
86 Regularization parameter for the Gaussian Process Regression.
88 weight: float
89 Prefactor of the Squared Exponential kernel.
90 If *update_hyperparams* is False, changing this parameter
91 has no effect on the dynamics of the algorithm.
93 update_prior_strategy: string
94 Strategy to update the constant from the ConstantPrior
95 when more data is collected. It does only work when
96 Prior = None
98 options:
99 'maximum': update the prior to the maximum sampled energy
100 'init' : fix the prior to the initial energy
101 'average': use the average of sampled energies as prior
103 scale: float
104 scale of the Squared Exponential Kernel
106 update_hyperparams: boolean
107 Update the scale of the Squared exponential kernel
108 every batch_size-th iteration by maximizing the
109 marginal likelihood.
111 batch_size: int
112 Number of new points in the sample before updating
113 the hyperparameters.
114 Only relevant if the optimizer is executed in update_hyperparams
115 mode: (update_hyperparams = True)
117 bounds: float, 0<bounds<1
118 Set bounds to the optimization of the hyperparameters.
119 Let t be a hyperparameter. Then it is optimized under the
120 constraint (1-bound)*t_0 <= t <= (1+bound)*t_0
121 where t_0 is the value of the hyperparameter in the previous
122 step.
123 If bounds is False, no constraints are set in the optimization of
124 the hyperparameters.
126 .. warning:: The memory of the optimizer scales as O(n²N²) where
127 N is the number of atoms and n the number of steps.
128 If the number of atoms is sufficiently high, this
129 may cause a memory issue.
130 This class prints a warning if the user tries to
131 run GPMin with more than 100 atoms in the unit cell.
132 """
133 # Warn the user if the number of atoms is very large
134 if len(atoms) > 100:
135 warning = ('Possible Memory Issue. There are more than '
136 '100 atoms in the unit cell. The memory '
137 'of the process will increase with the number '
138 'of steps, potentially causing a memory issue. '
139 'Consider using a different optimizer.')
141 warnings.warn(warning)
143 # Give it default hyperparameters
144 if update_hyperparams: # Updated GPMin
145 if scale is None:
146 scale = 0.3
147 if noise is None:
148 noise = 0.004
149 if weight is None:
150 weight = 2.
151 if bounds is None:
152 self.eps = 0.1
153 elif bounds is False:
154 self.eps = None
155 else:
156 self.eps = bounds
157 if batch_size is None:
158 self.nbatch = 1
159 else:
160 self.nbatch = batch_size
161 else: # GPMin without updates
162 if scale is None:
163 scale = 0.4
164 if noise is None:
165 noise = 0.001
166 if weight is None:
167 weight = 1.
168 if bounds is not None:
169 warning = ('The parameter bounds is of no use '
170 'if update_hyperparams is False. '
171 'The value provided by the user '
172 'is being ignored.')
173 warnings.warn(warning, UserWarning)
174 if batch_size is not None:
175 warning = ('The parameter batch_size is of no use '
176 'if update_hyperparams is False. '
177 'The value provided by the user '
178 'is being ignored.')
179 warnings.warn(warning, UserWarning)
181 # Set the variables to something anyways
182 self.eps = False
183 self.nbatch = None
185 self.strategy = update_prior_strategy
186 self.update_hp = update_hyperparams
187 self.function_calls = 1
188 self.force_calls = 0
189 self.x_list = [] # Training set features
190 self.y_list = [] # Training set targets
192 Optimizer.__init__(self, atoms, restart, logfile,
193 trajectory, master, force_consistent)
194 if prior is None:
195 self.update_prior = True
196 prior = ConstantPrior(constant=None)
197 else:
198 self.update_prior = False
200 if kernel is None:
201 kernel = SquaredExponential()
202 GaussianProcess.__init__(self, prior, kernel)
203 self.set_hyperparams(np.array([weight, scale, noise]))
205 def acquisition(self, r):
206 e = self.predict(r)
207 return e[0], e[1:]
209 def update(self, r, e, f):
210 """Update the PES
212 Update the training set, the prior and the hyperparameters.
213 Finally, train the model
214 """
215 # update the training set
216 self.x_list.append(r)
217 f = f.reshape(-1)
218 y = np.append(np.array(e).reshape(-1), -f)
219 self.y_list.append(y)
221 # Set/update the constant for the prior
222 if self.update_prior:
223 if self.strategy == 'average':
224 av_e = np.mean(np.array(self.y_list)[:, 0])
225 self.prior.set_constant(av_e)
226 elif self.strategy == 'maximum':
227 max_e = np.max(np.array(self.y_list)[:, 0])
228 self.prior.set_constant(max_e)
229 elif self.strategy == 'init':
230 self.prior.set_constant(e)
231 self.update_prior = False
233 # update hyperparams
234 if (self.update_hp and self.function_calls % self.nbatch == 0 and
235 self.function_calls != 0):
236 self.fit_to_batch()
238 # build the model
239 self.train(np.array(self.x_list), np.array(self.y_list))
241 def relax_model(self, r0):
242 result = minimize(self.acquisition, r0, method='L-BFGS-B', jac=True)
243 if result.success:
244 return result.x
245 else:
246 self.dump()
247 raise RuntimeError("The minimization of the acquisition function "
248 "has not converged")
250 def fit_to_batch(self):
251 """Fit hyperparameters keeping the ratio noise/weight fixed"""
252 ratio = self.noise/self.kernel.weight
253 self.fit_hyperparameters(np.array(self.x_list),
254 np.array(self.y_list), eps=self.eps)
255 self.noise = ratio*self.kernel.weight
257 def step(self, f=None):
258 atoms = self.atoms
259 if f is None:
260 f = atoms.get_forces()
262 fc = self.force_consistent
263 r0 = atoms.get_positions().reshape(-1)
264 e0 = atoms.get_potential_energy(force_consistent=fc)
265 self.update(r0, e0, f)
267 r1 = self.relax_model(r0)
268 self.atoms.set_positions(r1.reshape(-1, 3))
269 e1 = self.atoms.get_potential_energy(force_consistent=fc)
270 f1 = self.atoms.get_forces()
271 self.function_calls += 1
272 self.force_calls += 1
273 count = 0
274 while e1 >= e0:
275 self.update(r1, e1, f1)
276 r1 = self.relax_model(r0)
278 self.atoms.set_positions(r1.reshape(-1, 3))
279 e1 = self.atoms.get_potential_energy(force_consistent=fc)
280 f1 = self.atoms.get_forces()
281 self.function_calls += 1
282 self.force_calls += 1
283 if self.converged(f1):
284 break
286 count += 1
287 if count == 30:
288 raise RuntimeError("A descent model could not be built")
289 self.dump()
291 def dump(self):
292 """Save the training set"""
293 if world.rank == 0 and self.restart is not None:
294 with open(self.restart, 'wb') as fd:
295 write_json(fd, (self.x_list, self.y_list))
297 def read(self):
298 self.x_list, self.y_list = self.load()