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.numerics.optimization;
39  
40  import static ffx.numerics.optimization.LBFGS.DEFAULT_ANGLEMAX;
41  import static ffx.numerics.optimization.LBFGS.DEFAULT_CAPPA;
42  import static ffx.numerics.optimization.LBFGS.DEFAULT_INTMAX;
43  import static ffx.numerics.optimization.LBFGS.DEFAULT_SLOPEMAX;
44  import static ffx.numerics.optimization.LBFGS.DEFAULT_STEPMAX;
45  import static ffx.numerics.optimization.LBFGS.DEFAULT_STEPMIN;
46  import static ffx.numerics.optimization.LBFGS.aV1PlusV2;
47  import static ffx.numerics.optimization.LBFGS.v1DotV2;
48  import static java.lang.System.arraycopy;
49  import static org.apache.commons.math3.util.FastMath.abs;
50  import static org.apache.commons.math3.util.FastMath.acos;
51  import static org.apache.commons.math3.util.FastMath.max;
52  import static org.apache.commons.math3.util.FastMath.min;
53  import static org.apache.commons.math3.util.FastMath.sqrt;
54  import static org.apache.commons.math3.util.FastMath.toDegrees;
55  
56  import ffx.numerics.OptimizationInterface;
57  
58  /**
59   * This class implements an algorithm for uni-dimensional line search. This file is a translation of
60   * FORTRAN code written by Jay Ponder.<br>
61   *
62   * @author Michael J. Schnieders <br> Derived from Jay Ponder's FORTRAN code (search.f).
63   * @since 1.0
64   */
65  public class LineSearch {
66  
67    /**
68     * Number of parameters to optimize.
69     */
70    private final int n;
71    /**
72     * Step direction.
73     */
74    private final double[] s;
75    /**
76     * Storage for a copy of the parameters.
77     */
78    private final double[] x0;
79    /**
80     * Implementation of the energy and gradient for the system.
81     */
82    private OptimizationInterface optimizationSystem;
83    /**
84     * Number of function evaluations (pass by reference).
85     */
86    private int[] functionEvaluations;
87    /**
88     * Line search result (pass by reference).
89     */
90    private LineSearchResult[] info;
91    /**
92     * Array of current coordinates.
93     */
94    private double[] x;
95    /**
96     * The gradient array.
97     */
98    private double[] g;
99    /**
100    * Double step size.
101    */
102   private double step;
103   /**
104    * Interpolation.
105    */
106   private int interpolation;
107   /**
108    * Function values.
109    */
110   private double f0, fA, fB, fC;
111   /**
112    * Dot products.
113    */
114   private double sg0, sgA, sgB, sgC;
115   /**
116    * True if a restart is allowed (set to true at the beginning of the algorithm).
117    */
118   private boolean restart;
119 
120   /**
121    * LineSearch constructor.
122    *
123    * @param n Number of variables.
124    * @since 1.0
125    */
126   LineSearch(int n) {
127     s = new double[n];
128     x0 = new double[n];
129     this.n = n;
130   }
131 
132   /**
133    * Minimize a function along a search direction.
134    *
135    * <p>This is a unidimensional line search based upon parabolic extrapolation and cubic
136    * interpolation using both function and gradient values; if forced to search in an uphill
137    * direction, return is after the initial step.
138    *
139    * @param n                   Number of variables.
140    * @param x                   Current variable values.
141    * @param f                   Current function value.
142    * @param g                   Current gradient values.
143    * @param p                   Search direction.
144    * @param angle               Angle between the gradient and search direction.
145    * @param fMove               Change in function value due to previous step.
146    * @param info                Line search result.
147    * @param functionEvaluations Number of function evaluations.
148    * @param optimizationSystem  Instance of the {@link ffx.numerics.Potential} interface.
149    * @return The final function value.
150    * @since 1.0
151    */
152   public double search(int n, double[] x, double f, double[] g, double[] p, double[] angle,
153                        double fMove, LineSearchResult[] info, int[] functionEvaluations,
154                        OptimizationInterface optimizationSystem) {
155 
156     assert (n > 0);
157 
158     // Initialize the line search.
159     this.x = x;
160     this.g = g;
161     this.optimizationSystem = optimizationSystem;
162     this.functionEvaluations = functionEvaluations;
163     this.info = info;
164     fA = 0.0;
165     fB = 0.0;
166     fC = 0.0;
167     sgA = 0.0;
168     sgB = 0.0;
169     sgC = 0.0;
170 
171     // Zero out the status indicator.
172     info[0] = null;
173 
174     // Copy the search direction p into a new vector s.
175     arraycopy(p, 0, s, 0, n);
176 
177     // Compute the length of the gradient and search direction.
178     double gNorm = sqrt(v1DotV2(n, g, 0, 1, g, 0, 1));
179     double sNorm = sqrt(v1DotV2(n, s, 0, 1, s, 0, 1));
180 
181     /*
182      Store the initial function, then normalize the search vector and find
183      the projected gradient.
184     */
185     f0 = f;
186     arraycopy(x, 0, x0, 0, n);
187     for (int i = 0; i < n; i++) {
188       s[i] /= sNorm;
189     }
190     sg0 = v1DotV2(n, s, 0, 1, g, 0, 1);
191 
192     /*
193      Check the angle between the search direction and the negative
194      gradient vector.
195     */
196     double cosang = -sg0 / gNorm;
197     cosang = min(1.0, max(-1.0, cosang));
198     angle[0] = toDegrees(acos(cosang));
199     if (angle[0] > DEFAULT_ANGLEMAX) {
200       info[0] = LineSearchResult.WideAngle;
201       return f;
202     }
203 
204     /*
205      Set the initial stepSize to the length of the passed search vector,
206      or based on previous function decrease.
207     */
208     step = 2.0 * abs(fMove / sg0);
209     step = min(step, sNorm);
210     if (step > DEFAULT_STEPMAX) {
211       step = DEFAULT_STEPMAX;
212     }
213     if (step < DEFAULT_STEPMIN) {
214       step = DEFAULT_STEPMIN;
215     }
216 
217     return begin();
218   }
219 
220   /**
221    * Begin the parabolic extrapolation procedure.
222    */
223   private double begin() {
224     restart = true;
225     interpolation = 0;
226     fB = f0;
227     sgB = sg0;
228     return step();
229   }
230 
231   /**
232    * Replace last point by latest and take another step.
233    */
234   private double step() {
235     fA = fB;
236     sgA = sgB;
237     aV1PlusV2(n, step, s, 0, 1, x, 0, 1);
238 
239     // Get new function and projected gradient following a step
240     functionEvaluations[0]++;
241     fB = optimizationSystem.energyAndGradient(x, g);
242     sgB = v1DotV2(n, s, 0, 1, g, 0, 1);
243 
244     // Scale step size if initial gradient change is too large
245     if (abs(sgB / sgA) >= DEFAULT_SLOPEMAX && restart) {
246       arraycopy(x0, 0, x, 0, n);
247       step /= 10.0;
248       info[0] = LineSearchResult.ScaleStep;
249       begin();
250     }
251     restart = false;
252 
253     /*
254      We now have an appropriate step size. Return if the gradient is small
255      and function decreases.
256     */
257     if (abs(sgB / sg0) <= DEFAULT_CAPPA && fB < fA) {
258       if (info[0] == null) {
259         info[0] = LineSearchResult.Success;
260       }
261       f0 = fB;
262       sg0 = sgB;
263       return f0;
264     }
265 
266     // Interpolate if gradient changes sign or function increases.
267     if (sgB * sgA < 0.0 || fB > fA) {
268       return cubic();
269     }
270 
271     /*
272      If the finite difference curvature is negative double the step; or if
273      step is less than parabolic estimate less than 4 * step use this
274      estimate, otherwise truncate to step or 4 * step, respectively.
275     */
276     step = 2.0 * step;
277     if (sgB > sgA) {
278       double parab = (fA - fB) / (sgB - sgA);
279       if (parab > 2.0 * step) {
280         parab = 2.0 * step;
281       }
282       if (parab < 0.5 * step) {
283         parab = 0.5 * step;
284       }
285       step = parab;
286     }
287     if (step > DEFAULT_STEPMAX) {
288       step = DEFAULT_STEPMAX;
289     }
290     return step();
291   }
292 
293   /**
294    * Beginning of the cubic interpolation procedure.
295    */
296   private double cubic() {
297     interpolation++;
298     double sss = 3.0 * (fB - fA) / step - sgA - sgB;
299     double ttt = sss * sss - sgA * sgB;
300     if (ttt < 0.0) {
301       info[0] = LineSearchResult.IntplnErr;
302       f0 = fB;
303       sg0 = sgB;
304       return f0;
305     }
306     ttt = sqrt(ttt);
307     double cube = step * (sgB + ttt + sss) / (sgB - sgA + 2.0 * ttt);
308     if (cube < 0 || cube > step) {
309       info[0] = LineSearchResult.IntplnErr;
310       f0 = fB;
311       sg0 = sgB;
312       return f0;
313     }
314     aV1PlusV2(n, -cube, s, 0, 1, x, 0, 1);
315 
316     // Get new function and gradient, then test for termination.
317     functionEvaluations[0]++;
318     fC = optimizationSystem.energyAndGradient(x, g);
319     sgC = v1DotV2(n, s, 0, 1, g, 0, 1);
320     if (abs(sgC / sg0) <= DEFAULT_CAPPA) {
321       if (info[0] == null) {
322         info[0] = LineSearchResult.Success;
323       }
324       f0 = fC;
325       sg0 = sgC;
326       return f0;
327     }
328     /*
329      Get the next pair of bracketing points by replacing one of the
330      current brackets with the interpolated point
331     */
332     if (fC <= fA || fC <= fB) {
333       double cubstp = min(abs(cube), abs(step - cube));
334       if (cubstp >= DEFAULT_STEPMIN && interpolation < DEFAULT_INTMAX) {
335         if (sgA * sgB < 0.0) {
336           /*
337            If the current brackets have slopes of opposite sign,
338            then substitute the interpolated points for the bracket
339            point with slope of same sign as the interpolated point
340           */
341           if (sgA * sgC < 0.0) {
342             fB = fC;
343             sgB = sgC;
344             step = step - cube;
345           } else {
346             fA = fC;
347             sgA = sgC;
348             step = cube;
349             aV1PlusV2(n, cube, s, 0, 1, x, 0, 1);
350           }
351         } else {
352           /*
353            If current brackets have slope of same sign, then replace
354            the far bracket if the interpolated point has a slope of
355            the opposite sign or a lower function value than the near
356            bracket, otherwise replace the far bracket point.
357           */
358           if (sgA * sgC < 0.0 || fA <= fC) {
359             fB = fC;
360             sgB = sgC;
361             step -= cube;
362           } else {
363             fA = fC;
364             sgA = sgC;
365             step = cube;
366             aV1PlusV2(n, cube, s, 0, 1, x, 0, 1);
367           }
368         }
369         return cubic();
370       }
371     }
372 
373     // Interpolation has failed; reset to the best current point.
374     double f1 = min(fA, min(fB, fC));
375     double sg1;
376     if (f1 == fA) {
377       sg1 = sgA;
378       aV1PlusV2(n, cube - step, s, 0, 1, x, 0, 1);
379     } else if (f1 == fB) {
380       sg1 = sgB;
381       aV1PlusV2(n, cube, s, 0, 1, x, 0, 1);
382     } else {
383       sg1 = sgC;
384     }
385 
386     // Try to restart from best point with smaller step size.
387     if (f1 > f0) {
388       functionEvaluations[0]++;
389       f0 = optimizationSystem.energyAndGradient(x, g);
390       sg0 = v1DotV2(n, s, 0, 1, g, 0, 1);
391       info[0] = LineSearchResult.IntplnErr;
392       return f0;
393     }
394     f0 = f1;
395     sg0 = sg1;
396     if (sg1 > 0.0) {
397       for (int i = 0; i < n; i++) {
398         s[i] *= -1.0;
399       }
400       sg0 = -sg1;
401     }
402     step = max(cube, step - cube) / 10.0;
403     if (step < DEFAULT_STEPMIN) {
404       step = DEFAULT_STEPMIN;
405     }
406 
407     // If already restarted once, then return with the best point.
408     if (info[0] == LineSearchResult.ReSearch) {
409       functionEvaluations[0]++;
410       f0 = optimizationSystem.energyAndGradient(x, g);
411       sg0 = v1DotV2(n, s, 0, 1, g, 0, 1);
412       info[0] = LineSearchResult.BadIntpln;
413       return f0;
414     } else {
415       // Begin again.
416       info[0] = LineSearchResult.ReSearch;
417       return begin();
418     }
419   }
420 
421   /**
422    * The six possible line search results:
423    * <p>
424    * Success, WideAngle, ScaleStep, IntplnErr, ReSearch, BadIntpln
425    */
426   public enum LineSearchResult {
427     Success,
428     WideAngle,
429     ScaleStep,
430     IntplnErr,
431     ReSearch,
432     BadIntpln
433   }
434 }