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 }