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.Double.isNaN;
41  import static java.lang.String.format;
42  import static java.lang.System.arraycopy;
43  import static java.util.Arrays.fill;
44  import static org.apache.commons.math3.util.FastMath.log;
45  
46  import edu.rit.pj.ParallelTeam;
47  import ffx.algorithms.Terminatable;
48  import ffx.crystal.Crystal;
49  import ffx.crystal.HKL;
50  import ffx.crystal.ReflectionList;
51  import ffx.numerics.optimization.LBFGS;
52  import ffx.numerics.optimization.LineSearch.LineSearchResult;
53  import ffx.numerics.optimization.OptimizationListener;
54  import ffx.xray.CrystalReciprocalSpace.SolventModel;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  
58  /**
59   * ScaleBulkMinimize class.
60   *
61   * @author Timothy D. Fenn
62   * @since 1.0
63   */
64  public class ScaleBulkMinimize implements OptimizationListener, Terminatable {
65  
66    private static final Logger logger = Logger.getLogger(ScaleBulkMinimize.class.getName());
67    private static final double toSeconds = 1.0e-9;
68    private final ReflectionList reflectionlist;
69    private final DiffractionRefinementData refinementData;
70    private final Crystal crystal;
71    private final CrystalReciprocalSpace crystalReciprocalSpace;
72    private final ScaleBulkEnergy bulkSolventEnergy;
73    private final int solventN;
74    private final int n;
75    private final double[] x;
76    private final double[] grad;
77    private final double[] scaling;
78    private boolean done = false;
79    private boolean terminate = false;
80    private long time;
81    private double grms;
82    private int nSteps;
83  
84    /**
85     * Constructor for ScaleBulkMinimize.
86     *
87     * @param reflectionList a {@link ffx.crystal.ReflectionList} object.
88     * @param refinementData a {@link ffx.xray.DiffractionRefinementData} object.
89     * @param crystalReciprocalSpace a {@link ffx.xray.CrystalReciprocalSpace} object.
90     * @param parallelTeam the ParallelTeam to execute the ScaleBulkMinimize.
91     */
92    public ScaleBulkMinimize(
93        ReflectionList reflectionList,
94        DiffractionRefinementData refinementData,
95        CrystalReciprocalSpace crystalReciprocalSpace,
96        ParallelTeam parallelTeam) {
97      this.reflectionlist = reflectionList;
98      this.refinementData = refinementData;
99      this.crystal = reflectionList.crystal;
100     this.crystalReciprocalSpace = crystalReciprocalSpace;
101 
102     if (crystalReciprocalSpace.solventModel == SolventModel.NONE) {
103       solventN = 1;
104     } else {
105       solventN = 3;
106     }
107     n = solventN + refinementData.nScale;
108     bulkSolventEnergy = new ScaleBulkEnergy(reflectionList, refinementData, n, parallelTeam);
109 
110     x = new double[n];
111     grad = new double[n];
112     scaling = new double[n];
113     fill(scaling, 1.0);
114 
115     x[0] = refinementData.modelScaleK;
116     if (solventN > 1) {
117       x[1] = refinementData.bulkSolventK;
118       x[2] = refinementData.bulkSolventUeq;
119     }
120     for (int i = 0; i < 6; i++) {
121       if (crystal.scaleB[i] >= 0) {
122         x[solventN + crystal.scaleB[i]] = refinementData.modelAnisoB[i];
123       }
124     }
125 
126     setInitialScale();
127   }
128 
129   /**
130    * getCoordinates.
131    *
132    * @param x an array of {@link double} objects.
133    * @return an array of {@link double} objects.
134    */
135   public double[] getCoordinates(double[] x) {
136     if (x == null) {
137       x = new double[this.x.length];
138     }
139     arraycopy(this.x, 0, x, 0, this.x.length);
140     return x;
141   }
142 
143   /**
144    * getNumberOfVariables.
145    *
146    * @return a int.
147    */
148   public int getNumberOfVariables() {
149     return x.length;
150   }
151 
152   /** ksbsGridOptimize */
153   public void ksbsGridOptimize() {
154     if (solventN < 3) {
155       return;
156     }
157 
158     bulkSolventEnergy.setScaling(scaling);
159 
160     double min = Double.POSITIVE_INFINITY;
161     double bulkSolventK = refinementData.bulkSolventK;
162     double bulkSolventUeq = refinementData.bulkSolventUeq;
163     double kmin = 0.05;
164     double kmax = 0.9;
165     double kstep = 0.05;
166     double bmin = 10.0;
167     double bmax = 200.0;
168     double bstep = 10.0;
169     for (double i = kmin; i <= kmax; i += kstep) {
170       for (double j = bmin; j <= bmax; j += bstep) {
171         x[1] = i;
172         x[2] = j;
173         double sum = bulkSolventEnergy.energyAndGradient(x, grad);
174         logger.info(" ks: " + i + " bs: " + j + " sum: " + sum);
175         if (sum < min) {
176           min = sum;
177           bulkSolventK = i;
178           bulkSolventUeq = j;
179         }
180       }
181     }
182     logger.info(" minks: " + bulkSolventK + " minbs: " + bulkSolventUeq + " min: " + min);
183     refinementData.bulkSolventK = bulkSolventK;
184     refinementData.bulkSolventUeq = bulkSolventUeq;
185     bulkSolventEnergy.setScaling(null);
186   }
187 
188   /**
189    * minimize
190    *
191    * @return a {@link ffx.xray.ScaleBulkEnergy} object.
192    */
193   public ScaleBulkEnergy minimize() {
194     return minimize(0.5);
195   }
196 
197   /**
198    * minimize
199    *
200    * @param eps a double.
201    * @return a {@link ffx.xray.ScaleBulkEnergy} object.
202    */
203   public ScaleBulkEnergy minimize(double eps) {
204     return minimize(5, eps);
205   }
206 
207   /**
208    * minimize
209    *
210    * @param m a int.
211    * @param eps a double.
212    * @return a {@link ffx.xray.ScaleBulkEnergy} object.
213    */
214   public ScaleBulkEnergy minimize(int m, double eps) {
215     bulkSolventEnergy.setScaling(scaling);
216     double e = bulkSolventEnergy.energyAndGradient(x, grad);
217 
218     long mtime = -System.nanoTime();
219     time = -System.nanoTime();
220     done = false;
221     int status = LBFGS.minimize(n, m, x, e, grad, eps, bulkSolventEnergy, this);
222     done = true;
223     switch (status) {
224       case 0:
225         logger.info(format("\n Optimization achieved convergence criteria: %10.5f\n", grms));
226         break;
227       case 1:
228         logger.info(format("\n Optimization terminated at step %d.\n", nSteps));
229         break;
230       default:
231         logger.warning("\n Optimization failed.\n");
232     }
233     refinementData.modelScaleK = x[0] / scaling[0];
234     if (solventN > 1) {
235       refinementData.bulkSolventK = x[1] / scaling[1];
236       refinementData.bulkSolventUeq = x[2] / scaling[2];
237       if (crystalReciprocalSpace != null) {
238         refinementData.solventA = crystalReciprocalSpace.solventA;
239         refinementData.solventB = crystalReciprocalSpace.solventB;
240       }
241     }
242     for (int i = 0; i < 6; i++) {
243       int offset = crystal.scaleB[i];
244       if (offset >= 0) {
245         int index = solventN + offset;
246         refinementData.modelAnisoB[i] = x[index] / scaling[index];
247       }
248     }
249 
250     if (logger.isLoggable(Level.INFO)) {
251       mtime += System.nanoTime();
252       logger.info(format(" Optimization time: %8.3f (sec)\n", mtime * toSeconds));
253     }
254 
255     bulkSolventEnergy.setScaling(null);
256 
257     return bulkSolventEnergy;
258   }
259 
260   /** {@inheritDoc} */
261   @Override
262   public boolean optimizationUpdate(
263       int iter,
264       int nBFGS,
265       int nfun,
266       double grms,
267       double xrms,
268       double f,
269       double df,
270       double angle,
271       LineSearchResult info) {
272     long currentTime = System.nanoTime();
273     Double seconds = (currentTime - time) * 1.0e-9;
274     time = currentTime;
275     this.grms = grms;
276     this.nSteps = iter;
277 
278     if (iter == 0) {
279       if (nBFGS > 0) {
280         logger.info("\n Limited Memory BFGS Quasi-Newton Optimization of Fc to Fo Scaling Parameters\n");
281       } else {
282         logger.info("\n Steepest Decent Optimization of Fc to Fo Scaling Parameters\n");
283       }
284       logger.info(" Cycle       Energy      G RMS    Delta E   Delta X    Angle  Evals     Time");
285     }
286     if (info == null) {
287       logger.info(format("%6d %12.5f %10.6f", iter, f, grms));
288     } else {
289       if (info == LineSearchResult.Success) {
290         logger.info(format("%6d %12.5f %10.6f %10.6f %9.5f %8.2f %6d %8.3f",
291                 iter, f, grms, df, xrms, angle, nfun, seconds));
292       } else {
293         logger.info(format("%6d %12.5f %10.6f %10.6f %9.5f %8.2f %6d %8s",
294                 iter, f, grms, df, xrms, angle, nfun, info));
295       }
296     }
297     if (terminate) {
298       logger.info(" The optimization recieved a termination request.");
299       // Tell the L-BFGS optimizer to terminate.
300       return false;
301     }
302     return true;
303   }
304 
305   /** {@inheritDoc} */
306   @Override
307   public void terminate() {
308     terminate = true;
309     while (!done) {
310       synchronized (this) {
311         try {
312           wait(1);
313         } catch (Exception e) {
314           logger.log(Level.WARNING, "Exception terminating minimization.\n", e);
315         }
316       }
317     }
318   }
319 
320   /**
321    * getScaleBulkEnergy.
322    *
323    * @return a {@link ffx.xray.ScaleBulkEnergy} object.
324    */
325   ScaleBulkEnergy getScaleBulkEnergy() {
326     return bulkSolventEnergy;
327   }
328 
329   private void setInitialScale() {
330     double[][] fc = refinementData.fc;
331     double[][] fSigf = refinementData.fSigF;
332 
333     bulkSolventEnergy.setScaling(scaling);
334     double e = bulkSolventEnergy.energyAndGradient(x, grad);
335     bulkSolventEnergy.setScaling(null);
336 
337     double sumfofc = 0.0;
338     double sumfc = 0.0;
339     for (HKL ih : reflectionlist.hklList) {
340       int i = ih.getIndex();
341       if (isNaN(fc[i][0]) || isNaN(fSigf[i][0]) || fSigf[i][1] <= 0.0) {
342         continue;
343       }
344 
345       double fcTotF = refinementData.fcTotF(i);
346       sumfofc += fSigf[i][0] * fcTotF;
347       sumfc += fcTotF * fcTotF;
348     }
349 
350     x[0] = log(4.0 * sumfofc / sumfc);
351   }
352 
353   /** GridOptimize */
354   void gridOptimize() {
355     if (crystalReciprocalSpace == null) {
356       return;
357     }
358 
359     bulkSolventEnergy.setScaling(scaling);
360 
361     double min = Double.POSITIVE_INFINITY;
362     double solventA = crystalReciprocalSpace.solventA;
363     double solventB = crystalReciprocalSpace.solventB;
364     double amin = solventA - 1.0;
365     double amax = (solventA + 1.0) / 0.9999;
366     double astep = 0.25;
367     double bmin = solventB - 0.2;
368     double bmax = (solventB + 0.2) / 0.9999;
369     double bstep = 0.05;
370     if (crystalReciprocalSpace.solventModel == SolventModel.BINARY) {
371       amin = solventA - 0.2;
372       amax = (solventA + 0.2) / 0.9999;
373       astep = 0.05;
374     }
375 
376     logger.info(" Bulk Solvent Grid Search");
377     for (double i = amin; i <= amax; i += astep) {
378       for (double j = bmin; j <= bmax; j += bstep) {
379         crystalReciprocalSpace.setSolventAB(i, j);
380         crystalReciprocalSpace.computeDensity(refinementData.fs);
381         double sum = bulkSolventEnergy.energy(x);
382         logger.info(format(" A: %6.3f B: %6.3f Sum: %12.8f", i, j, sum));
383         if (sum < min) {
384           min = sum;
385           solventA = i;
386           solventB = j;
387         }
388       }
389     }
390 
391     logger.info(format("\n Minimum at\n A: %6.3f B: %6.3f Sum: %12.8f", solventA, solventB, min));
392     crystalReciprocalSpace.setSolventAB(solventA, solventB);
393     refinementData.solventA = solventA;
394     refinementData.solventB = solventB;
395     crystalReciprocalSpace.computeDensity(refinementData.fs);
396     bulkSolventEnergy.setScaling(null);
397   }
398 }