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 org.apache.commons.math3.util.FastMath.pow;
44  import static org.apache.commons.math3.util.FastMath.sqrt;
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.crystal.ReflectionSpline;
52  import ffx.numerics.math.ComplexNumber;
53  import ffx.numerics.optimization.LBFGS;
54  import ffx.numerics.optimization.LineSearch.LineSearchResult;
55  import ffx.numerics.optimization.OptimizationListener;
56  import java.util.logging.Level;
57  import java.util.logging.Logger;
58  
59  /**
60   * SigmaAMinimize class.
61   *
62   * @author Timothy D. Fenn
63   * @since 1.0
64   */
65  public class SigmaAMinimize implements OptimizationListener, Terminatable {
66  
67    private static final Logger logger = Logger.getLogger(SigmaAMinimize.class.getName());
68    private static final double toSeconds = 1.0e-9;
69    protected final DiffractionRefinementData refinementData;
70    private final ReflectionList reflectionList;
71    private final Crystal crystal;
72    private final SigmaAEnergy sigmaAEnergy;
73    private final int n;
74    private final double[] x;
75    private final double[] grad;
76    private final double[] scaling;
77    private boolean done = false;
78    private boolean terminate = false;
79    private long time;
80    private double grms;
81    private int nSteps;
82  
83    /**
84     * Constructor for SigmaAMinimize.
85     *
86     * @param reflectionList a {@link ffx.crystal.ReflectionList} object.
87     * @param refinementData a {@link ffx.xray.DiffractionRefinementData} object.
88     * @param parallelTeam the ParallelTeam to execute the SigmaAMinimize.
89     */
90    SigmaAMinimize(
91        ReflectionList reflectionList,
92        DiffractionRefinementData refinementData,
93        ParallelTeam parallelTeam) {
94      this.reflectionList = reflectionList;
95      this.refinementData = refinementData;
96      this.crystal = reflectionList.crystal;
97  
98      n = refinementData.nBins * 2;
99      sigmaAEnergy = new SigmaAEnergy(reflectionList, refinementData, parallelTeam);
100     x = new double[n];
101     grad = new double[n];
102     scaling = new double[n];
103 
104     for (int i = 0; i < refinementData.nBins; i++) {
105       // For optimization scaling, best to move to 0.0.
106       x[i] = refinementData.sigmaA[i] - 1.0;
107       scaling[i] = 1.0;
108       x[i + refinementData.nBins] = refinementData.sigmaW[i];
109       scaling[i + refinementData.nBins] = 2.0;
110     }
111 
112     // Generate Es
113     int type = SplineEnergy.Type.FCTOESQ;
114     SplineMinimize splineMinimize =
115         new SplineMinimize(reflectionList, refinementData, refinementData.esqFc, type);
116     splineMinimize.minimize(7, 1.0);
117 
118     type = SplineEnergy.Type.FOTOESQ;
119     splineMinimize = new SplineMinimize(reflectionList, refinementData, refinementData.esqFo, type);
120     splineMinimize.minimize(7, 1.0);
121 
122     setWEstimate();
123   }
124 
125   /**
126    * calculateLikelihoodFree
127    *
128    * @return a double.
129    */
130   public double calculateLikelihoodFree() {
131     sigmaAEnergy.setScaling(scaling);
132     double energy = sigmaAEnergy.energyAndGradient(x, grad);
133     sigmaAEnergy.setScaling(null);
134     return energy;
135   }
136 
137   /**
138    * getCoordinates.
139    *
140    * @param x an array of {@link double} objects.
141    * @return an array of {@link double} objects.
142    */
143   public double[] getCoordinates(double[] x) {
144     if (x == null) {
145       x = new double[this.x.length];
146     }
147     arraycopy(this.x, 0, x, 0, this.x.length);
148     return x;
149   }
150 
151   /**
152    * getNumberOfVariables.
153    *
154    * @return a int.
155    */
156   public int getNumberOfVariables() {
157     return x.length;
158   }
159 
160   /**
161    * minimize
162    *
163    * @return a {@link ffx.xray.SigmaAEnergy} object.
164    */
165   public SigmaAEnergy minimize() {
166     return minimize(0.5);
167   }
168 
169   /**
170    * minimize
171    *
172    * @param eps a double.
173    * @return a {@link ffx.xray.SigmaAEnergy} object.
174    */
175   public SigmaAEnergy minimize(double eps) {
176     return minimize(7, eps);
177   }
178 
179   /**
180    * minimize
181    *
182    * @param m a int.
183    * @param eps a double.
184    * @return a {@link ffx.xray.SigmaAEnergy} object.
185    */
186   public SigmaAEnergy minimize(int m, double eps) {
187 
188     sigmaAEnergy.setScaling(scaling);
189 
190     double e = sigmaAEnergy.energyAndGradient(x, grad);
191 
192     long mTime = -System.nanoTime();
193     time = -System.nanoTime();
194     done = false;
195     int status = LBFGS.minimize(n, m, x, e, grad, eps, sigmaAEnergy, this);
196     done = true;
197 
198     switch (status) {
199       case 0:
200         logger.info(format("\n Optimization achieved convergence criteria: %8.5f\n", grms));
201         break;
202       case 1:
203         logger.info(format("\n Optimization terminated at step %d.\n", nSteps));
204         break;
205       default:
206         logger.warning("\n Optimization failed.\n");
207     }
208 
209     for (int i = 0; i < refinementData.nBins; i++) {
210       refinementData.sigmaA[i] = 1.0 + x[i] / scaling[i];
211       int index = i + refinementData.nBins;
212       refinementData.sigmaW[i] = x[index] / scaling[index];
213     }
214 
215     if (logger.isLoggable(Level.INFO)) {
216       StringBuilder sb = new StringBuilder();
217       mTime += System.nanoTime();
218       sb.append(format(" Optimization time: %8.3f (sec)\n", mTime * toSeconds));
219       logger.info(sb.toString());
220     }
221 
222     sigmaAEnergy.setScaling(null);
223     return sigmaAEnergy;
224   }
225 
226   /** {@inheritDoc} */
227   @Override
228   public boolean optimizationUpdate(
229       int iter,
230       int nBFGS,
231       int nfun,
232       double grms,
233       double xrms,
234       double f,
235       double df,
236       double angle,
237       LineSearchResult info) {
238     long currentTime = System.nanoTime();
239     Double seconds = (currentTime - time) * 1.0e-9;
240     time = currentTime;
241     this.grms = grms;
242     this.nSteps = iter;
243 
244     if (iter == 0) {
245       if (nBFGS > 0) {
246         logger.info("\n Limited Memory BFGS Quasi-Newton Optimization of SigmaA Parameters\n");
247       } else {
248         logger.info("\n Steepest Decent Optimization of SigmaA Parameters\n");
249       }
250       logger.info(" Cycle       Energy      G RMS    Delta E   Delta X    Angle  Evals     Time");
251     }
252     if (info == null) {
253       logger.info(format("%6d %12.2f %10.2f", iter, f, grms));
254     } else {
255       if (info == LineSearchResult.Success) {
256         logger.info(format("%6d %12.2f %10.2f %10.5f %9.5f %8.2f %6d %8.3f",
257                 iter, f, grms, df, xrms, angle, nfun, seconds));
258       } else {
259         logger.info(format("%6d %12.2f %10.2f %10.5f %9.5f %8.2f %6d %8s",
260                 iter, f, grms, df, xrms, angle, nfun, info));
261       }
262     }
263     if (terminate) {
264       logger.info(" The optimization recieved a termination request.");
265       // Tell the L-BFGS optimizer to terminate.
266       return false;
267     }
268     return true;
269   }
270 
271   /** {@inheritDoc} */
272   @Override
273   public void terminate() {
274     terminate = true;
275     while (!done) {
276       synchronized (this) {
277         try {
278           wait(1);
279         } catch (Exception e) {
280           logger.log(Level.WARNING, "Exception terminating minimization.\n", e);
281         }
282       }
283     }
284   }
285 
286   private void setWEstimate() {
287     // Generate initial w estimate
288     ReflectionSpline spline = new ReflectionSpline(reflectionList, refinementData.nBins);
289     int[] nMean = new int[refinementData.nBins];
290     for (int i = 0; i < refinementData.nBins; i++) {
291       nMean[i] = 0;
292     }
293     double mean = 0.0;
294     double tot = 0.0;
295     double[][] fcTot = refinementData.fcTot;
296     double[][] fSigF = refinementData.fSigF;
297     for (HKL ih : reflectionList.hklList) {
298       int i = ih.getIndex();
299       if (ih.getAllowed() == 0.0 || isNaN(fcTot[i][0]) || isNaN(fSigF[i][0])) {
300         continue;
301       }
302 
303       double s2 = crystal.invressq(ih);
304       double epsc = ih.epsilonc();
305       ComplexNumber fct = new ComplexNumber(fcTot[i][0], fcTot[i][1]);
306       double ecscale = spline.f(s2, refinementData.esqFc);
307       double eoscale = spline.f(s2, refinementData.esqFo);
308       double ec = fct.times(sqrt(ecscale)).abs();
309       double eo = fSigF[i][0] * sqrt(eoscale);
310       double wi = pow(eo - ec, 2.0) / epsc;
311 
312       nMean[spline.i1()]++;
313       tot++;
314 
315       x[spline.i1() + refinementData.nBins] +=
316           (wi - x[spline.i1() + refinementData.nBins]) / nMean[spline.i1()];
317 
318       mean += (wi - mean) / tot;
319     }
320     logger.info(format(" Starting mean w:    %8.3f", mean));
321     double initialScale = 0.01;
322     if (mean > 0.0) {
323       initialScale = 1.0 / mean;
324     }
325     logger.info(format(" Starting w scaling: %8.3f", initialScale));
326     for (int i = 0; i < refinementData.nBins; i++) {
327       x[i] -= x[i + refinementData.nBins];
328       x[i] *= scaling[i];
329       scaling[i + refinementData.nBins] = initialScale;
330       x[i + refinementData.nBins] *= scaling[i + refinementData.nBins];
331     }
332   }
333 
334   /**
335    * Getter for the field <code>sigmaAEnergy</code>.
336    *
337    * @return a {@link ffx.xray.SigmaAEnergy} object.
338    */
339   SigmaAEnergy getSigmaAEnergy() {
340     return sigmaAEnergy;
341   }
342 
343   /**
344    * calculateLikelihood
345    *
346    * @return a double.
347    */
348   double calculateLikelihood() {
349     sigmaAEnergy.setScaling(scaling);
350     sigmaAEnergy.energyAndGradient(x, grad);
351     sigmaAEnergy.setScaling(null);
352 
353     return refinementData.llkR;
354   }
355 }