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 static ffx.numerics.math.ScalarMath.doubleFactorial;
41  import static ffx.numerics.multipole.GKTensorMode.POTENTIAL;
42  import static java.lang.System.arraycopy;
43  import static java.util.Arrays.fill;
44  import static org.apache.commons.math3.util.FastMath.exp;
45  import static org.apache.commons.math3.util.FastMath.pow;
46  import static org.apache.commons.math3.util.FastMath.sqrt;
47  
48  /**
49   * The GKSource class generates the source terms for the Generalized Kirkwood version of the tensor recursion.
50   */
51  public class GKSource {
52  
53    private GKTensorMode mode = POTENTIAL;
54  
55    /**
56     * Born radius of atom i multiplied by the Born radius of atom j.
57     */
58    private double rb2;
59  
60    /**
61     * The GK term exp(-r2 / (gc * ai * aj)).
62     */
63    private double expTerm;
64  
65    /**
66     * The GK effective separation distance.
67     */
68    private double f;
69  
70    /**
71     * f1 = 1.0 - expTerm * igc;
72     */
73    private double f1;
74  
75    /**
76     * f2 = 2.0 * expTerm / (gc * gcAiAj);
77     */
78    private double f2;
79  
80    /**
81     * Generalized Kirkwood constant.
82     */
83    private final double gc;
84  
85    /**
86     * Inverse generalized Kirkwood constant.
87     */
88    private final double igc;
89  
90    /**
91     * The product: gc * Ai * Aj.
92     */
93    private double gcAiAj;
94  
95    /**
96     * The ratio -r2 / (gc * Ai * Aj).
97     */
98    private double ratio;
99  
100   /**
101    * The quantity: -2.0 / (gc * Ai * Aj);
102    */
103   private double fr;
104 
105   /**
106    * Separation distance squared.
107    */
108   private double r2;
109 
110   /**
111    * Recursion order.
112    */
113   private final int order;
114 
115   /**
116    * Compute the "source" terms for the recursion.
117    */
118   private final double[] kirkwoodSource;
119 
120   /**
121    * Coefficients needed when taking derivatives of auxiliary functions.
122    */
123   private final double[][] anmc;
124 
125   /**
126    * Chain rule terms from differentiating zeroth order auxiliary functions (an0) with respect to x,
127    * y or z.
128    */
129   private final double[] fn;
130 
131   /**
132    * Chain rule terms from differentiating zeroth order born radii auxiliary functions (bn0) with respect to Ai
133    * or Aj.
134    */
135   protected final double[] bn;
136 
137   /**
138    * Source terms for the Kirkwood version of the Challacombe et al. recursion.
139    */
140   private final double[][] anm;
141 
142   /**
143    * Source terms for the Kirkwood version of the Challacombe et al. recursion for Born-chain rule
144    * derivatives.
145    */
146   private final double[][] bnm;
147 
148   /**
149    * Construct a new GKSource object.
150    *
151    * @param order Recursion order.
152    * @param gc    Generalized Kirkwood constant.
153    */
154   public GKSource(int order, double gc) {
155     this.order = order;
156     this.gc = gc;
157     this.igc = 1.0 / gc;
158 
159     // Auxiliary terms for Generalized Kirkwood (equivalent to Coulomb and Thole Screening).
160     kirkwoodSource = new double[order + 1];
161     for (short n = 0; n <= order; n++) {
162       kirkwoodSource[n] = pow(-1, n) * doubleFactorial(2 * n - 1);
163     }
164 
165     anmc = new double[order + 1][];
166     for (int n = 0; n <= order; n++) {
167       anmc[n] = anmc(n);
168     }
169 
170     fn = new double[order + 1];
171     anm = new double[order + 1][order + 1];
172     bn = new double[order + 1];
173     bnm = new double[order + 1][order + 1];
174   }
175 
176   /**
177    * Generate source terms for the Kirkwood version of the Challacombe et al. recursion.
178    *
179    * @param work           The array to store the source terms.
180    * @param multipoleOrder The multipole order.
181    */
182   protected void source(double[] work, GKMultipoleOrder multipoleOrder) {
183     int mpoleOrder = multipoleOrder.getOrder();
184     fill(work, 0, mpoleOrder, 0.0);
185     if (mode == POTENTIAL) {
186       // Max derivatives.
187       int derivatives = order - mpoleOrder;
188       arraycopy(anm[mpoleOrder], 0, work, mpoleOrder, derivatives + 1);
189     } else {
190       // Max derivatives.
191       int derivatives = (order - 1) - mpoleOrder;
192       arraycopy(bnm[mpoleOrder], 0, work, mpoleOrder, derivatives + 1);
193     }
194   }
195 
196   /**
197    * Generate source terms for the Kirkwood version of the Challacombe et al. recursion.
198    *
199    * @param mode      The tensor mode.
200    * @param multipole The multipole order.
201    * @param r2        Separation distance squared.
202    * @param ai        Born radius of atom i.
203    * @param aj        Born radius of atom j.
204    */
205   public void generateSource(GKTensorMode mode, GKMultipoleOrder multipole, double r2,
206                              double ai, double aj) {
207     int multipoleOrder = multipole.getOrder();
208     this.mode = mode;
209 
210     this.r2 = r2;
211     rb2 = ai * aj;
212     gcAiAj = gc * rb2;
213     ratio = -r2 / gcAiAj;
214     expTerm = exp(ratio);
215     fr = -2.0 / gcAiAj;
216 
217     f = sqrt(r2 + rb2 * expTerm);
218     f1 = 1.0 - expTerm * igc;
219     f2 = 2.0 * expTerm / (gc * gcAiAj);
220 
221     if (mode == POTENTIAL) {
222       // Prepare the GK Potential tensor.
223       anm(order, order - multipoleOrder);
224     } else {
225       // Prepare the GK Born-chain rule tensor.
226       bnm(order - 1, (order - 1) - multipoleOrder);
227     }
228   }
229 
230   /**
231    * Fill the GK auxiliary matrix.
232    * <p>
233    * The first row is the GK potential and derivatives for monopoles. The second row is the GK
234    * potential and derivatives for dipoles. The third row is the GK potential and derivatives for
235    * quadrupoles.
236    *
237    * @param n           Order.
238    * @param derivatives Number of derivatives.
239    */
240   private void anm(int n, int derivatives) {
241     // Fill the fn chain rule terms.
242     fn(n);
243 
244     // Fill the auxiliary potential.
245     an0(n);
246 
247     // Derivative loop over columns.
248     for (int d = 1; d <= derivatives; d++) {
249       // The coefficients are the same for each order.
250       var coef = anmc[d];
251       // The current derivative can only be computed for order n - d.
252       int limit = n - d;
253       // Order loop over rows.
254       for (int order = 0; order <= limit; order++) {
255         // Compute this term from previous terms for 1 higher order.
256         var terms = anm[order + 1];
257         var sum = 0.0;
258         for (int i = 1; i <= d; i++) {
259           sum += coef[i - 1] * fn[i] * terms[d - i];
260         }
261         anm[order][d] = sum;
262       }
263     }
264   }
265 
266   /**
267    * Fill the GK auxiliary matrix derivatives with respect to Born radii.
268    * <p>
269    * The first row are derivatives for the monopole potential. The second row are derivatives for the
270    * dipole potential. The third row are derivatives for the quadrupole potential.
271    *
272    * @param n           Order.
273    * @param derivatives Number of derivatives.
274    */
275   private void bnm(int n, int derivatives) {
276     // Fill the bn chain rule terms.
277     bn(n);
278 
279     // Fill the auxiliary potential derivatives.
280     bn0(n);
281 
282     // Derivative loop over columns.
283     for (int d = 1; d <= derivatives; d++) {
284       // The coefficients are the same for each order.
285       var coef = anmc[d];
286       // The current derivative can only be computed for order n - d.
287       int limit = n - d;
288       // Order loop over rows.
289       for (int order = 0; order <= limit; order++) {
290         // Compute this term from previous terms for 1 higher order.
291         var terma = anm[order + 1];
292         var termb = bnm[order + 1];
293         var sum = 0.0;
294         for (int i = 1; i <= d; i++) {
295           sum += coef[i - 1] * bn[i] * terma[d - i];
296           sum += coef[i - 1] * fn[i] * termb[d - i];
297         }
298         bnm[order][d] = sum;
299       }
300     }
301   }
302 
303   /**
304    * Sets the function f, which are chain rule terms from differentiating zeroth order auxiliary
305    * functions (an0) with respect to x, y or z.
306    *
307    * @param n Multipole order.
308    */
309   private void fn(int n) {
310     switch (n) {
311       case 0 -> fn[0] = f;
312       case 1 -> {
313         fn[0] = f;
314         fn[1] = f1;
315       }
316       case 2 -> {
317         fn[0] = f;
318         fn[1] = f1;
319         fn[2] = f2;
320       }
321       case 3 -> {
322         fn[0] = f;
323         fn[1] = f1;
324         fn[2] = f2;
325         fn[3] = fr * f2;
326       }
327       case 4 -> {
328         fn[0] = f;
329         fn[1] = f1;
330         fn[2] = f2;
331         fn[3] = fr * f2;
332         fn[4] = fr * fn[3];
333       }
334       case 5 -> {
335         fn[0] = f;
336         fn[1] = f1;
337         fn[2] = f2;
338         fn[3] = fr * f2;
339         fn[4] = fr * fn[3];
340         fn[5] = fr * fn[4];
341       }
342       case 6 -> {
343         fn[0] = f;
344         fn[1] = f1;
345         fn[2] = f2;
346         fn[3] = fr * f2;
347         fn[4] = fr * fn[3];
348         fn[5] = fr * fn[4];
349         fn[6] = fr * fn[5];
350       }
351       default -> {
352         fn[0] = f;
353         fn[1] = f1;
354         fn[2] = f2;
355         for (int i = 3; i <= n; i++) {
356           fn[i] = fr * fn[i - 1];
357         }
358       }
359     }
360   }
361 
362   /**
363    * Compute the function b, which are chain rule terms from differentiating zeroth order auxiliary
364    * functions (an0) with respect to Ai or Aj.
365    *
366    * @param n Multipole order.
367    */
368   protected void bn(int n) {
369     var b2 = 2.0 * expTerm / (gcAiAj * gcAiAj) * (-ratio - 1.0);
370     switch (n) {
371       case 0 -> bn[0] = 0.5 * expTerm * (1.0 - ratio);
372       case 1 -> {
373         bn[0] = 0.5 * expTerm * (1.0 - ratio);
374         bn[1] = -r2 * expTerm / (gcAiAj * gcAiAj);
375       }
376       case 2 -> {
377         bn[0] = 0.5 * expTerm * (1.0 - ratio);
378         bn[1] = -r2 * expTerm / (gcAiAj * gcAiAj);
379         bn[2] = b2;
380       }
381       default -> {
382         bn[0] = 0.5 * expTerm * (1.0 - ratio);
383         bn[1] = -r2 * expTerm / (gcAiAj * gcAiAj);
384         bn[2] = b2;
385         var br = 2.0 / (gcAiAj * rb2);
386         var f2 = 2.0 / (gc * gcAiAj) * expTerm;
387         var frA = 1.0;
388         var frB = fr;
389         for (int i = 3; i < n; i++) {
390           // bn[i] = (i - 2) * pow(fr, i - 3) * br * f2 + pow(fr, i - 2) * b2;
391           bn[i] = (i - 2) * frA * br * f2 + frB * b2;
392           frA *= fr;
393           frB *= fr;
394         }
395       }
396     }
397   }
398 
399   /**
400    * Compute the potential auxiliary function up to order n.
401    *
402    * @param n order.
403    */
404   private void an0(int n) {
405     double inverseF = 1.0 / f;
406     double inverseF2 = inverseF * inverseF;
407     for (int i = 0; i <= n; i++) {
408       anm[i][0] = kirkwoodSource[i] * inverseF;
409       inverseF *= inverseF2;
410     }
411   }
412 
413   /**
414    * Compute the Born chain-rule auxiliary function up to order n.
415    *
416    * @param n order.
417    */
418   private void bn0(int n) {
419     for (int i = 0; i <= n; i++) {
420       bnm[i][0] = bn[0] * anm[i + 1][0];
421     }
422   }
423 
424   /**
425    * Return coefficients needed when taking derivatives of auxiliary functions.
426    *
427    * @param n Multipole order.
428    * @return Returns coefficients needed when taking derivatives of auxiliary functions.
429    */
430   protected static double[] anmc(int n) {
431     double[] ret = new double[n + 1];
432     ret[0] = 1.0;
433     switch (n) {
434       case 0 -> {
435         return ret;
436       }
437       case 1 -> {
438         ret[1] = 1.0;
439         return ret;
440       }
441       default -> {
442         ret[1] = 1.0;
443         var prev = new double[n];
444         prev[0] = 1.0;
445         prev[1] = 1.0;
446         for (int i = 3; i <= n; i++) {
447           for (int j = 2; j <= i - 1; j++) {
448             ret[j - 1] = prev[j - 2] + prev[j - 1];
449           }
450           ret[i - 1] = 1.0;
451           arraycopy(ret, 0, prev, 0, i);
452         }
453         return ret;
454       }
455     }
456   }
457 
458   /**
459    * Compute the Kirkwood dielectric function for a multipole of order n.
460    *
461    * @param n  Multipole order.
462    * @param Eh Homogeneous dielectric.
463    * @param Es Solvent dielectric.
464    * @return Returns (n+1)*(Eh-Es)/((n+1)*Es + n*Eh)) / Eh.
465    */
466   public static double cn(int n, double Eh, double Es) {
467     var ret = (n + 1) * (Eh - Es) / ((n + 1) * Es + n * Eh);
468     return ret / Eh;
469   }
470 
471   /**
472    * Compute the self-energy of a polarizable multipole.
473    *
474    * @param polarizableMultipole The polarizable multipole.
475    * @param ai                   Born radius of atom i.
476    * @param Eh                   Homogeneous dielectric.
477    * @param Es                   Solvent dielectric.
478    * @return Returns the self-energy of a polarizable multipole.
479    */
480   public static double selfEnergy(PolarizableMultipole polarizableMultipole,
481                                   double ai, double Eh, double Es) {
482     double q2 = polarizableMultipole.q * polarizableMultipole.q;
483     double dx = polarizableMultipole.dx;
484     double dy = polarizableMultipole.dy;
485     double dz = polarizableMultipole.dz;
486     double dx2 = dx * dx;
487     double dy2 = dy * dy;
488     double dz2 = dz * dz;
489     double ux = polarizableMultipole.ux;
490     double uy = polarizableMultipole.uy;
491     double uz = polarizableMultipole.uz;
492     double qxy2 = polarizableMultipole.qxy * polarizableMultipole.qxy;
493     double qxz2 = polarizableMultipole.qxz * polarizableMultipole.qxz;
494     double qyz2 = polarizableMultipole.qyz * polarizableMultipole.qyz;
495     double qxx2 = polarizableMultipole.qxx * polarizableMultipole.qxx;
496     double qyy2 = polarizableMultipole.qyy * polarizableMultipole.qyy;
497     double qzz2 = polarizableMultipole.qzz * polarizableMultipole.qzz;
498 
499     double a2 = ai * ai;
500     double a3 = ai * a2;
501     double a5 = a2 * a3;
502 
503     // Born partial charge
504     double e0 = cn(0, Eh, Es) * q2 / ai;
505     // Permanent Dipole
506     double e1 = cn(1, Eh, Es) * (dx2 + dy2 + dz2) / a3;
507     // Permanent Quadrupole
508     double e2 = cn(2, Eh, Es) * (3.0 * (qxy2 + qxz2 + qyz2) + 6.0 * (qxx2 + qyy2 + qzz2)) / a5;
509 
510     // Induced self-energy
511     double ei = cn(1, Eh, Es) * (dx * ux + dy * uy + dz * uz) / a3;
512 
513     return 0.5 * (e0 + e1 + e2 + ei);
514   }
515 
516 }