1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.algorithms.optimize;
39
40 import ffx.algorithms.AlgorithmListener;
41 import ffx.algorithms.Terminatable;
42 import ffx.algorithms.dynamics.MDEngine;
43 import ffx.algorithms.dynamics.MolecularDynamics;
44 import ffx.numerics.Potential;
45 import ffx.numerics.optimization.LBFGS;
46 import ffx.numerics.optimization.LineSearch;
47 import ffx.numerics.optimization.OptimizationListener;
48 import ffx.potential.ForceFieldEnergy;
49 import ffx.potential.MolecularAssembly;
50 import ffx.potential.Platform;
51 import ffx.potential.openmm.OpenMMEnergy;
52 import org.apache.commons.configuration2.CompositeConfiguration;
53
54 import java.util.ArrayList;
55 import java.util.EnumSet;
56 import java.util.List;
57 import java.util.logging.Level;
58 import java.util.logging.Logger;
59
60 import static java.lang.String.format;
61 import static java.util.Arrays.fill;
62
63
64
65
66
67
68
69 public class Minimize implements OptimizationListener, Terminatable {
70
71 private static final Logger logger = Logger.getLogger(Minimize.class.getName());
72
73
74
75
76 protected final MolecularAssembly molecularAssembly;
77
78
79
80 protected final Potential potential;
81
82
83
84 protected final AlgorithmListener algorithmListener;
85
86
87
88 protected final int n;
89
90
91
92 protected final double[] x;
93
94
95
96 protected final double[] grad;
97
98
99
100 protected final double[] scaling;
101
102
103
104 protected boolean done = false;
105
106
107
108 protected boolean terminate = false;
109
110
111
112 protected long time;
113
114
115
116 protected List<Double> energyList = new ArrayList<>();
117
118
119
120 protected double energy;
121
122
123
124 protected int status;
125
126
127
128 protected int nSteps;
129
130
131
132 double rmsGradient;
133
134
135
136
137
138
139
140
141 public static final int DEFAULT_LBFGS_VECTORS = 7;
142
143
144
145
146
147
148
149
150 public Minimize(MolecularAssembly molecularAssembly, Potential potential,
151 AlgorithmListener algorithmListener) {
152 this.molecularAssembly = molecularAssembly;
153 this.algorithmListener = algorithmListener;
154 this.potential = potential;
155 n = potential.getNumberOfVariables();
156 x = new double[n];
157 grad = new double[n];
158 scaling = new double[n];
159 fill(scaling, 12.0);
160 }
161
162
163
164
165
166
167
168 public Minimize(MolecularAssembly molecularAssembly, AlgorithmListener algorithmListener) {
169 this.molecularAssembly = molecularAssembly;
170 this.algorithmListener = algorithmListener;
171 if (molecularAssembly.getPotentialEnergy() == null) {
172 molecularAssembly.setPotential(ForceFieldEnergy.energyFactory(molecularAssembly));
173 }
174 potential = molecularAssembly.getPotentialEnergy();
175 n = potential.getNumberOfVariables();
176 x = new double[n];
177 grad = new double[n];
178 scaling = new double[n];
179 fill(scaling, 12.0);
180 }
181
182 public static MinimizationEngine defaultEngine(MolecularAssembly molecularAssembly,
183 Potential potentialEnergy) {
184 CompositeConfiguration properties = molecularAssembly.getProperties();
185 String minimizeEngine = properties.getString("minimize-engine", null);
186 if (minimizeEngine != null) {
187 if (minimizeEngine.equalsIgnoreCase("OMM")) {
188 return MinimizationEngine.OPENMM;
189 } else {
190 return MinimizationEngine.FFX;
191 }
192 } else {
193 if (potentialEnergy instanceof OpenMMEnergy) {
194 return MinimizationEngine.OPENMM;
195 } else {
196 return MinimizationEngine.FFX;
197 }
198 }
199 }
200
201
202
203
204
205
206
207
208
209
210 public static Minimize minimizeFactory(MolecularAssembly assembly, Potential potentialEnergy,
211 AlgorithmListener listener, MinimizationEngine engine) {
212 return switch (engine) {
213 case OPENMM -> new MinimizeOpenMM(assembly, (OpenMMEnergy) potentialEnergy, listener);
214 default -> new Minimize(assembly, potentialEnergy, listener);
215 };
216 }
217
218
219
220
221
222
223 public double getEnergy() {
224 return energy;
225 }
226
227
228
229
230
231
232 public double getRMSGradient() {
233 return rmsGradient;
234 }
235
236
237
238
239
240
241 public int getStatus() {
242 return status;
243 }
244
245
246
247
248
249
250 public int getIterations() {
251 return nSteps;
252 }
253
254
255
256
257
258
259 public List<Double> getEnergyList() {
260 return energyList;
261 }
262
263
264
265
266
267
268 public Potential minimize() {
269 return minimize(DEFAULT_LBFGS_VECTORS, 1.0, Integer.MAX_VALUE);
270 }
271
272
273
274
275
276
277
278 public Potential minimize(double eps) {
279 return minimize(DEFAULT_LBFGS_VECTORS, eps, Integer.MAX_VALUE);
280 }
281
282
283
284
285
286
287
288
289 public Potential minimize(double eps, int maxIterations) {
290 return minimize(DEFAULT_LBFGS_VECTORS, eps, maxIterations);
291 }
292
293
294
295
296
297
298
299
300
301 public Potential minimize(int m, double eps, int maxIterations) {
302 time = System.nanoTime();
303 potential.getCoordinates(x);
304 potential.setScaling(scaling);
305
306
307 for (int i = 0; i < n; i++) {
308 x[i] *= scaling[i];
309 }
310
311 done = false;
312 energy = potential.energyAndGradient(x, grad);
313
314 if (logger.isLoggable(Level.FINE)) {
315 logger.fine(format(" Minimize initial energy: %16.8f", energy));
316 }
317
318 status = LBFGS.minimize(n, m, x, energy, grad, eps, maxIterations, potential, this);
319 done = true;
320
321 switch (status) {
322 case 0 -> logger.info(format("\n Optimization achieved convergence criteria: %8.5f", rmsGradient));
323 case 1 -> logger.info(format("\n Optimization terminated at step %d.", nSteps));
324 default -> logger.warning("\n Optimization failed.");
325 }
326
327 potential.setScaling(null);
328 return potential;
329 }
330
331
332
333
334 @Override
335 public String toString() {
336 StringBuilder sb = new StringBuilder();
337 sb.append(" Minimize Results:\n");
338 sb.append(format(" Number of Variables: %16d\n", n));
339 sb.append(format(" Number of Iterations: %16d\n", nSteps));
340 sb.append(format(" Final Energy: %16.6f (kcal/mol)\n", energy));
341 sb.append(format(" RMS Gradient: %16.6f (kcal/mol/A)\n", rmsGradient));
342 return sb.toString();
343 }
344
345
346
347
348
349
350
351
352 @Override
353 public boolean optimizationUpdate(int iteration, int nBFGS, int functionEvaluations,
354 double rmsGradient, double rmsCoordinateChange, double energy, double energyChange,
355 double angle, LineSearch.LineSearchResult lineSearchResult) {
356 long currentTime = System.nanoTime();
357 Double seconds = (currentTime - time) * 1.0e-9;
358 time = currentTime;
359 this.rmsGradient = rmsGradient;
360 this.nSteps = iteration;
361 this.energy = energy;
362
363 if (iteration == 0) {
364 energyList.clear();
365 if (nBFGS > 0) {
366 logger.info("\n Limited Memory BFGS Quasi-Newton Optimization: \n");
367 } else {
368 logger.info("\n Steepest Decent Optimization: \n");
369 }
370 logger.info(" Cycle Energy G RMS Delta E Delta X Angle Evals Time\n");
371 }
372 energyList.add(energy);
373 if (lineSearchResult == null) {
374 logger.info(format("%6d%13.4f%11.4f", iteration, energy, rmsGradient));
375 } else {
376 if (lineSearchResult == LineSearch.LineSearchResult.Success) {
377 logger.info(
378 format("%6d%13.4f%11.4f%11.4f%10.4f%9.2f%7d %8.3f", iteration, energy, rmsGradient,
379 energyChange, rmsCoordinateChange, angle, functionEvaluations, seconds));
380 } else {
381 logger.info(format("%6d%13.4f%11.4f%11.4f%10.4f%9.2f%7d %8s", iteration, energy, rmsGradient,
382 energyChange, rmsCoordinateChange, angle, functionEvaluations, lineSearchResult));
383 }
384 }
385
386 if (algorithmListener != null) {
387 algorithmListener.algorithmUpdate(molecularAssembly);
388 }
389
390 if (terminate) {
391 logger.info(" The optimization received a termination request.");
392
393 return false;
394 }
395 return true;
396 }
397
398
399
400
401 @Override
402 public void terminate() {
403 terminate = true;
404 while (!done) {
405 synchronized (this) {
406 try {
407 wait(1);
408 } catch (Exception e) {
409 logger.log(Level.WARNING, "Exception terminating minimization.\n", e);
410 }
411 }
412 }
413 }
414
415
416
417
418
419
420
421
422 public enum MinimizationEngine {
423 FFX(true, true), OPENMM(false, true);
424
425
426
427 private final EnumSet<Platform> platforms = EnumSet.noneOf(Platform.class);
428
429
430
431
432
433
434
435 MinimizationEngine(boolean ffx, boolean openMM) {
436 if (ffx) {
437 platforms.add(Platform.FFX);
438 }
439 if (openMM) {
440 platforms.add(Platform.OMM);
441 platforms.add(Platform.OMM_REF);
442 platforms.add(Platform.OMM_CUDA);
443 platforms.add(Platform.OMM_OPENCL);
444 platforms.add(Platform.OMM_CPU);
445 }
446 }
447
448
449
450
451
452
453 public EnumSet<Platform> getSupportedPlatforms() {
454 return EnumSet.copyOf(platforms);
455 }
456
457
458
459
460
461
462
463 public boolean supportsPlatform(Platform platform) {
464 return platforms.contains(platform);
465 }
466 }
467 }