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.numerics.multipole;
39  
40  import jdk.incubator.vector.DoubleVector;
41  
42  import static ffx.numerics.math.ScalarMath.doubleFactorial;
43  import static ffx.numerics.multipole.GKTensorMode.POTENTIAL;
44  import static java.lang.System.arraycopy;
45  import static java.util.Arrays.fill;
46  import static jdk.incubator.vector.DoubleVector.SPECIES_PREFERRED;
47  import static jdk.incubator.vector.VectorOperators.EXP;
48  import static org.apache.commons.math3.util.FastMath.pow;
49  
50  /**
51   * The GKSource class generates the source terms for the Generalized Kirkwood version of the tensor recursion.
52   */
53  public class GKSourceSIMD {
54  
55    private GKTensorMode mode = POTENTIAL;
56  
57    /**
58     * Born radius of atom i multiplied by the Born radius of atom j.
59     */
60    private DoubleVector rb2;
61  
62    /**
63     * The GK term exp(-r2 / (gc * ai * aj)).
64     */
65    private DoubleVector expTerm;
66  
67    /**
68     * The GK effective separation distance.
69     */
70    private DoubleVector f;
71  
72    /**
73     * f1 = 1.0 - expTerm * igc;
74     */
75    private DoubleVector f1;
76  
77    /**
78     * f2 = 2.0 * expTerm / (gc * gcAiAj);
79     */
80    private DoubleVector f2;
81  
82    /**
83     * Generalized Kirkwood constant.
84     */
85    private final double gc;
86  
87    /**
88     * Inverse generalized Kirkwood constant.
89     */
90    private final double igc;
91  
92    /**
93     * The product: gc * Ai * Aj.
94     */
95    private DoubleVector gcAiAj;
96  
97    /**
98     * The ratio -r2 / (gc * Ai * Aj).
99     */
100   private DoubleVector ratio;
101 
102   /**
103    * The quantity: -2.0 / (gc * Ai * Aj);
104    */
105   private DoubleVector fr;
106 
107   /**
108    * Separation distance squared.
109    */
110   private DoubleVector r2;
111 
112   /**
113    * Recursion order.
114    */
115   private final int order;
116 
117   /**
118    * Compute the "source" terms for the recursion.
119    */
120   private final double[] kirkwoodSource;
121 
122   /**
123    * Coefficients needed when taking derivatives of auxiliary functions.
124    */
125   private final double[][] anmc;
126 
127   /**
128    * Chain rule terms from differentiating zeroth order auxiliary functions (an0) with respect to x,
129    * y or z.
130    */
131   private final DoubleVector[] fn;
132 
133   /**
134    * Chain rule terms from differentiating zeroth order born radii auxiliary functions (bn0) with respect to Ai
135    * or Aj.
136    */
137   protected final DoubleVector[] bn;
138 
139   /**
140    * Source terms for the Kirkwood version of the Challacombe et al. recursion.
141    */
142   private final DoubleVector[][] anm;
143 
144   /**
145    * Source terms for the Kirkwood version of the Challacombe et al. recursion for Born-chain rule
146    * derivatives.
147    */
148   private final DoubleVector[][] bnm;
149 
150   private final DoubleVector zero = DoubleVector.zero(SPECIES_PREFERRED);
151   private final DoubleVector one = zero.add(1.0);
152 
153   /**
154    * Construct a new GKSource object.
155    *
156    * @param order Recursion order.
157    * @param gc    Generalized Kirkwood constant.
158    */
159   public GKSourceSIMD(int order, double gc) {
160     this.order = order;
161     this.gc = gc;
162     this.igc = 1.0 / gc;
163 
164     // Auxiliary terms for Generalized Kirkwood (equivalent to Coulomb and Thole Screening).
165     kirkwoodSource = new double[order + 1];
166     for (short n = 0; n <= order; n++) {
167       kirkwoodSource[n] = pow(-1, n) * doubleFactorial(2 * n - 1);
168     }
169 
170     anmc = new double[order + 1][];
171     for (int n = 0; n <= order; n++) {
172       anmc[n] = GKSource.anmc(n);
173     }
174 
175     fn = new DoubleVector[order + 1];
176     anm = new DoubleVector[order + 1][order + 1];
177     bn = new DoubleVector[order + 1];
178     bnm = new DoubleVector[order + 1][order + 1];
179   }
180 
181   /**
182    * Generate source terms for the Kirkwood version of the Challacombe et al. recursion.
183    *
184    * @param work           The array to store the source terms.
185    * @param multipoleOrder The multipole order.
186    */
187   protected void source(DoubleVector[] work, GKMultipoleOrder multipoleOrder) {
188     int mpoleOrder = multipoleOrder.getOrder();
189     fill(work, 0, mpoleOrder, zero);
190     if (mode == POTENTIAL) {
191       // Max derivatives.
192       int derivatives = order - mpoleOrder;
193       arraycopy(anm[mpoleOrder], 0, work, mpoleOrder, derivatives + 1);
194     } else {
195       // Max derivatives.
196       int derivatives = (order - 1) - mpoleOrder;
197       arraycopy(bnm[mpoleOrder], 0, work, mpoleOrder, derivatives + 1);
198     }
199   }
200 
201   /**
202    * Generate source terms for the Kirkwood version of the Challacombe et al. recursion.
203    *
204    * @param mode      The tensor mode.
205    * @param multipole The multipole order.
206    * @param r2        Separation distance squared.
207    * @param ai        Born radius of atom i.
208    * @param aj        Born radius of atom j.
209    */
210   public void generateSource(GKTensorMode mode, GKMultipoleOrder multipole,
211                              DoubleVector r2, DoubleVector ai, DoubleVector aj) {
212     int multipoleOrder = multipole.getOrder();
213     this.mode = mode;
214 
215     this.r2 = r2;
216     rb2 = ai.mul(aj);
217     gcAiAj = rb2.mul(gc);
218     ratio = r2.neg().div(gcAiAj);
219     expTerm = ratio.lanewise(EXP);
220     fr = zero.sub(2.0).div(gcAiAj);
221     f = r2.add(rb2.mul(expTerm)).sqrt();
222     f1 = one.sub(expTerm.mul(igc));
223     f2 = zero.add(2.0).mul(expTerm).div(gcAiAj.mul(gc));
224     if (mode == POTENTIAL) {
225       // Prepare the GK Potential tensor.
226       anm(order, order - multipoleOrder);
227     } else {
228       // Prepare the GK Born-chain rule tensor.
229       bnm(order - 1, (order - 1) - multipoleOrder);
230     }
231   }
232 
233   /**
234    * Fill the GK auxiliary matrix.
235    * <p>
236    * The first row is the GK potential and derivatives for monopoles. The second row is the GK
237    * potential and derivatives for dipoles. The third row is the GK potential and derivatives for
238    * quadrupoles.
239    *
240    * @param n           Order.
241    * @param derivatives Number of derivatives.
242    */
243   private void anm(int n, int derivatives) {
244     // Fill the fn chain rule terms.
245     fn(n);
246 
247     // Fill the auxiliary potential.
248     an0(n);
249 
250     // Derivative loop over columns.
251     for (int d = 1; d <= derivatives; d++) {
252       // The coefficients are the same for each order.
253       var coef = anmc[d];
254       // The current derivative can only be computed for order n - d.
255       int limit = n - d;
256       // Order loop over rows.
257       for (int order = 0; order <= limit; order++) {
258         // Compute this term from previous terms for 1 higher order.
259         var terms = anm[order + 1];
260         var sum = zero;
261         for (int i = 1; i <= d; i++) {
262           sum = sum.add(fn[i].mul(terms[d - i].mul(coef[i - 1])));
263         }
264         anm[order][d] = sum;
265       }
266     }
267   }
268 
269   /**
270    * Fill the GK auxiliary matrix derivatives with respect to Born radii.
271    * <p>
272    * The first row are derivatives for the monopole potential. The second row are derivatives for the
273    * dipole potential. The third row are derivatives for the quadrupole potential.
274    *
275    * @param n           Order.
276    * @param derivatives Number of derivatives.
277    */
278   private void bnm(int n, int derivatives) {
279     // Fill the bn chain rule terms.
280     bn(n);
281 
282     // Fill the auxiliary potential derivatives.
283     bn0(n);
284 
285     // Derivative loop over columns.
286     for (int d = 1; d <= derivatives; d++) {
287       // The coefficients are the same for each order.
288       var coef = anmc[d];
289       // The current derivative can only be computed for order n - d.
290       int limit = n - d;
291       // Order loop over rows.
292       for (int order = 0; order <= limit; order++) {
293         // Compute this term from previous terms for 1 higher order.
294         var terma = anm[order + 1];
295         var termb = bnm[order + 1];
296         var sum = zero;
297         for (int i = 1; i <= d; i++) {
298           sum = sum.add(bn[i].mul(terma[d - i].mul(coef[i - 1])));
299           sum = sum.add(fn[i].mul(termb[d - i].mul(coef[i - 1])));
300         }
301         bnm[order][d] = sum;
302       }
303     }
304   }
305 
306   /**
307    * Sets the function f, which are chain rule terms from differentiating zeroth order auxiliary
308    * functions (an0) with respect to x, y or z.
309    *
310    * @param n Multipole order.
311    */
312   private void fn(int n) {
313     switch (n) {
314       case 0 -> fn[0] = f;
315       case 1 -> {
316         fn[0] = f;
317         fn[1] = f1;
318       }
319       case 2 -> {
320         fn[0] = f;
321         fn[1] = f1;
322         fn[2] = f2;
323       }
324       case 3 -> {
325         fn[0] = f;
326         fn[1] = f1;
327         fn[2] = f2;
328         fn[3] = fr.mul(f2);
329       }
330       case 4 -> {
331         fn[0] = f;
332         fn[1] = f1;
333         fn[2] = f2;
334         fn[3] = fr.mul(f2);
335         fn[4] = fr.mul(fn[3]);
336       }
337       case 5 -> {
338         fn[0] = f;
339         fn[1] = f1;
340         fn[2] = f2;
341         fn[3] = fr.mul(f2);
342         fn[4] = fr.mul(fn[3]);
343         fn[5] = fr.mul(fn[4]);
344       }
345       case 6 -> {
346         fn[0] = f;
347         fn[1] = f1;
348         fn[2] = f2;
349         fn[3] = fr.mul(f2);
350         fn[4] = fr.mul(fn[3]);
351         fn[5] = fr.mul(fn[4]);
352         fn[6] = fr.mul(fn[5]);
353       }
354       default -> {
355         fn[0] = f;
356         fn[1] = f1;
357         fn[2] = f2;
358         for (int i = 3; i <= n; i++) {
359           fn[i] = fr.mul(fn[i - 1]);
360         }
361       }
362     }
363   }
364 
365   /**
366    * Compute the function b, which are chain rule terms from differentiating zeroth order auxiliary
367    * functions (an0) with respect to Ai or Aj.
368    *
369    * @param n Multipole order.
370    */
371   protected void bn(int n) {
372     var b2 = expTerm.mul(2.0).div(gcAiAj.mul(gcAiAj)).mul(ratio.neg().sub(1.0));
373     switch (n) {
374       case 0 -> bn[0] = expTerm.mul(0.5).mul(ratio.neg().add(1.0));
375       case 1 -> {
376         bn[0] = expTerm.mul(0.5).mul(ratio.neg().add(1.0));
377         bn[1] = r2.mul(expTerm).div(gcAiAj.mul(gcAiAj)).neg();
378       }
379       case 2 -> {
380         bn[0] = expTerm.mul(0.5).mul(ratio.neg().add(1.0));
381         bn[1] = r2.mul(expTerm).div(gcAiAj.mul(gcAiAj)).neg();
382         bn[2] = b2;
383       }
384       default -> {
385         bn[0] = expTerm.mul(0.5).mul(ratio.neg().add(1.0));
386         bn[1] = r2.mul(expTerm).div(gcAiAj.mul(gcAiAj)).neg();
387         bn[2] = b2;
388         var br = zero.add(2.0).div(gcAiAj.mul(rb2));
389         var f2 = zero.add(2.0).div(gcAiAj.mul(gc)).mul(expTerm);
390         var frA = one;
391         var frB = fr;
392         for (int i = 3; i < n; i++) {
393           // bn[i] = (i - 2) * pow(fr, i - 3) * br * f2 + pow(fr, i - 2) * b2;
394           bn[i] = frA.mul(i - 2).mul(br).mul(f2).add(frB.mul(b2));
395           frA = frA.mul(fr);
396           frB = frB.mul(fr);
397         }
398       }
399     }
400   }
401 
402   /**
403    * Compute the potential auxiliary function up to order n.
404    *
405    * @param n order.
406    */
407   private void an0(int n) {
408     DoubleVector inverseF = one.div(f);
409     DoubleVector inverseF2 = inverseF.mul(inverseF);
410     for (int i = 0; i <= n; i++) {
411       anm[i][0] = inverseF.mul(kirkwoodSource[i]);
412       inverseF = inverseF.mul(inverseF2);
413     }
414   }
415 
416   /**
417    * Compute the Born chain-rule auxiliary function up to order n.
418    *
419    * @param n order.
420    */
421   private void bn0(int n) {
422     for (int i = 0; i <= n; i++) {
423       bnm[i][0] = bn[0].mul(anm[i + 1][0]);
424     }
425   }
426 
427 }