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.xray;
39  
40  import static java.lang.String.format;
41  
42  import ffx.algorithms.AlgorithmListener;
43  import ffx.algorithms.Terminatable;
44  import ffx.numerics.optimization.LBFGS;
45  import ffx.numerics.optimization.LineSearch.LineSearchResult;
46  import ffx.numerics.optimization.OptimizationListener;
47  import ffx.potential.MolecularAssembly;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.Molecule;
50  import ffx.potential.bonded.Residue;
51  import ffx.realspace.RealSpaceData;
52  import java.util.List;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  
56  /**
57   * Refinement minimization class using {@link OptimizationListener} interface, constructs a {@link
58   * ffx.xray.RefinementEnergy} object for this purpose
59   *
60   * @author Timothy D. Fenn
61   * @since 1.0
62   */
63  public class RefinementMinimize implements OptimizationListener, Terminatable {
64  
65    private static final Logger logger = Logger.getLogger(RefinementMinimize.class.getName());
66    private static final double toSeconds = 1.0e-9;
67    public final RefinementEnergy refinementEnergy;
68    private final AlgorithmListener listener;
69    private final DataContainer dataContainer;
70    private final Atom[] activeAtomArray;
71    private final int nXYZ;
72    private final int nB;
73    private final int nOcc;
74    private final int n;
75    private final double[] x;
76    private final double[] grad;
77    private final double[] scaling;
78    private final RefinementMode refinementMode;
79    private boolean done = false;
80    private boolean terminate = false;
81    private long time;
82    private double grms;
83    private int nSteps;
84    private double eps = 0.1;
85  
86    /**
87     * constructor for refinement, assumes coordinates and B factor optimization
88     *
89     * @param data input {@link ffx.xray.DataContainer} that will be used as the model, must contain a
90     *     {@link ffx.xray.RefinementModel}
91     */
92    public RefinementMinimize(DataContainer data) {
93      this(data, RefinementMode.COORDINATES_AND_BFACTORS, null);
94    }
95  
96    /**
97     * constructor for refinement
98     *
99     * @param data input {@link ffx.xray.DataContainer} that will be used as the model, must contain a
100    *     {@link ffx.xray.RefinementModel} and either {@link ffx.xray.DiffractionData} or {@link
101    *     ffx.realspace.RealSpaceData}
102    * @param refinementmode {@link ffx.xray.RefinementMinimize.RefinementMode} for refinement
103    */
104   public RefinementMinimize(DataContainer data, RefinementMode refinementmode) {
105     this(data, refinementmode, null);
106   }
107 
108   /**
109    * constructor for refinement
110    *
111    * @param data input {@link ffx.xray.DataContainer} that will be used as the model, must contain a
112    *     {@link ffx.xray.RefinementModel} and either {@link ffx.xray.DiffractionData} or {@link
113    *     ffx.realspace.RealSpaceData}
114    * @param refinementMode {@link ffx.xray.RefinementMinimize.RefinementMode} for refinement
115    * @param listener {@link ffx.algorithms.AlgorithmListener} a listener for updates
116    */
117   public RefinementMinimize(
118       DataContainer data, RefinementMode refinementMode, AlgorithmListener listener) {
119     dataContainer = data;
120     this.listener = listener;
121     this.refinementMode = refinementMode;
122     refinementEnergy = new RefinementEnergy(data, refinementMode, null);
123     nXYZ = refinementEnergy.nXYZ;
124     nB = refinementEnergy.nBFactor;
125     nOcc = refinementEnergy.nOccupancy;
126     n = refinementEnergy.getNumberOfVariables();
127 
128     Atom[] atomArray = data.getAtomArray();
129     RefinementModel refinementModel = data.getRefinementModel();
130     int nAtoms = atomArray.length;
131 
132     // Fill an active atom array.
133     int count = 0;
134     for (Atom a : atomArray) {
135       if (a.isActive()) {
136         count++;
137       }
138     }
139     int nActive = count;
140     activeAtomArray = new Atom[count];
141     count = 0;
142     for (Atom a : atomArray) {
143       if (a.isActive()) {
144         activeAtomArray[count++] = a;
145       }
146     }
147 
148     x = new double[n];
149     grad = new double[n];
150     scaling = new double[n];
151 
152     refinementEnergy.getCoordinates(x);
153     refinementEnergy.setScaling(scaling);
154 
155     double xyzscale = 1.0;
156     double bisoscale = 1.0;
157     double anisouscale = 50.0;
158     double occscale = 15.0;
159 
160     if (refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
161         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
162       bisoscale = 0.4;
163       anisouscale = 40.0;
164     }
165 
166     if (refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
167         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
168       occscale = 10.0;
169     }
170 
171     // set up scaling
172     if (refinementMode == RefinementMode.COORDINATES
173         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
174         || refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
175         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
176       for (int i = 0; i < n; i++) {
177         scaling[i] = xyzscale;
178       }
179     }
180 
181     if (refinementMode == RefinementMode.BFACTORS
182         || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
183         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS
184         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
185       int i = nXYZ;
186       int resnum = -1;
187       if (data instanceof DiffractionData) {
188         DiffractionData diffractionData = (DiffractionData) data;
189         int nres = diffractionData.getnResidueBFactor() + 1;
190         for (Atom a : activeAtomArray) {
191           // Ignore hydrogens or inactive atoms.
192           if (a.getAtomicNumber() == 1) {
193             continue;
194           }
195           if (a.getAnisou(null) != null) {
196             for (int j = 0; j < 6; j++) {
197               scaling[i + j] = anisouscale;
198             }
199             i += 6;
200           } else if (diffractionData.isResidueBFactor()) {
201             if (resnum != a.getResidueNumber()) {
202               if (nres >= diffractionData.getnResidueBFactor()) {
203                 if (resnum > -1 && i < nXYZ + nB - 1) {
204                   i++;
205                 }
206                 if (i < nXYZ + nB) {
207                   scaling[i] = bisoscale;
208                 }
209                 nres = 1;
210               } else {
211                 nres++;
212               }
213               resnum = a.getResidueNumber();
214             }
215           } else {
216             scaling[i] = bisoscale;
217             i++;
218           }
219         }
220       } else {
221         logger.severe(" B refinement not supported for this data type!");
222       }
223     }
224 
225     if (refinementMode == RefinementMode.OCCUPANCIES
226         || refinementMode == RefinementMode.BFACTORS_AND_OCCUPANCIES
227         || refinementMode == RefinementMode.COORDINATES_AND_OCCUPANCIES
228         || refinementMode == RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES) {
229       if (nAtoms != nActive) {
230         logger.severe(" Occupancy refinement is not supported for inactive atoms.");
231       }
232       if (data instanceof DiffractionData) {
233 
234         int i = nXYZ + nB;
235         for (List<Residue> list : refinementModel.getAltResidues()) {
236           for (int j = 0; j < list.size(); j++) {
237             scaling[i] = occscale;
238             i++;
239           }
240         }
241         for (List<Molecule> list : refinementModel.getAltMolecules()) {
242           for (int j = 0; j < list.size(); j++) {
243             scaling[i] = occscale;
244             i++;
245           }
246         }
247       } else {
248         logger.severe(" Occupancy refinement not supported for this data type!");
249       }
250     }
251   }
252 
253   /**
254    * Parse a string into a refinement mode.
255    *
256    * @param str Refinement mode string.
257    * @return An instance of RefinementMode.
258    */
259   public static RefinementMode parseMode(String str) {
260     try {
261       return RefinementMode.valueOf(str.toUpperCase());
262     } catch (Exception e) {
263       logger.info(
264           format(
265               " Could not parse %s as a refinement mode; defaulting to coordinates.", str));
266       return RefinementMode.COORDINATES;
267     }
268   }
269 
270   /**
271    * Getter for the field <code>eps</code>.
272    *
273    * @return a {@link java.lang.Double} object.
274    */
275   public Double getEps() {
276     boolean hasaniso = false;
277 
278     for (Atom a : activeAtomArray) {
279       // ignore hydrogens!!!
280       if (a.getAtomicNumber() == 1) {
281         continue;
282       }
283       if (a.getAnisou(null) != null) {
284         hasaniso = true;
285         break;
286       }
287     }
288 
289     switch (refinementMode) {
290       case COORDINATES:
291         eps = 0.4;
292         break;
293       case BFACTORS, BFACTORS_AND_OCCUPANCIES:
294         if (hasaniso) {
295           eps = 20.0;
296         } else {
297           eps = 0.01;
298         }
299         break;
300       case COORDINATES_AND_BFACTORS, COORDINATES_AND_BFACTORS_AND_OCCUPANCIES:
301         if (hasaniso) {
302           eps = 20.0;
303         } else {
304           eps = 0.2;
305         }
306         break;
307       case OCCUPANCIES:
308         eps = 0.1;
309         break;
310       case COORDINATES_AND_OCCUPANCIES:
311         eps = 0.2;
312         break;
313     }
314 
315     return eps;
316   }
317 
318   /**
319    * get the number of B factor parameters being fit
320    *
321    * @return the number of B factor parameters
322    */
323   public int getNB() {
324     return nB;
325   }
326 
327   /**
328    * get the number of occupancy parameters being fit
329    *
330    * @return the number of occupancy parameters
331    */
332   public int getNOcc() {
333     return nOcc;
334   }
335 
336   /**
337    * get the number of xyz parameters being fit
338    *
339    * @return the number of xyz parameters
340    */
341   public int getNXYZ() {
342     return nXYZ;
343   }
344 
345   /**
346    * minimize assuming an eps of 1.0 and Integer.MAX_VALUE cycles
347    *
348    * @return {@link ffx.xray.RefinementEnergy} result
349    */
350   public RefinementEnergy minimize() {
351     return minimize(1.0);
352   }
353 
354   /**
355    * minimize assuming Integer.MAX_VALUE cycles
356    *
357    * @param eps input gradient rms desired
358    * @return {@link ffx.xray.RefinementEnergy} result
359    */
360   public RefinementEnergy minimize(double eps) {
361     return minimize(7, eps, Integer.MAX_VALUE - 2);
362   }
363 
364   /**
365    * minimize assuming an eps of 1.0 and limited cycles
366    *
367    * @param maxiter maximum iterations allowed
368    * @return {@link ffx.xray.RefinementEnergy} result
369    */
370   public RefinementEnergy minimize(int maxiter) {
371     return minimize(7, 1.0, maxiter);
372   }
373 
374   /**
375    * minimize with input eps and cycles
376    *
377    * @param eps input gradient rms desired
378    * @param maxiter maximum iterations allowed
379    * @return {@link ffx.xray.RefinementEnergy} result
380    */
381   public RefinementEnergy minimize(double eps, int maxiter) {
382     return minimize(7, eps, maxiter);
383   }
384 
385   /**
386    * minimize with input cycles for matrix conditioning, eps and cycles
387    *
388    * @param m number of cycles of matrix updates
389    * @param eps input gradient rms desired
390    * @param maxiter maximum iterations allowed
391    * @return {@link ffx.xray.RefinementEnergy} result
392    */
393   public RefinementEnergy minimize(int m, double eps, int maxiter) {
394     String typestring;
395     if (dataContainer instanceof DiffractionData) {
396       typestring = "X-ray";
397     } else if (dataContainer instanceof RealSpaceData) {
398       typestring = "Real Space";
399     } else {
400       typestring = "null";
401     }
402 
403     logger.info(" Beginning " + typestring + " Refinement");
404     switch (refinementMode) {
405       case COORDINATES:
406         logger.info(" Mode: Coordinates");
407         break;
408       case BFACTORS:
409         logger.info(" Mode: B-Factors");
410         break;
411       case COORDINATES_AND_BFACTORS:
412         logger.info(" Mode: Coordinates and B-Factors");
413         break;
414       case OCCUPANCIES:
415         logger.info(" Mode: Occupancies");
416         break;
417       case COORDINATES_AND_OCCUPANCIES:
418         logger.info(" Mode: Coordinates and Occupancies");
419         break;
420       case BFACTORS_AND_OCCUPANCIES:
421         logger.info(" Mode: B-Factors and Occupancies");
422         break;
423       case COORDINATES_AND_BFACTORS_AND_OCCUPANCIES:
424         logger.info(" Mode: Coordinates, B-Factors and Occupancies");
425         break;
426     }
427     logger.info(" Number of Parameters: " + n);
428 
429     refinementEnergy.getCoordinates(x);
430 
431     // Scale coordinates.
432     for (int i = 0; i < n; i++) {
433       x[i] *= scaling[i];
434     }
435 
436     long mtime = -System.nanoTime();
437     time = -System.nanoTime();
438     done = false;
439     int status;
440     double e = refinementEnergy.energyAndGradient(x, grad);
441     status = LBFGS.minimize(n, m, x, e, grad, eps, maxiter, refinementEnergy, this);
442     done = true;
443     switch (status) {
444       case 0:
445         logger.info(format("\n Optimization achieved convergence criteria: %8.5f", grms));
446         break;
447       case 1:
448         logger.info(format("\n Optimization terminated at step %d.", nSteps));
449         break;
450       default:
451         logger.warning("\n Optimization failed.");
452     }
453 
454     if (logger.isLoggable(Level.INFO)) {
455       StringBuilder sb = new StringBuilder();
456       mtime += System.nanoTime();
457       sb.append(format(" Optimization time: %g (sec)", mtime * toSeconds));
458       logger.info(sb.toString());
459     }
460 
461     return refinementEnergy;
462   }
463 
464   /** {@inheritDoc} */
465   @Override
466   public boolean optimizationUpdate(
467       int iter,
468       int nBFGS,
469       int nfun,
470       double grms,
471       double xrms,
472       double f,
473       double df,
474       double angle,
475       LineSearchResult info) {
476     long currentTime = System.nanoTime();
477     Double seconds = (currentTime - time) * 1.0e-9;
478     time = currentTime;
479     this.grms = grms;
480     this.nSteps = iter;
481 
482     // Update display.
483     if (listener != null) {
484       MolecularAssembly[] molecularAssembly = dataContainer.getMolecularAssemblies();
485       for (MolecularAssembly ma : molecularAssembly) {
486         listener.algorithmUpdate(ma);
487       }
488     }
489 
490     if (iter == 0) {
491       if (nBFGS > 0) {
492         logger.info("\n Limited Memory BFGS Quasi-Newton Optimization: \n");
493       } else {
494         logger.info("\n Steepest Decent Optimization: \n");
495       }
496       logger.info(
497           " Cycle       Energy      G RMS    Delta E   Delta X    Angle  Evals     Time      "
498               + dataContainer.printOptimizationHeader());
499     }
500     if (info == null) {
501       logger.info(format("%6d %12.3f %10.3f", iter, f, grms));
502     } else if (info == LineSearchResult.Success) {
503       StringBuilder sb = new StringBuilder();
504       sb.append(format("%6d %12.3f %10.3f %10.3f %9.4f %8.2f %6d %8.3f ",
505               iter, f, grms, df, xrms, angle, nfun, seconds));
506       sb.append(dataContainer.printOptimizationUpdate());
507       logger.info(sb.toString());
508     } else {
509       logger.info(
510           format("%6d %12.3f %10.3f %10.3f %9.4f %8.2f %6d %8s",
511               iter, f, grms, df, xrms, angle, nfun, info));
512     }
513     if (terminate) {
514       logger.info(" The optimization recieved a termination request.");
515       // Tell the L-BFGS optimizer to terminate.
516       return false;
517     }
518     return true;
519   }
520 
521   /** {@inheritDoc} */
522   @Override
523   public void terminate() {
524     terminate = true;
525     while (!done) {
526       synchronized (this) {
527         try {
528           wait(1);
529         } catch (Exception e) {
530           logger.log(Level.WARNING, " Exception terminating minimization.\n", e);
531         }
532       }
533     }
534   }
535 
536   /** Different refinement mode selection types. */
537   public enum RefinementMode {
538 
539     /** refine coordinates only */
540     COORDINATES,
541     /** refine B factors only (if anisotropic, refined as such) */
542     BFACTORS,
543     /** refine coordinates and B factors (if anisotropic, refined as such) */
544     COORDINATES_AND_BFACTORS,
545     /** refine occupancies only (alternate conformers are constrained) */
546     OCCUPANCIES,
547     /** refine B factors and occupancies */
548     BFACTORS_AND_OCCUPANCIES,
549     /** refine coordinates and occupancies */
550     COORDINATES_AND_OCCUPANCIES,
551     /** refine all */
552     COORDINATES_AND_BFACTORS_AND_OCCUPANCIES
553   }
554 }