View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.algorithms.optimize;
39  
40  import ffx.algorithms.AlgorithmListener;
41  import ffx.algorithms.Terminatable;
42  import ffx.numerics.Potential;
43  import ffx.numerics.optimization.LBFGS;
44  import ffx.numerics.optimization.LineSearch;
45  import ffx.numerics.optimization.OptimizationListener;
46  import ffx.potential.ForceFieldEnergy;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.extended.ExtendedSystem;
50  
51  import java.util.logging.Level;
52  import java.util.logging.Logger;
53  
54  import static java.lang.String.format;
55  import static java.util.Arrays.fill;
56  
57  /**
58   * Minimize the potential energy of a system to an RMS gradient per atom convergence criteria.
59   *
60   * @author Michael J. Schnieders
61   * @since 1.0
62   */
63  public class PhMinimize implements OptimizationListener, Terminatable {
64  
65    private static final Logger logger = Logger.getLogger(PhMinimize.class.getName());
66  
67    /** The MolecularAssembly being operated on. */
68    protected final MolecularAssembly molecularAssembly;
69    /** The potential energy to optimize. */
70    protected final Potential potential;
71    /** The extended system that contains the fictitious particle */
72    protected final ExtendedSystem esvSystem;
73    /** The AlgorithmListener to update the UI. */
74    protected final AlgorithmListener algorithmListener;
75    /** Number of variables. */
76    protected final int n;
77    /** Number of Extended System variables. */
78    protected final int nESV;
79    /** Current value of each variable. */
80    protected final double[] x;
81    /** Current value of each Extended System variable. */
82    protected double[] theta;
83    /** The gradient. */
84    protected final double[] grad;
85    /** The gradient. */
86    protected double[] gradESV;
87    /** Scaling applied to each variable. */
88    protected final double[] scaling;
89    /** A flag to indicate the algorithm is done. */
90    protected boolean done = false;
91    /** A flag to indicate the algorithm should be terminated. */
92    protected boolean terminate = false;
93    /** Minimization time in nanoseconds. */
94    protected long time;
95    /** The final potential energy. */
96    protected double energy;
97    /** The return status of the optimization. */
98    protected int status;
99    /** The number of optimization steps taken. */
100   protected int nSteps;
101   /** The final RMS gradient. */
102   double rmsGradient;
103 
104   /**
105    * Constructor for Minimize.
106    *
107    * @param molecularAssembly a {@link MolecularAssembly} object.
108    * @param potential a {@link Potential} object.
109    * @param algorithmListener a {@link AlgorithmListener} object.
110    */
111   public PhMinimize(MolecularAssembly molecularAssembly, Potential potential,
112       AlgorithmListener algorithmListener, ExtendedSystem esvSystem) {
113     this.molecularAssembly = molecularAssembly;
114     this.algorithmListener = algorithmListener;
115     this.potential = potential;
116     this.esvSystem = esvSystem;
117     n = potential.getNumberOfVariables();
118     nESV = esvSystem.getNumberOfVariables();
119     x = new double[n];
120     theta = new double[nESV];
121     grad = new double[n];
122     gradESV = new double[nESV];
123     scaling = new double[n];
124     fill(scaling, 12.0);
125   }
126 
127   /**
128    * Constructor for Minimize.
129    *
130    * @param molecularAssembly a {@link MolecularAssembly} object.
131    * @param algorithmListener a {@link AlgorithmListener} object.
132    */
133   public PhMinimize(MolecularAssembly molecularAssembly, AlgorithmListener algorithmListener,
134       ExtendedSystem esvSystem) {
135     this.molecularAssembly = molecularAssembly;
136     this.algorithmListener = algorithmListener;
137     this.esvSystem = esvSystem;
138     if (molecularAssembly.getPotentialEnergy() == null) {
139       molecularAssembly.setPotential(ForceFieldEnergy.energyFactory(molecularAssembly));
140     }
141     potential = molecularAssembly.getPotentialEnergy();
142     n = potential.getNumberOfVariables();
143     nESV = esvSystem.getNumberOfVariables();
144     x = new double[n];
145     theta = new double[nESV];
146     grad = new double[n];
147     gradESV = new double[nESV];
148     scaling = new double[n];
149     fill(scaling, 12.0);
150   }
151 
152   /**
153    * Getter for the field <code>energy</code>.
154    *
155    * @return a double.
156    */
157   public double getEnergy() {
158     return energy;
159   }
160 
161   /**
162    * getRMSGradient.
163    *
164    * @return a double.
165    */
166   public double getRMSGradient() {
167     return rmsGradient;
168   }
169 
170   /**
171    * Getter for the field <code>status</code>.
172    *
173    * @return The status of the optimization.
174    */
175   public int getStatus() {
176     return status;
177   }
178 
179   /**
180    * minimize
181    *
182    * @return a {@link Potential} object.
183    */
184   public Potential minimizeCoordinates() {
185     return minimizeCoordinates(7, 1.0, Integer.MAX_VALUE);
186   }
187 
188   /**
189    * minimize
190    *
191    * @param eps The convergence criteria.
192    * @return a {@link Potential} object.
193    */
194   public Potential minimizeCoordinates(double eps) {
195     return minimizeCoordinates(7, eps, Integer.MAX_VALUE);
196   }
197 
198   /**
199    * minimize
200    *
201    * @param eps The convergence criteria.
202    * @param maxIterations The maximum number of iterations.
203    * @return a {@link Potential} object.
204    */
205   public Potential minimizeCoordinates(double eps, int maxIterations) {
206     return minimizeCoordinates(7, eps, maxIterations);
207   }
208 
209   /**
210    * minimize
211    *
212    * @param m The number of previous steps used to estimate the Hessian.
213    * @param eps The convergence criteria.
214    * @param maxIterations The maximum number of iterations.
215    * @return a {@link Potential} object.
216    */
217   public Potential minimizeCoordinates(int m, double eps, int maxIterations) {
218     time = System.nanoTime();
219     potential.getCoordinates(x);
220     potential.setScaling(scaling);
221 
222     // Scale coordinates.
223     for (int i = 0; i < n; i++) {
224       x[i] *= scaling[i];
225     }
226 
227     done = false;
228     energy = potential.energyAndGradient(x, grad);
229 
230     if (logger.isLoggable(Level.FINE)) {
231       logger.fine(format(" Minimize initial energy: %16.8f", energy));
232     }
233 
234     status = LBFGS.minimize(n, m, x, energy, grad, eps, maxIterations, potential, this);
235     done = true;
236 
237     switch (status) {
238       case 0 -> logger.info(format("\n Optimization achieved convergence criteria: %8.5f", rmsGradient));
239       case 1 -> logger.info(format("\n Optimization terminated at step %d.", nSteps));
240       default -> logger.warning("\n Optimization failed.");
241     }
242 
243     potential.setScaling(null);
244     return potential;
245   }
246 
247   /**
248    * minimize
249    *
250    * @param eps The convergence criteria.
251    * @param maxIterations The maximum number of iterations.
252    * @return a {@link Potential} object.
253    */
254   public Potential minimizeTitration(double eps, int maxIterations) {
255     return minimizeTitration(7, eps, maxIterations);
256   }
257 
258   /**
259    * minimize
260    *
261    * @param m The number of previous steps used to estimate the Hessian.
262    * @param eps The convergence criteria.
263    * @param maxIterations The maximum number of iterations.
264    * @return a {@link Potential} object.
265    */
266   public Potential minimizeTitration(int m, double eps, int maxIterations) {
267     time = System.nanoTime();
268     theta = esvSystem.getThetaPosition();
269 
270     done = false;
271     potential.getCoordinates(x);
272     theta = esvSystem.getThetaPosition();
273     energy = esvSystem.energyAndGradient(theta, gradESV);
274 
275     if (logger.isLoggable(Level.FINE)) {
276       logger.fine(format(" Minimize initial energy: %16.8f", energy));
277     }
278 
279     status = LBFGS.minimize(nESV, m, theta, energy, gradESV, eps, maxIterations, esvSystem, this);
280     done = true;
281 
282     switch (status) {
283       case 0 -> {
284         logger.info(format("\n Optimization achieved convergence criteria: %8.5f", rmsGradient));
285         for (Atom atom : molecularAssembly.getAtomList()) {
286           int atomIndex = atom.getIndex() - 1;
287           atom.setOccupancy(esvSystem.getTitrationLambda(atomIndex));
288           atom.setTempFactor(esvSystem.getTautomerLambda(atomIndex));
289         }
290       }
291       case 1 -> logger.info(format("\n Optimization terminated at step %d.", nSteps));
292       default -> logger.warning("\n Optimization failed.");
293     }
294 
295     potential.setScaling(null);
296     return potential;
297   }
298 
299   /**
300    * {@inheritDoc}
301    *
302    * <p>Implement the OptimizationListener interface.
303    *
304    * @since 1.0
305    */
306   @Override
307   public boolean optimizationUpdate(int iteration, int nBFGS, int functionEvaluations,
308       double rmsGradient, double rmsCoordinateChange, double energy, double energyChange,
309       double angle, LineSearch.LineSearchResult lineSearchResult) {
310     long currentTime = System.nanoTime();
311     Double seconds = (currentTime - time) * 1.0e-9;
312     time = currentTime;
313     this.rmsGradient = rmsGradient;
314     this.nSteps = iteration;
315     this.energy = energy;
316 
317     if (iteration == 0) {
318       if (nBFGS > 0) {
319         logger.info("\n Limited Memory BFGS Quasi-Newton Optimization: \n");
320       } else {
321         logger.info("\n Steepest Decent Optimization: \n");
322       }
323       logger.info(" Cycle       Energy      G RMS    Delta E   Delta X    Angle  Evals     Time\n");
324     }
325     if (lineSearchResult == null) {
326       logger.info(format("%6d%13.4f%11.4f", iteration, energy, rmsGradient));
327     } else {
328       if (lineSearchResult == LineSearch.LineSearchResult.Success) {
329         logger.info(
330             format("%6d%13.4f%11.4f%11.4f%10.4f%9.2f%7d %8.3f", iteration, energy, rmsGradient,
331                 energyChange, rmsCoordinateChange, angle, functionEvaluations, seconds));
332       } else {
333         logger.info(format("%6d%13.4f%11.4f%11.4f%10.4f%9.2f%7d %8s", iteration, energy, rmsGradient,
334             energyChange, rmsCoordinateChange, angle, functionEvaluations, lineSearchResult));
335       }
336     }
337     // Update the listener and check for a termination request.
338     if (algorithmListener != null) {
339       algorithmListener.algorithmUpdate(molecularAssembly);
340     }
341 
342     if (terminate) {
343       logger.info(" The optimization recieved a termination request.");
344       // Tell the L-BFGS optimizer to terminate.
345       return false;
346     }
347     return true;
348   }
349 
350   /** {@inheritDoc} */
351   @Override
352   public void terminate() {
353     terminate = true;
354     while (!done) {
355       synchronized (this) {
356         try {
357           wait(1);
358         } catch (Exception e) {
359           logger.log(Level.WARNING, "Exception terminating minimization.\n", e);
360         }
361       }
362     }
363   }
364 }