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