1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 package ffx.numerics.multipole;
39
40 import ffx.utilities.FFXTest;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.Parameterized;
44 import org.junit.runners.Parameterized.Parameters;
45
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.logging.Logger;
49
50 import static ffx.numerics.math.DoubleMath.length;
51 import static ffx.numerics.multipole.MultipoleTensorTest.Qi;
52 import static ffx.numerics.multipole.MultipoleTensorTest.Qk;
53 import static ffx.numerics.multipole.MultipoleTensorTest.Ui;
54 import static ffx.numerics.multipole.MultipoleTensorTest.UiEwald;
55 import static ffx.numerics.multipole.MultipoleTensorTest.Uk;
56 import static ffx.numerics.multipole.MultipoleTensorTest.UkEwald;
57 import static ffx.numerics.multipole.MultipoleTensorTest.permTorqueI;
58 import static ffx.numerics.multipole.MultipoleTensorTest.permTorqueIEwald;
59 import static ffx.numerics.multipole.MultipoleTensorTest.permTorqueK;
60 import static ffx.numerics.multipole.MultipoleTensorTest.permTorqueKEwald;
61 import static ffx.numerics.multipole.MultipoleTensorTest.permanentEnergy;
62 import static ffx.numerics.multipole.MultipoleTensorTest.permanentEnergyEwald;
63 import static ffx.numerics.multipole.MultipoleTensorTest.polarGradICoulomb;
64 import static ffx.numerics.multipole.MultipoleTensorTest.polarGradIEwald;
65 import static ffx.numerics.multipole.MultipoleTensorTest.polarGradIThole;
66 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueICoulomb;
67 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueIEwald;
68 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueIThole;
69 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueKCoulomb;
70 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueKEwald;
71 import static ffx.numerics.multipole.MultipoleTensorTest.polarTorqueKThole;
72 import static ffx.numerics.multipole.MultipoleTensorTest.polarizationEnergyCoulomb;
73 import static ffx.numerics.multipole.MultipoleTensorTest.polarizationEnergyEwald;
74 import static ffx.numerics.multipole.MultipoleTensorTest.polarizationEnergyThole;
75 import static ffx.numerics.multipole.MultipoleTensorTest.scaleMutual;
76 import static ffx.numerics.multipole.MultipoleTensorTest.xyz;
77 import static java.lang.String.format;
78 import static java.util.Arrays.fill;
79 import static org.junit.Assert.assertEquals;
80
81
82
83
84
85
86
87 @RunWith(Parameterized.class)
88 public class MultipoleTensorQITest extends FFXTest {
89
90
91
92
93 private static final Logger logger = Logger.getLogger(MultipoleTensorQITest.class.getName());
94
95 private final double tolerance = 1.0e-13;
96 private final double fdTolerance = 1.0e-6;
97 private final double[] r = new double[3];
98 private final double[] tensor;
99 private final double[] noStorageTensor;
100 private final double[] fastTensor;
101 private final int order;
102 private final int tensorCount;
103 private final String info;
104 private final Operator operator;
105 private final double beta = 0.545;
106 private final double thole = 0.39;
107 private final double AiAk = 1.061104559485911;
108
109 public MultipoleTensorQITest(
110 String info,
111 int order,
112 Operator operator) {
113 this.info = info;
114 this.order = order;
115 r[0] = xyz[0];
116 r[1] = xyz[1];
117 r[2] = xyz[2];
118 this.tensorCount = MultipoleUtilities.tensorCount(order);
119 tensor = new double[tensorCount];
120 noStorageTensor = new double[tensorCount];
121 fastTensor = new double[tensorCount];
122 this.operator = operator;
123 }
124
125 @Parameters
126 public static Collection<Object[]> data() {
127 return Arrays.asList(
128 new Object[][]{
129 {
130 "Order 5 Coulomb",
131 5,
132 Operator.COULOMB
133 },
134 {
135 "Order 5 Screened Coulomb",
136 5,
137 Operator.SCREENED_COULOMB
138 },
139 {
140 "Order 4 Thole Field",
141 4,
142 Operator.THOLE_FIELD
143 }
144 });
145 }
146
147 @Test
148 public void codeGenerationTest() {
149
150 if (operator != Operator.COULOMB) {
151 return;
152 }
153
154 double[] Qi = {0.11, 0.22, 0.33, 0.44, 0.11, 0.22, -0.33, 0.12, 0.13, 0.14};
155 double[] r = {2.11, 2.12, 2.13};
156
157 double[] tensor = new double[tensorCount];
158 CoulombTensorQI multipoleTensor = new CoulombTensorQI(order);
159 logger.info(format(" Writing QI Order %d tensor recursion code:", order));
160
161 r[2] = length(r);
162 r[0] = 0.0;
163 r[1] = 0.0;
164 String code = multipoleTensor.codeTensorRecursion(r, tensor);
165 logger.info(format("\n%s", code));
166
167 logger.info(format(" Writing QI Order %d vectorized tensor recursion code:", order));
168 code = multipoleTensor.codeVectorTensorRecursion(r, tensor);
169 logger.info(format("\n%s", code));
170
171 PolarizableMultipole polarizableMultipole = new PolarizableMultipole(Qi, Ui, Ui);
172 QIFrame qiFrame = new QIFrame(r);
173 qiFrame.rotatePolarizableMultipole(polarizableMultipole);
174 StringBuilder sb = new StringBuilder();
175 logger.info(" Writing QI potential code due to multipole I:");
176 multipoleTensor.codePotentialMultipoleI(polarizableMultipole, tensor, 0, 0, 0, sb);
177 logger.info("\n\n" + sb);
178
179 sb = new StringBuilder();
180 logger.info(" Writing QI potential vectorized code due to multipole I:");
181 multipoleTensor.codePotentialMultipoleISIMD(polarizableMultipole, tensor, 0, 0, 0, sb);
182 logger.info("\n\n" + sb);
183
184 sb = new StringBuilder();
185 logger.info(" Writing QI potential code due to multipole K:");
186 multipoleTensor.codePotentialMultipoleK(polarizableMultipole, tensor, 0, 0, 0, sb);
187 logger.info("\n\n" + sb);
188
189 sb = new StringBuilder();
190 logger.info(" Writing QI potential vectorized code due to multipole K:");
191 multipoleTensor.codePotentialMultipoleKSIMD(polarizableMultipole, tensor, 0, 0, 0, sb);
192 logger.info("\n\n" + sb);
193
194 }
195
196 @Test
197 public void permanentMultipoleEnergyAndGradTest() {
198
199 if (operator == Operator.THOLE_FIELD) {
200 return;
201 }
202
203 MultipoleTensor multipoleTensor = new CoulombTensorQI(order);
204 if (operator == Operator.SCREENED_COULOMB) {
205 multipoleTensor = new EwaldTensorQI(order, beta);
206 }
207
208 double delta = 1.0e-5;
209 double delta2 = 2.0 * 1.0e-5;
210 double[] Gi = new double[3];
211 double[] Gk = new double[3];
212 double[] Ti = new double[3];
213 double[] Tk = new double[3];
214
215 PolarizableMultipole mI = new PolarizableMultipole(Qi, Ui, Ui);
216 PolarizableMultipole mK = new PolarizableMultipole(Qk, Uk, Uk);
217 QIFrame qiFrame = new QIFrame(r[0], r[1], r[2]);
218 qiFrame.rotatePolarizableMultipole(mI);
219 qiFrame.rotatePolarizableMultipole(mK);
220
221 multipoleTensor.generateTensor(r);
222 double e = multipoleTensor.multipoleEnergyAndGradient(mI, mK, Gi, Gk, Ti, Tk);
223
224 qiFrame.toGlobal(Gk);
225 qiFrame.toGlobal(Ti);
226 qiFrame.toGlobal(Tk);
227
228 if (operator == Operator.COULOMB) {
229 assertEquals(info + " QI Permanent Energy", permanentEnergy, e, tolerance);
230 assertEquals(info + " QI Multipole Torque I", permTorqueI[1], Ti[1], tolerance);
231 assertEquals(info + " QI Multipole Torque K", permTorqueK[1], Tk[1], tolerance);
232 } else if (operator == Operator.SCREENED_COULOMB) {
233 assertEquals(info + " QI Ewald Permanent Energy", permanentEnergyEwald, e, tolerance);
234 assertEquals(info + " QI Ewald Multipole Torque I", permTorqueIEwald[1], Ti[1], tolerance);
235 assertEquals(info + " QI Ewald Multipole Torque K", permTorqueKEwald[1], Tk[1], tolerance);
236 }
237
238
239 double aX = Gk[0];
240 double aY = Gk[1];
241 double aZ = Gk[2];
242
243 r[0] += delta;
244 mI.set(Qi, Ui, Ui);
245 mK.set(Qk, Uk, Uk);
246 qiFrame.setAndRotate(r, mI, mK);
247 multipoleTensor.generateTensor(r);
248 double posX = multipoleTensor.multipoleEnergy(mI, mK);
249
250 r[0] -= delta2;
251 mI.set(Qi, Ui, Ui);
252 mK.set(Qk, Uk, Uk);
253 qiFrame.setAndRotate(r, mI, mK);
254 multipoleTensor.generateTensor(r);
255 double negX = multipoleTensor.multipoleEnergy(mI, mK);
256 r[0] += delta;
257
258 r[1] += delta;
259 mI.set(Qi, Ui, Ui);
260 mK.set(Qk, Uk, Uk);
261 qiFrame.setAndRotate(r, mI, mK);
262 multipoleTensor.generateTensor(r);
263 double posY = multipoleTensor.multipoleEnergy(mI, mK);
264 r[1] -= delta2;
265 mI.set(Qi, Ui, Ui);
266 mK.set(Qk, Uk, Uk);
267 qiFrame.setAndRotate(r, mI, mK);
268 multipoleTensor.generateTensor(r);
269 double negY = multipoleTensor.multipoleEnergy(mI, mK);
270 r[1] += delta;
271
272 r[2] += delta;
273 mI.set(Qi, Ui, Ui);
274 mK.set(Qk, Uk, Uk);
275 qiFrame.setAndRotate(r, mI, mK);
276 multipoleTensor.generateTensor(r);
277 double posZ = multipoleTensor.multipoleEnergy(mI, mK);
278 r[2] -= delta2;
279 mI.set(Qi, Ui, Ui);
280 mK.set(Qk, Uk, Uk);
281 qiFrame.setAndRotate(r, mI, mK);
282 multipoleTensor.generateTensor(r);
283 double negZ = multipoleTensor.multipoleEnergy(mI, mK);
284 r[2] += delta;
285
286 double expect = aX;
287 double actual = (posX - negX) / delta2;
288 assertEquals(info + " QI Force X", expect, actual, fdTolerance);
289
290 expect = aY;
291 actual = (posY - negY) / delta2;
292 assertEquals(info + " QI Force Y", expect, actual, fdTolerance);
293
294 expect = aZ;
295 actual = (posZ - negZ) / delta2;
296 assertEquals(info + " QI Force Z", expect, actual, fdTolerance);
297 }
298
299 @Test
300 public void polarizationEnergyAndGradTest() {
301
302 MultipoleTensor multipoleTensor = new CoulombTensorQI(order);
303 if (operator == Operator.THOLE_FIELD) {
304 multipoleTensor = new TholeTensorQI(order, thole, AiAk);
305 } else if (operator == Operator.SCREENED_COULOMB) {
306 multipoleTensor = new EwaldTensorQI(order, beta);
307 }
308
309 double delta = 1.0e-5;
310 double delta2 = 2.0 * 1.0e-5;
311 double[] Gi = new double[3];
312 double[] Ti = new double[3];
313 double[] Tk = new double[3];
314
315
316
317
318 PolarizableMultipole mI = new PolarizableMultipole(Qi, Ui, Ui);
319 PolarizableMultipole mK = new PolarizableMultipole(Qk, Uk, Uk);
320 if (operator == Operator.SCREENED_COULOMB) {
321 mI.setInducedDipole(UiEwald, UiEwald);
322 mK.setInducedDipole(UkEwald, UkEwald);
323 }
324
325 QIFrame qiFrame = new QIFrame(r[0], r[1], r[2]);
326 qiFrame.rotatePolarizableMultipole(mI);
327 qiFrame.rotatePolarizableMultipole(mK);
328
329 multipoleTensor.generateTensor(r);
330 double e = multipoleTensor.polarizationEnergyAndGradient(mI, mK, 1.0, 1.0, scaleMutual, Gi, Ti,
331 Tk);
332
333 qiFrame.toGlobal(Gi);
334 qiFrame.toGlobal(Ti);
335 qiFrame.toGlobal(Tk);
336
337
338 double aX = Gi[0];
339 double aY = Gi[1];
340 double aZ = Gi[2];
341
342 double energy = polarizationEnergyCoulomb;
343 double[] gradI = polarGradICoulomb;
344 double[] torqueI = polarTorqueICoulomb;
345 double[] torqueK = polarTorqueKCoulomb;
346 if (operator == Operator.THOLE_FIELD) {
347 energy = polarizationEnergyThole;
348 gradI = polarGradIThole;
349 torqueI = polarTorqueIThole;
350 torqueK = polarTorqueKThole;
351 } else if (operator == Operator.SCREENED_COULOMB) {
352 energy = polarizationEnergyEwald;
353 gradI = polarGradIEwald;
354 torqueI = polarTorqueIEwald;
355 torqueK = polarTorqueKEwald;
356 }
357 assertEquals(info + " Polarization Energy", energy, e, tolerance);
358 assertEquals(info + " Polarization GradX", gradI[0], aX, tolerance);
359 assertEquals(info + " Polarization GradY", gradI[1], aY, tolerance);
360 assertEquals(info + " Polarization GradZ", gradI[2], aZ, tolerance);
361 assertEquals(info + " Polarization TorqueX I", torqueI[0], Ti[0], tolerance);
362 assertEquals(info + " Polarization TorqueY I", torqueI[1], Ti[1], tolerance);
363 assertEquals(info + " Polarization TorqueZ I", torqueI[2], Ti[2], tolerance);
364 assertEquals(info + " Polarization TorqueX K", torqueK[0], Tk[0], tolerance);
365 assertEquals(info + " Polarization TorqueY K", torqueK[1], Tk[1], tolerance);
366 assertEquals(info + " Polarization TorqueZ K", torqueK[2], Tk[2], tolerance);
367
368 if (scaleMutual == 0.0) {
369
370 r[0] += delta;
371 mI.set(Qi, Ui, Ui);
372 mK.set(Qk, Uk, Uk);
373 qiFrame.setAndRotate(r, mI, mK);
374 multipoleTensor.generateTensor(r);
375 double posX = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
376
377 r[0] -= delta2;
378 mI.set(Qi, Ui, Ui);
379 mK.set(Qk, Uk, Uk);
380 qiFrame.setAndRotate(r, mI, mK);
381 multipoleTensor.generateTensor(r);
382 double negX = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
383 r[0] += delta;
384
385 r[1] += delta;
386 mI.set(Qi, Ui, Ui);
387 mK.set(Qk, Uk, Uk);
388 qiFrame.setAndRotate(r, mI, mK);
389 multipoleTensor.generateTensor(r);
390 double posY = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
391 r[1] -= delta2;
392 mI.set(Qi, Ui, Ui);
393 mK.set(Qk, Uk, Uk);
394 qiFrame.setAndRotate(r, mI, mK);
395 multipoleTensor.generateTensor(r);
396 double negY = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
397 r[1] += delta;
398
399 r[2] += delta;
400 mI.set(Qi, Ui, Ui);
401 mK.set(Qk, Uk, Uk);
402 qiFrame.setAndRotate(r, mI, mK);
403 multipoleTensor.generateTensor(r);
404 double posZ = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
405 r[2] -= delta2;
406 mI.set(Qi, Ui, Ui);
407 mK.set(Qk, Uk, Uk);
408 qiFrame.setAndRotate(r, mI, mK);
409 multipoleTensor.generateTensor(r);
410 double negZ = multipoleTensor.polarizationEnergy(mI, mK, 1.0);
411 r[2] += delta;
412
413
414
415 double expect = -aX;
416 double actual = 2.0 * (posX - negX) / delta2;
417 assertEquals(info + " QI Polarization Force X", expect, actual, fdTolerance);
418
419 expect = -aY;
420 actual = 2.0 * (posY - negY) / delta2;
421 assertEquals(info + " QI Polarization Force Y", expect, actual, fdTolerance);
422
423 expect = -aZ;
424 actual = 2.0 * (posZ - negZ) / delta2;
425 assertEquals(info + " QI Polarization Force Z", expect, actual, fdTolerance);
426 }
427 }
428
429 @Test
430 public void finiteDifferenceTest() {
431
432 MultipoleTensor multipoleTensor = new CoulombTensorQI(order);
433 if (operator == Operator.THOLE_FIELD) {
434 multipoleTensor = new TholeTensorQI(order, thole, AiAk);
435 } else if (operator == Operator.SCREENED_COULOMB) {
436 multipoleTensor = new EwaldTensorQI(order, beta);
437 }
438
439 r[0] = 0.0;
440 r[1] = 0.0;
441 r[2] = 2.1;
442
443 multipoleTensor.noStorageRecursion(r, tensor);
444 double[] tensorsPz = new double[tensorCount];
445 double[] tensorsNz = new double[tensorCount];
446 double delta = 1.0e-5;
447 double delta2 = delta * 2;
448
449 r[2] += delta;
450 multipoleTensor.noStorageRecursion(r, tensorsPz);
451 r[2] -= delta2;
452 multipoleTensor.noStorageRecursion(r, tensorsNz);
453 r[2] += delta;
454
455 tensorFiniteDifference(multipoleTensor, delta2, tensorsPz, tensorsNz);
456
457 multipoleTensor.recursion(r, tensor);
458 tensorFiniteDifference(multipoleTensor, delta2, tensorsPz, tensorsNz);
459
460 multipoleTensor.generateTensor();
461 multipoleTensor.getTensor(tensor);
462 tensorFiniteDifference(multipoleTensor, delta2, tensorsPz, tensorsNz);
463 }
464
465 @Test
466 public void multipoleTensorTest() {
467
468 MultipoleTensor multipoleTensor = new CoulombTensorQI(order);
469 if (operator == Operator.THOLE_FIELD) {
470 multipoleTensor = new TholeTensorQI(order, thole, AiAk);
471 } else if (operator == Operator.SCREENED_COULOMB) {
472 multipoleTensor = new EwaldTensorQI(order, beta);
473 }
474
475
476 multipoleTensor.noStorageRecursion(r, noStorageTensor);
477 multipoleTensor.recursion(r, tensor);
478 multipoleTensor.generateTensor();
479 multipoleTensor.getTensor(fastTensor);
480
481 for (int i = 0; i < tensorCount; i++) {
482 double expect = noStorageTensor[i];
483 double actual = tensor[i];
484 assertEquals(info + " @ " + i, expect, actual, tolerance);
485 if (order == 4 || order == 5) {
486 expect = noStorageTensor[i];
487 actual = fastTensor[i];
488 assertEquals(info + " @ " + i, expect, actual, tolerance);
489 }
490 }
491
492
493
494 r[2] = length(r);
495 r[0] = 0.0;
496 r[1] = 0.0;
497 fill(noStorageTensor, 0.0);
498 fill(tensor, 0.0);
499 fill(fastTensor, 0.0);
500 multipoleTensor.noStorageRecursion(r, noStorageTensor);
501 multipoleTensor.recursion(r, tensor);
502 multipoleTensor.setTensor(fastTensor);
503 multipoleTensor.generateTensor();
504 multipoleTensor.getTensor(fastTensor);
505
506 for (int i = 0; i < tensorCount; i++) {
507 double expect = noStorageTensor[i];
508 double actual = tensor[i];
509 assertEquals(info + " @ " + i, expect, actual, tolerance);
510 if (order == 4 || order == 5) {
511 expect = noStorageTensor[i];
512 actual = fastTensor[i];
513 assertEquals(info + " @ " + i, expect, actual, tolerance);
514 }
515 }
516 }
517
518 private void tensorFiniteDifference(
519 MultipoleTensor multipoleTensor, double delta2, double[] tensorsPz, double[] tensorsNz) {
520
521 int start = 0;
522
523
524 if (operator == Operator.THOLE_FIELD) {
525 start = 1;
526 }
527
528
529 for (int l = start; l < order; l++) {
530
531 double expect = tensor[multipoleTensor.ti(l, 0, 1)];
532 double actual =
533 (tensorsPz[multipoleTensor.ti(l, 0, 0)] - tensorsNz[multipoleTensor.ti(l, 0, 0)]) / delta2;
534 assertEquals(info + " (d/dx): " + l, expect, actual, fdTolerance);
535 }
536 for (int l = 0; l < order; l++) {
537 for (int m = 1; m < order - l; m++) {
538
539 double expect = tensor[multipoleTensor.ti(l, m, 1)];
540 double actual =
541 (tensorsPz[multipoleTensor.ti(l, m, 0)] - tensorsNz[multipoleTensor.ti(l, m, 0)])
542 / delta2;
543 assertEquals(info + " (d/dx): " + l + " (d/dy): " + m, expect, actual, fdTolerance);
544 }
545 }
546
547 for (int l = 0; l < order; l++) {
548 for (int m = 0; m < order - l; m++) {
549 for (int n = 1; n < order - l - m; n++) {
550
551 double expect = tensor[multipoleTensor.ti(l, m, n + 1)];
552 double actual =
553 (tensorsPz[multipoleTensor.ti(l, m, n)] - tensorsNz[multipoleTensor.ti(l, m, n)])
554 / delta2;
555 assertEquals(info + " (d/dx): " + l + " (d/dy): " + m + " (d/dz):" + n, expect, actual,
556 fdTolerance);
557 }
558 }
559 }
560 }
561 }