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-2025.
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.xray;
39  
40  import ffx.algorithms.AlgorithmListener;
41  import ffx.algorithms.Terminatable;
42  import ffx.numerics.optimization.LBFGS;
43  import ffx.numerics.optimization.LineSearch.LineSearchResult;
44  import ffx.numerics.optimization.OptimizationListener;
45  import ffx.potential.MolecularAssembly;
46  import ffx.realspace.RealSpaceData;
47  import ffx.xray.refine.RefinementMode;
48  import ffx.xray.refine.RefinementModel;
49  
50  import javax.annotation.Nullable;
51  import java.util.logging.Level;
52  import java.util.logging.Logger;
53  
54  import static ffx.utilities.Constants.NS2SEC;
55  import static java.lang.String.format;
56  
57  /**
58   * The RefinementMinimize class is responsible for performing energy minimization for refinement
59   * tasks. It provides multiple methods for minimization, allowing customization of gradient
60   * root mean square (RMS) values, maximum iterations, and matrix conditioning settings if necessary.
61   * The class enables iterative optimization with monitoring capabilities through an optional listener,
62   * and supports graceful termination during the process.
63   * <p>
64   * This class implements both the OptimizationListener and Terminatable interfaces and
65   * acts as an intermediary for managing refinement energy computation and convergence behavior.
66   *
67   * @author Timothy D. Fenn
68   * @author Michael J. Schnieders
69   * @since 1.0
70   */
71  public class RefinementMinimize implements OptimizationListener, Terminatable {
72  
73    private static final Logger logger = Logger.getLogger(RefinementMinimize.class.getName());
74  
75    /**
76     * A {@link DataContainer} instance that serves as the primary data model for the
77     * refinement process in {@link RefinementMinimize}.
78     * <p>
79     * This data container must contain a valid {@link RefinementModel} and
80     * at least one type of data representation, such as {@link DiffractionData}
81     * or {@link RealSpaceData}. It is utilized to manage parameters, weights,
82     * and additional metadata necessary for optimization and energy calculations during
83     * refinement.
84     * <p>
85     * The data within this container supports methods for managing weights, printing
86     * optimization updates, and handling data-specific configurations.
87     */
88    private final DataContainer dataContainer;
89    /**
90     * The {@code listener} field is an instance of {@link AlgorithmListener}.
91     * It serves as an observer to handle updates during the progress of the refinement algorithm.
92     * The listener provides mechanisms for monitoring the algorithm's state or terminating
93     * the process based on user interaction or external conditions.
94     */
95    private final AlgorithmListener listener;
96    /**
97     * The {@code refinementModel} field represents the {@link RefinementModel}
98     * used in the optimization process during refinement. This model encapsulates
99     * structural and energy-related parameters crucial for refining the input data
100    * during the minimization workflows.
101    * <p>
102    * It is a required component of the {@link RefinementMinimize} class and is
103    * initialized during object construction to ensure accurate and efficient
104    * refinement operations.
105    * <p>
106    * The field is immutable and must be present in the associated
107    * {@link DataContainer} provided during the instantiation of
108    * {@link RefinementMinimize}.
109    */
110   private final RefinementModel refinementModel;
111   /**
112    * Represents the energy of the refinement process, utilized during the refinement and optimization
113    * steps in the {@link RefinementMinimize} class. This variable encapsulates the current state of
114    * the refinement energy, which may include contributions from diffraction or real-space data.
115    * <p>
116    * The {@link RefinementEnergy} instance is central to the refinement workflow, enabling evaluation,
117    * gradients, and convergence determination based on the energy values during minimization stages.
118    */
119   private final RefinementEnergy refinementEnergy;
120 
121   /**
122    * The variable n represents the number of data points or observations
123    * involved in the refinement process. It is a constant value, initialized
124    * during the creation of the RefinementMinimize object, and does not change
125    * during the lifetime of the object.
126    */
127   private final int n;
128   /**
129    * Stores the current set of variables for the minimization process.
130    * Represents an array of doubles used to describe the system's state
131    * during the refinement or optimization procedure.
132    * <p>
133    * This is immutable and finalized to maintain the integrity of the optimization
134    * algorithm and prevent unintended modifications during runtime.
135    */
136   private final double[] x;
137   /**
138    * The `grad` array stores the current gradient vector for the refinement process.
139    * It represents the gradient of the objective function with respect to the parameters
140    * being optimized during the minimization procedure.
141    * <p>
142    * This variable is crucial in determining the direction and magnitude of parameter adjustments
143    * to refine the model toward a minimized energy state. It is updated iteratively during the optimization process.
144    */
145   private final double[] grad;
146   /**
147    * This array defines scaling factors applied to specific problem variables during optimization.
148    * The scaling factors are used to normalize variable gradients, ensuring convergence and
149    * stability during numerical minimization. Scaling may depend on the problem's dimensionality
150    * or the relative magnitude of specific variable contributions to the objective function.
151    * <p>
152    * Initialized and used internally by the optimization algorithm to adjust step sizes or
153    * condition the optimization landscape.
154    */
155   private final double[] scaling;
156   /**
157    * A flag indicating whether the refinement process has completed.
158    * <p>
159    * The `done` variable is used within the `RefinementMinimize` class to track the
160    * completion status of the refinement procedure. It is set to `false` initially
161    * and updated to `true` when the process is finished.
162    */
163   private boolean done = false;
164   /**
165    * Flag indicating whether the optimization or refinement process should be terminated.
166    * <p>
167    * This variable is used to signal that the current operation should stop, typically in response
168    * to an external or internal condition. It is managed by the class and leveraged by relevant methods
169    * to ensure termination of iterative processes when required.
170    */
171   private boolean terminate = false;
172   /**
173    * A variable that represents the elapsed time or duration associated
174    * with the refinement process in milliseconds.
175    * <p>
176    * This variable may be used internally to measure or limit the
177    * computation time of the refinement operations, such as minimization
178    * or optimization processes.
179    */
180   private long time;
181   /**
182    * The grms variable represents the gradient root mean square (RMS) of the
183    * current optimization step. It is used as a measure of convergence in
184    * optimization algorithms, indicating how close the current system is to a
185    * locally optimal state. A lower value of grms typically corresponds to a
186    * better convergence during the refinement process.
187    */
188   private double grms;
189   /**
190    * The variable nSteps represents the number of steps or iterations completed during
191    * the refinement minimization process. This value is used to track the progress of
192    * the optimization algorithm and ensure it adheres to a defined maximum iteration limit.
193    */
194   private int nSteps;
195 
196   /**
197    * Constructor for the RefinementMinimize class, which initializes the refinement process
198    * with a specified data container and no algorithm listener.
199    *
200    * @param dataContainer the data container object containing refinement data and methods
201    */
202   public RefinementMinimize(DataContainer dataContainer) {
203     this(dataContainer, null);
204   }
205 
206   /**
207    * Constructor for the RefinementMinimize class, initializing the refinement process
208    * with a specified data container and an optional algorithm listener for updates.
209    *
210    * @param dataContainer     the data container object containing refinement data and methods
211    * @param algorithmListener an optional algorithm listener that provides updates during processing (can be null)
212    */
213   public RefinementMinimize(DataContainer dataContainer, @Nullable AlgorithmListener algorithmListener) {
214     this.dataContainer = dataContainer;
215     this.listener = algorithmListener;
216     this.refinementModel = dataContainer.getRefinementModel();
217 
218     // Create the target potential to optimize.
219     refinementEnergy = new RefinementEnergy(dataContainer);
220 
221     // Define the number of parameters to optimize.
222     n = refinementModel.getNumParameters();
223     x = new double[n];
224     grad = new double[n];
225     scaling = new double[n];
226     refinementModel.loadOptimizationScaling(scaling);
227 
228     refinementEnergy.getCoordinates(x);
229     refinementEnergy.setScaling(scaling);
230   }
231 
232   /**
233    * Minimizes the refinement energy using a default gradient root mean square (RMS) value of 1.0.
234    *
235    * @return a {@link RefinementEnergy} object representing the result of the minimization process.
236    */
237   public RefinementEnergy minimize() {
238     return minimize(1.0);
239   }
240 
241   /**
242    * Minimizes the refinement energy using a specified gradient root mean square (RMS) value.
243    *
244    * @param eps the desired gradient RMS value for the refinement process
245    * @return a {@link RefinementEnergy} object representing the result of the minimization process
246    */
247   public RefinementEnergy minimize(double eps) {
248     return minimize(7, eps, Integer.MAX_VALUE);
249   }
250 
251   /**
252    * Minimizes the refinement energy with a specified maximum number of iterations,
253    * using a gradient RMS value of 1.0 and default matrix conditioning cycles.
254    *
255    * @param maxIterations the maximum number of iterations allowed for the minimization process
256    * @return a {@link RefinementEnergy} object representing the result of the minimization process
257    */
258   public RefinementEnergy minimize(int maxIterations) {
259     return minimize(7, 1.0, maxIterations);
260   }
261 
262   /**
263    * Minimizes the refinement energy using a specified gradient root mean square (RMS) value
264    * and maximum number of iterations.
265    *
266    * @param eps           the desired gradient RMS value for the refinement process
267    * @param maxIterations the maximum number of iterations allowed for the minimization process
268    * @return a {@link RefinementEnergy} object representing the result of the minimization process
269    */
270   public RefinementEnergy minimize(double eps, int maxIterations) {
271     return minimize(7, eps, maxIterations);
272   }
273 
274   /**
275    * Minimizes the refinement energy using the provided matrix conditioning cycles, gradient RMS value,
276    * and a maximum number of iterations. The default number of matrix conditioning steps is 7, while
277    * 0 is for steepest decent.
278    *
279    * @param m       the number of matrix conditioning cycles for the optimization process
280    * @param eps     the desired gradient root mean square (RMS) value for convergence
281    * @param maxiter the maximum number of iterations allowed for the minimization process
282    * @return a {@link RefinementEnergy} object representing the outcome of the minimization process
283    */
284   public RefinementEnergy minimize(int m, double eps, int maxiter) {
285     if (dataContainer instanceof DiffractionData) {
286       logger.info(" Beginning X-ray Refinement");
287     } else if (dataContainer instanceof RealSpaceData) {
288       logger.info(" Beginning Real Space Refinement");
289     } else {
290       logger.info(" Beginning Refinement");
291     }
292 
293     RefinementMode refinementMode = refinementModel.getRefinementMode();
294     logger.info(refinementMode.toString());
295     logger.info(refinementModel.toString());
296 
297     refinementEnergy.getCoordinates(x);
298 
299     // Scale coordinates.
300     for (int i = 0; i < n; i++) {
301       x[i] *= scaling[i];
302     }
303 
304     long mtime = -System.nanoTime();
305     time = -System.nanoTime();
306     done = false;
307     int status;
308     double e = refinementEnergy.energyAndGradient(x, grad);
309     status = LBFGS.minimize(n, m, x, e, grad, eps, maxiter, refinementEnergy, this);
310     done = true;
311     switch (status) {
312       case 0:
313         logger.info(format("\n Optimization achieved convergence criteria: %8.5f", grms));
314         break;
315       case 1:
316         logger.info(format("\n Optimization terminated at step %d.", nSteps));
317         break;
318       default:
319         logger.warning("\n Optimization failed.");
320     }
321 
322     if (logger.isLoggable(Level.INFO)) {
323       StringBuilder sb = new StringBuilder();
324       mtime += System.nanoTime();
325       sb.append(format(" Optimization time: %g (sec)", mtime * NS2SEC));
326       logger.info(sb.toString());
327     }
328 
329     return refinementEnergy;
330   }
331 
332   /**
333    * {@inheritDoc}
334    */
335   @Override
336   public boolean optimizationUpdate(int iter, int nBFGS, int nfun, double grms, double xrms, double f,
337                                     double df, double angle, @Nullable LineSearchResult info) {
338 
339     long currentTime = System.nanoTime();
340     double seconds = (currentTime - time) * NS2SEC;
341     time = currentTime;
342     this.grms = grms;
343     this.nSteps = iter;
344 
345     // Update display.
346     if (listener != null) {
347       RefinementModel refinementModel = dataContainer.getRefinementModel();
348       MolecularAssembly[] molecularAssembly = refinementModel.getMolecularAssemblies();
349       for (MolecularAssembly ma : molecularAssembly) {
350         listener.algorithmUpdate(ma);
351       }
352     }
353 
354     if (iter == 0) {
355       if (nBFGS > 0) {
356         logger.info("\n Limited Memory BFGS Quasi-Newton Optimization: \n");
357       } else {
358         logger.info("\n Steepest Decent Optimization: \n");
359       }
360       logger.info(
361           " Cycle       Energy      G RMS    Delta E   Delta X    Angle  Evals     Time      "
362               + dataContainer.printOptimizationHeader());
363     }
364     if (info == null) {
365       logger.info(format("%6d %12.3f %10.3f", iter, f, grms));
366     } else if (info == LineSearchResult.Success) {
367       StringBuilder sb = new StringBuilder();
368       sb.append(format("%6d %12.3f %10.3f %10.3f %9.4f %8.2f %6d %8.3f ",
369           iter, f, grms, df, xrms, angle, nfun, seconds));
370       sb.append(dataContainer.printOptimizationUpdate());
371       logger.info(sb.toString());
372     } else {
373       logger.info(
374           format("%6d %12.3f %10.3f %10.3f %9.4f %8.2f %6d %8s",
375               iter, f, grms, df, xrms, angle, nfun, info));
376     }
377     if (terminate) {
378       logger.info(" The optimization received a termination request.");
379       // Tell the L-BFGS optimizer to terminate.
380       return false;
381     }
382     return true;
383   }
384 
385   /**
386    * {@inheritDoc}
387    */
388   @Override
389   public void terminate() {
390     terminate = true;
391     while (!done) {
392       synchronized (this) {
393         try {
394           wait(1);
395         } catch (Exception e) {
396           logger.log(Level.WARNING, " Exception terminating minimization.\n", e);
397         }
398       }
399     }
400   }
401 
402 }