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.potential.nonbonded.pme;
39  
40  import ffx.potential.parameters.ForceField;
41  import org.apache.commons.math3.analysis.DifferentiableMultivariateVectorFunction;
42  import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
43  import org.apache.commons.math3.optimization.general.LevenbergMarquardtOptimizer;
44  
45  import java.util.logging.Level;
46  import java.util.logging.Logger;
47  
48  import static ffx.numerics.math.ScalarMath.binomial;
49  import static java.lang.String.format;
50  import static java.util.Arrays.fill;
51  
52  @SuppressWarnings("deprecation")
53  public class SCFPredictorParameters {
54  
55    private static final Logger logger = Logger.getLogger(SCFPredictorParameters.class.getName());
56  
57    /** Induced dipole predictor order. */
58    public int predictorOrder;
59    /** Induced dipole predictor index. */
60    public int predictorStartIndex;
61    /** Induced dipole predictor count. */
62    public int predictorCount;
63    /** Dimensions of [mode][predictorOrder][nAtoms][3] */
64    public double[][][][] predictorInducedDipole;
65    /** Dimensions of [mode][predictorOrder][nAtoms][3] */
66    public double[][][][] predictorInducedDipoleCR;
67  
68    private LeastSquaresPredictor leastSquaresPredictor;
69    public LevenbergMarquardtOptimizer leastSquaresOptimizer;
70  
71    public final SCFPredictor scfPredictor;
72    private final int nAtoms;
73  
74    public SCFPredictorParameters(SCFPredictor scfPredictor, int nAtoms) {
75      this.nAtoms = nAtoms;
76      this.scfPredictor = scfPredictor;
77    }
78  
79    /** Always-stable predictor-corrector for the mutual induced dipoles. */
80    public void aspcPredictor(LambdaMode lambdaMode,
81        double[][][] inducedDipole, double[][][] inducedDipoleCR) {
82  
83      if (predictorCount < 6) {
84        return;
85      }
86  
87      int mode;
88      switch (lambdaMode) {
89        case CONDENSED_NO_LIGAND:
90          mode = 1;
91          break;
92        case VAPOR:
93          mode = 2;
94          break;
95        default:
96          mode = 0;
97      }
98  
99      final double[] aspc = {
100         22.0 / 7.0, -55.0 / 14.0, 55.0 / 21.0, -22.0 / 21.0, 5.0 / 21.0, -1.0 / 42.0
101     };
102 
103     // Initialize a pointer into predictor induced dipole array.
104     int index = predictorStartIndex;
105 
106     // Expansion loop.
107     for (int k = 0; k < 6; k++) {
108 
109       // Set the current predictor coefficient.
110       double c = aspc[k];
111       for (int i = 0; i < nAtoms; i++) {
112         for (int j = 0; j < 3; j++) {
113           inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
114           inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
115         }
116       }
117       index++;
118       if (index >= predictorOrder) {
119         index = 0;
120       }
121     }
122   }
123 
124   public void init(ForceField forceField) {
125     predictorCount = 0;
126     int defaultOrder = 6;
127     predictorOrder = forceField.getInteger("SCF_PREDICTOR_ORDER", defaultOrder);
128     if (scfPredictor == SCFPredictor.LS) {
129       leastSquaresPredictor = new LeastSquaresPredictor();
130       double eps = 1.0e-4;
131       leastSquaresOptimizer =
132           new LevenbergMarquardtOptimizer(
133               new org.apache.commons.math3.optimization.SimpleVectorValueChecker(eps, eps));
134     } else if (scfPredictor == SCFPredictor.ASPC) {
135       predictorOrder = 6;
136     }
137     predictorStartIndex = 0;
138   }
139 
140   /**
141    * The least-squares predictor with induced dipole information from 8-10 previous steps reduces the
142    * number SCF iterations by ~50%.
143    */
144   public void leastSquaresPredictor(LambdaMode lambdaMode,
145       double[][][] inducedDipole, double[][][] inducedDipoleCR) {
146     if (predictorCount < 2) {
147       return;
148     }
149     try {
150       /*
151        The Jacobian and target do not change during the LS optimization,
152        so it's most efficient to update them once before the
153        Least-Squares optimizer starts.
154       */
155       leastSquaresPredictor.lambdaMode = lambdaMode;
156       leastSquaresPredictor.updateJacobianAndTarget();
157       int maxEvals = 100;
158       fill(leastSquaresPredictor.initialSolution, 0.0);
159       leastSquaresPredictor.initialSolution[0] = 1.0;
160       org.apache.commons.math3.optimization.PointVectorValuePair optimum =
161           leastSquaresOptimizer.optimize(
162               maxEvals,
163               leastSquaresPredictor,
164               leastSquaresPredictor.calculateTarget(),
165               leastSquaresPredictor.weights,
166               leastSquaresPredictor.initialSolution);
167       double[] optimalValues = optimum.getPoint();
168       if (logger.isLoggable(Level.FINEST)) {
169         logger.finest(format("\n LS RMS:            %10.6f", leastSquaresOptimizer.getRMS()));
170         logger.finest(format(" LS Iterations:     %10d", leastSquaresOptimizer.getEvaluations()));
171         logger.finest(
172             format(" Jacobian Evals:    %10d", leastSquaresOptimizer.getJacobianEvaluations()));
173         logger.finest(format(" Chi Square:        %10.6f", leastSquaresOptimizer.getChiSquare()));
174         logger.finest(" LS Coefficients");
175         for (int i = 0; i < predictorOrder - 1; i++) {
176           logger.finest(format(" %2d  %10.6f", i + 1, optimalValues[i]));
177         }
178       }
179 
180       int mode;
181       switch (lambdaMode) {
182         case CONDENSED_NO_LIGAND:
183           mode = 1;
184           break;
185         case VAPOR:
186           mode = 2;
187           break;
188         default:
189           mode = 0;
190       }
191 
192       // Initialize a pointer into predictor induced dipole array.
193       int index = predictorStartIndex;
194 
195       // Apply the LS coefficients in order to provide an initial guess at the converged induced
196       // dipoles.
197       for (int k = 0; k < predictorOrder - 1; k++) {
198 
199         // Set the current coefficient.
200         double c = optimalValues[k];
201         for (int i = 0; i < nAtoms; i++) {
202           for (int j = 0; j < 3; j++) {
203             inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
204             inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
205           }
206         }
207         index++;
208         if (index >= predictorOrder) {
209           index = 0;
210         }
211       }
212     } catch (Exception e) {
213       logger.log(Level.WARNING, " Exception computing predictor coefficients", e);
214     }
215   }
216 
217   /** Polynomial predictor for the mutual induced dipoles. */
218   public void polynomialPredictor(LambdaMode lambdaMode,
219       double[][][] inducedDipole, double[][][] inducedDipoleCR) {
220 
221     if (predictorCount == 0) {
222       return;
223     }
224 
225     int mode;
226     switch (lambdaMode) {
227       case CONDENSED_NO_LIGAND:
228         mode = 1;
229         break;
230       case VAPOR:
231         mode = 2;
232         break;
233       default:
234         mode = 0;
235     }
236 
237     // Check the number of previous induced dipole vectors available.
238     int n = predictorOrder;
239     if (predictorCount < predictorOrder) {
240       n = predictorCount;
241     }
242 
243     // Initialize a pointer into predictor induced dipole array.
244     int index = predictorStartIndex;
245 
246     // Initialize the sign of the polynomial expansion.
247     double sign = -1.0;
248 
249     // Expansion loop.
250     for (int k = 0; k < n; k++) {
251 
252       // Set the current predictor sign and coefficient.
253       sign *= -1.0;
254       double c = sign * binomial(n, k);
255       for (int i = 0; i < nAtoms; i++) {
256         for (int j = 0; j < 3; j++) {
257           inducedDipole[0][i][j] += c * predictorInducedDipole[mode][index][i][j];
258           inducedDipoleCR[0][i][j] += c * predictorInducedDipoleCR[mode][index][i][j];
259         }
260       }
261       index++;
262       if (index >= predictorOrder) {
263         index = 0;
264       }
265     }
266   }
267 
268   /** Save the current converged mutual induced dipoles. */
269   public void saveMutualInducedDipoles(LambdaMode lambdaMode,
270       double[][][] inducedDipole, double[][][] inducedDipoleCR,
271       double[][] directDipole, double[][] directDipoleCR) {
272 
273     int mode;
274     switch (lambdaMode) {
275       case CONDENSED_NO_LIGAND:
276         mode = 1;
277         break;
278       case VAPOR:
279         mode = 2;
280         break;
281       default:
282         mode = 0;
283     }
284 
285     // Current induced dipoles are saved before those from the previous step.
286     predictorStartIndex--;
287     if (predictorStartIndex < 0) {
288       predictorStartIndex = predictorOrder - 1;
289     }
290 
291     if (predictorCount < predictorOrder) {
292       predictorCount++;
293     }
294 
295     for (int i = 0; i < nAtoms; i++) {
296       for (int j = 0; j < 3; j++) {
297         predictorInducedDipole[mode][predictorStartIndex][i][j] =
298             inducedDipole[0][i][j] - directDipole[i][j];
299         predictorInducedDipoleCR[mode][predictorStartIndex][i][j] =
300             inducedDipoleCR[0][i][j] - directDipoleCR[i][j];
301       }
302     }
303   }
304 
305   private class LeastSquaresPredictor implements DifferentiableMultivariateVectorFunction {
306 
307     double[] weights;
308     double[] target;
309     double[] values;
310     double[][] jacobian;
311     double[] initialSolution;
312     private final MultivariateMatrixFunction multivariateMatrixFunction = this::jacobian;
313 
314     public LambdaMode lambdaMode = null;
315 
316     LeastSquaresPredictor() {
317       weights = new double[2 * nAtoms * 3];
318       target = new double[2 * nAtoms * 3];
319       values = new double[2 * nAtoms * 3];
320       jacobian = new double[2 * nAtoms * 3][predictorOrder - 1];
321       initialSolution = new double[predictorOrder - 1];
322       fill(weights, 1.0);
323       initialSolution[0] = 1.0;
324     }
325 
326     @Override
327     public MultivariateMatrixFunction jacobian() {
328       return multivariateMatrixFunction;
329     }
330 
331     @Override
332     public double[] value(double[] variables) {
333       int mode;
334       switch (lambdaMode) {
335         case CONDENSED_NO_LIGAND:
336           mode = 1;
337           break;
338         case VAPOR:
339           mode = 2;
340           break;
341         default:
342           mode = 0;
343       }
344 
345       for (int i = 0; i < nAtoms; i++) {
346         int index = 6 * i;
347         values[index] = 0;
348         values[index + 1] = 0;
349         values[index + 2] = 0;
350         values[index + 3] = 0;
351         values[index + 4] = 0;
352         values[index + 5] = 0;
353         int pi = predictorStartIndex + 1;
354         if (pi >= predictorOrder) {
355           pi = 0;
356         }
357         for (int j = 0; j < predictorOrder - 1; j++) {
358           values[index] += variables[j] * predictorInducedDipole[mode][pi][i][0];
359           values[index + 1] += variables[j] * predictorInducedDipole[mode][pi][i][1];
360           values[index + 2] += variables[j] * predictorInducedDipole[mode][pi][i][2];
361           values[index + 3] += variables[j] * predictorInducedDipoleCR[mode][pi][i][0];
362           values[index + 4] += variables[j] * predictorInducedDipoleCR[mode][pi][i][1];
363           values[index + 5] += variables[j] * predictorInducedDipoleCR[mode][pi][i][2];
364           pi++;
365           if (pi >= predictorOrder) {
366             pi = 0;
367           }
368         }
369       }
370       return values;
371     }
372 
373     double[] calculateTarget() {
374       return target;
375     }
376 
377     void updateJacobianAndTarget() {
378       int mode;
379       switch (lambdaMode) {
380         case CONDENSED_NO_LIGAND:
381           mode = 1;
382           break;
383         case VAPOR:
384           mode = 2;
385           break;
386         default:
387           mode = 0;
388       }
389 
390       // Update the target.
391       int index = 0;
392       for (int i = 0; i < nAtoms; i++) {
393         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][0];
394         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][1];
395         target[index++] = predictorInducedDipole[mode][predictorStartIndex][i][2];
396         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][0];
397         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][1];
398         target[index++] = predictorInducedDipoleCR[mode][predictorStartIndex][i][2];
399       }
400 
401       // Update the Jacobian.
402       index = predictorStartIndex + 1;
403       if (index >= predictorOrder) {
404         index = 0;
405       }
406       for (int j = 0; j < predictorOrder - 1; j++) {
407         int ji = 0;
408         for (int i = 0; i < nAtoms; i++) {
409           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][0];
410           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][1];
411           jacobian[ji++][j] = predictorInducedDipole[mode][index][i][2];
412           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][0];
413           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][1];
414           jacobian[ji++][j] = predictorInducedDipoleCR[mode][index][i][2];
415         }
416         index++;
417         if (index >= predictorOrder) {
418           index = 0;
419         }
420       }
421     }
422 
423     private double[][] jacobian(double[] variables) {
424       return jacobian;
425     }
426   }
427 }