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.potential.nonbonded;
39  
40  import static ffx.numerics.math.ScalarMath.binomial;
41  import static java.util.Arrays.fill;
42  
43  import ffx.potential.nonbonded.pme.LambdaMode;
44  import ffx.potential.parameters.ForceField;
45  import java.util.logging.Level;
46  import java.util.logging.Logger;
47  import org.apache.commons.math3.fitting.leastsquares.EvaluationRmsChecker;
48  import org.apache.commons.math3.fitting.leastsquares.LeastSquaresFactory;
49  import org.apache.commons.math3.fitting.leastsquares.LeastSquaresOptimizer;
50  import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem;
51  import org.apache.commons.math3.fitting.leastsquares.LevenbergMarquardtOptimizer;
52  import org.apache.commons.math3.fitting.leastsquares.MultivariateJacobianFunction;
53  import org.apache.commons.math3.linear.Array2DRowRealMatrix;
54  import org.apache.commons.math3.linear.ArrayRealVector;
55  import org.apache.commons.math3.linear.RealMatrix;
56  import org.apache.commons.math3.linear.RealVector;
57  import org.apache.commons.math3.optim.ConvergenceChecker;
58  import org.apache.commons.math3.util.Pair;
59  
60  /**
61   * Predict Mutual Induced Dipoles based on previous steps.
62   *
63   * @author Stephen LuCore
64   * @since 1.0
65   */
66  public class ScfPredictor {
67  
68    private static final Logger logger = Logger.getLogger(ScfPredictor.class.getName());
69    /** Convergence tolerance of the LeastSquares optimizer. */
70    private static final double eps = 1.0e-4;
71  
72    private final PredictorMode predictorMode;
73    /** Induced dipole predictor order. */
74    private final int predictorOrder;
75    /** Dimensions of [nsymm][nAtoms][3] */
76    protected double[][][] inducedDipole;
77  
78    protected double[][][] inducedDipoleCR;
79    /** Number of atoms. */
80    private int nAtoms;
81    /** Maps LambdaMode to array indices: OFF/CONDENSED=0, CONDENSED_NOLIGAND=1, VAPOR=2 */
82    private int mode;
83    /** Dimensions of [mode][predictorOrder][nAtoms][3] */
84    private double[][][][] predictorInducedDipole;
85  
86    private double[][][][] predictorInducedDipoleCR;
87    /** Induced dipole predictor index. */
88    private int predictorStartIndex;
89    /** Induced dipole predictor count. */
90    private int predictorCount;
91    /**
92     * Predicts induced dipoles by locally minimizing and testing eps against squared change in
93     * parameters.
94     */
95    private LeastSquaresPredictor leastSquaresPredictor;
96    /**
97     * Constructor for ScfPredictor.
98     *
99     * @param mode a {@link ffx.potential.nonbonded.ScfPredictor.PredictorMode} object.
100    * @param order a int.
101    * @param ff a {@link ffx.potential.parameters.ForceField} object.
102    */
103   public ScfPredictor(PredictorMode mode, int order, ForceField ff) {
104     predictorMode = mode;
105     predictorOrder = order;
106     predictorCount = 0;
107     predictorStartIndex = 0;
108     if (predictorMode != PredictorMode.NONE) {
109       if (predictorMode == PredictorMode.LS) {
110         leastSquaresPredictor = new LeastSquaresPredictor(eps);
111       }
112       if (ff.getBoolean("LAMBDATERM", false) ) {
113         predictorInducedDipole = new double[3][predictorOrder][nAtoms][3];
114         predictorInducedDipoleCR = new double[3][predictorOrder][nAtoms][3];
115       } else {
116         predictorInducedDipole = new double[1][predictorOrder][nAtoms][3];
117         predictorInducedDipoleCR = new double[1][predictorOrder][nAtoms][3];
118       }
119     }
120   }
121 
122   /**
123    * run.
124    *
125    * @param lambdaMode a {@link LambdaMode} object.
126    */
127   public void run(LambdaMode lambdaMode) {
128     if (predictorMode == PredictorMode.NONE) {
129       return;
130     }
131     switch (lambdaMode) {
132       case CONDENSED_NO_LIGAND:
133         mode = 1;
134         break;
135       case VAPOR:
136         mode = 2;
137         break;
138       default:
139         mode = 0;
140     }
141     if (predictorMode != PredictorMode.NONE) {
142       switch (predictorMode) {
143         case ASPC:
144           aspcPredictor();
145           break;
146         case LS:
147           leastSquaresPredictor();
148           break;
149         case POLY:
150           polynomialPredictor();
151           break;
152         case NONE:
153         default:
154           break;
155       }
156     }
157   }
158 
159   /**
160    * Save the current converged mutual induced dipoles.
161    *
162    * @param inducedDipole an array of induced dipoles.
163    * @param inducedDipoleCR an array of induced dipoles chain rule terms.
164    * @param directDipole an array of direct dipoles.
165    * @param directDipoleCR an array of direct dipoles chain rule terms.
166    */
167   public void saveMutualInducedDipoles(
168       double[][][] inducedDipole,
169       double[][][] inducedDipoleCR,
170       double[][] directDipole,
171       double[][] directDipoleCR) {
172 
173     // Current induced dipoles are saved before those from the previous step.
174     predictorStartIndex--;
175     if (predictorStartIndex < 0) {
176       predictorStartIndex = predictorOrder - 1;
177     }
178 
179     if (predictorCount < predictorOrder) {
180       predictorCount++;
181     }
182 
183     for (int i = 0; i < nAtoms; i++) {
184       for (int j = 0; j < 3; j++) {
185         predictorInducedDipole[mode][predictorStartIndex][i][j] =
186             inducedDipole[0][i][j] - directDipole[i][j];
187         predictorInducedDipoleCR[mode][predictorStartIndex][i][j] =
188             inducedDipoleCR[0][i][j] - directDipoleCR[i][j];
189       }
190     }
191   }
192 
193   /**
194    * To be called upon initialization and update of inducedDipole arrays in parent.
195    *
196    * @param inducedDipole an array of induced dipoles.
197    * @param inducedDipoleCR an array of induced dipoles chain rule terms.
198    * @param lambdaTerm a boolean.
199    */
200   public void setInducedDipoleReferences(
201       double[][][] inducedDipole, double[][][] inducedDipoleCR, boolean lambdaTerm) {
202     this.inducedDipole = inducedDipole;
203     this.inducedDipoleCR = inducedDipoleCR;
204     if (lambdaTerm) {
205       predictorInducedDipole = new double[3][predictorOrder][nAtoms][3];
206       predictorInducedDipoleCR = new double[3][predictorOrder][nAtoms][3];
207     } else {
208       predictorInducedDipole = new double[1][predictorOrder][nAtoms][3];
209       predictorInducedDipoleCR = new double[1][predictorOrder][nAtoms][3];
210     }
211   }
212 
213   /** {@inheritDoc} */
214   @Override
215   public String toString() {
216     return predictorMode.toString();
217   }
218 
219   /**
220    * The least-squares predictor with induced dipole information from 8-10 previous steps reduces
221    * the number SCF iterations by ~50%.
222    */
223   private void leastSquaresPredictor() {
224     if (predictorCount < 2) {
225       return;
226     }
227     try {
228       /**
229        * The Jacobian and target do not change during the LS optimization, so it's most efficient to
230        * update them once before the Least-Squares optimizer starts.
231        */
232       leastSquaresPredictor.updateJacobianAndTarget();
233       int maxEvals, maxIter;
234       maxEvals = maxIter = 1000;
235       LeastSquaresOptimizer.Optimum optimum = leastSquaresPredictor.predict(maxEvals, maxIter);
236       double[] optimalValues = optimum.getPoint().toArray();
237       if (logger.isLoggable(Level.FINEST)) {
238         logger.finest(String.format("\n LS RMS:            %10.6f", optimum.getRMS()));
239         logger.finest(String.format(" LS Iterations:     %10d", optimum.getIterations()));
240         logger.finest(String.format(" Jacobian Evals:    %10d", optimum.getEvaluations()));
241         logger.finest(String.format(" Root Mean Square:  %10.6f", optimum.getRMS()));
242         logger.finest(" LS Coefficients");
243         for (int i = 0; i < predictorOrder - 1; i++) {
244           logger.finest(String.format(" %2d  %10.6f", i + 1, optimalValues[i]));
245         }
246       }
247 
248       /** Initialize a pointer into predictor induced dipole array. */
249       int index = predictorStartIndex;
250       /**
251        * Apply the LS coefficients in order to provide an initial guess at the converged induced
252        * dipoles.
253        */
254       for (int k = 0; k < predictorOrder - 1; k++) {
255         /** Set the current coefficient. */
256         double c = optimalValues[k];
257         for (int i = 0; i < nAtoms; i++) {
258           for (int j = 0; j < 3; j++) {
259             inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
260             inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
261           }
262         }
263         index++;
264         if (index >= predictorOrder) {
265           index = 0;
266         }
267       }
268     } catch (Exception e) {
269       logger.log(Level.WARNING, " Exception computing predictor coefficients", e);
270     }
271   }
272 
273   /** Always-stable predictor-corrector for the mutual induced dipoles. */
274   private void aspcPredictor() {
275 
276     if (predictorCount < 6) {
277       return;
278     }
279 
280     final double[] aspc = {
281       22.0 / 7.0, -55.0 / 14.0, 55.0 / 21.0, -22.0 / 21.0, 5.0 / 21.0, -1.0 / 42.0
282     };
283     /* Initialize a pointer into predictor induced dipole array. */
284     int index = predictorStartIndex;
285     /* Expansion loop. */
286     for (int k = 0; k < 6; k++) {
287       /* Set the current predictor coefficient. */
288       double c = aspc[k];
289       for (int i = 0; i < nAtoms; i++) {
290         for (int j = 0; j < 3; j++) {
291           inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
292           inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
293         }
294       }
295       index++;
296       if (index >= predictorOrder) {
297         index = 0;
298       }
299     }
300   }
301 
302   /** Polynomial predictor for the mutual induced dipoles. */
303   private void polynomialPredictor() {
304 
305     if (predictorCount == 0) {
306       return;
307     }
308 
309     /** Check the number of previous induced dipole vectors available. */
310     int n = predictorOrder;
311     if (predictorCount < predictorOrder) {
312       n = predictorCount;
313     }
314     /** Initialize a pointer into predictor induced dipole array. */
315     int index = predictorStartIndex;
316     /** Initialize the sign of the polynomial expansion. */
317     double sign = -1.0;
318     /** Expansion loop. */
319     for (int k = 0; k < n; k++) {
320       /** Set the current predictor sign and coefficient. */
321       sign *= -1.0;
322       double c = sign * binomial(n, k);
323       for (int i = 0; i < nAtoms; i++) {
324         for (int j = 0; j < 3; j++) {
325           inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
326           inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
327         }
328       }
329       index++;
330       if (index >= predictorOrder) {
331         index = 0;
332       }
333     }
334   }
335 
336   public enum PredictorMode {
337     NONE,
338     LS,
339     POLY,
340     ASPC
341   }
342 
343   private class LeastSquaresPredictor {
344 
345     double[] weights;
346     double[] target;
347     double[][] jacobian;
348     double[] initialSolution;
349     double tolerance;
350     RealVector valuesVector;
351     RealVector targetVector;
352     RealMatrix jacobianMatrix;
353     LeastSquaresOptimizer optimizer;
354     ConvergenceChecker<LeastSquaresProblem.Evaluation> checker;
355     Pair<RealVector, RealMatrix> test = new Pair<>(targetVector, jacobianMatrix);
356     MultivariateJacobianFunction function =
357         new MultivariateJacobianFunction() {
358           @Override
359           public Pair<RealVector, RealMatrix> value(RealVector point) {
360             return new Pair<>(targetVector, jacobianMatrix);
361           }
362         };
363 
364     public LeastSquaresPredictor(double eps) {
365       tolerance = eps;
366       weights = new double[2 * nAtoms * 3];
367       target = new double[2 * nAtoms * 3];
368       jacobian = new double[2 * nAtoms * 3][predictorOrder - 1];
369       initialSolution = new double[predictorOrder - 1];
370       fill(weights, 1.0);
371       fill(initialSolution, 0.0);
372       initialSolution[0] = 1.0;
373       optimizer = new LevenbergMarquardtOptimizer().withParameterRelativeTolerance(eps);
374       checker = new EvaluationRmsChecker(eps);
375     }
376 
377     public LeastSquaresOptimizer.Optimum predict(int maxEval, int maxIter) {
378       RealVector start = new ArrayRealVector(initialSolution);
379       LeastSquaresProblem lsp =
380           LeastSquaresFactory.create(function, targetVector, start, checker, maxEval, maxIter);
381 
382       LeastSquaresOptimizer.Optimum optimum = optimizer.optimize(lsp);
383       if (true)
384         logger.info(
385             String.format(
386                 " LS Optimization parameters:" + "  %s %s\n" + "  %s %s\n" + "  %d %d",
387                 function,
388                 targetVector.toString(),
389                 start,
390                 checker.toString(),
391                 maxIter,
392                 maxEval));
393       return optimum;
394     }
395 
396     public void updateJacobianAndTarget() {
397 
398       // Update the target.
399       int index = 0;
400       for (int i = 0; i < nAtoms; i++) {
401         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][0];
402         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][1];
403         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][2];
404         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][0];
405         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][1];
406         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][2];
407       }
408       targetVector = new ArrayRealVector(target);
409 
410       // Update the Jacobian.
411       index = predictorStartIndex + 1;
412       if (index >= predictorOrder) {
413         index = 0;
414       }
415       for (int j = 0; j < predictorOrder - 1; j++) {
416         int ji = 0;
417         for (int i = 0; i < nAtoms; i++) {
418           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][0];
419           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][1];
420           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][2];
421           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][0];
422           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][1];
423           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][2];
424         }
425         index++;
426         if (index >= predictorOrder) {
427           index = 0;
428         }
429       }
430       jacobianMatrix = new Array2DRowRealMatrix(jacobian);
431     }
432 
433     private RealVector value(double[] variables) {
434 
435       double[] values = new double[2 * nAtoms * 3];
436       for (int i = 0; i < nAtoms; i++) {
437         int index = 6 * i;
438         values[index] = 0;
439         values[index + 1] = 0;
440         values[index + 2] = 0;
441         values[index + 3] = 0;
442         values[index + 4] = 0;
443         values[index + 5] = 0;
444         int pi = predictorStartIndex + 1;
445         if (pi >= predictorOrder) {
446           pi = 0;
447         }
448         for (int j = 0; j < predictorOrder - 1; j++) {
449           values[index] += variables[j] * predictorInducedDipole[mode][pi][i][0];
450           values[index + 1] += variables[j] * predictorInducedDipole[mode][pi][i][1];
451           values[index + 2] += variables[j] * predictorInducedDipole[mode][pi][i][2];
452           values[index + 3] += variables[j] * predictorInducedDipoleCR[mode][pi][i][0];
453           values[index + 4] += variables[j] * predictorInducedDipoleCR[mode][pi][i][1];
454           values[index + 5] += variables[j] * predictorInducedDipoleCR[mode][pi][i][2];
455           pi++;
456           if (pi >= predictorOrder) {
457             pi = 0;
458           }
459         }
460       }
461       return new ArrayRealVector(values);
462     }
463   }
464 }