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 }