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 }