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  /**
43   * The GeneralizedKirkwoodTensor class contains utilities for generated Generalized Kirkwood
44   * interaction tensors.
45   *
46   * @author Michael J. Schnieders
47   * @since 1.0
48   */
49  public class GKTensorGlobal extends CoulombTensorGlobal {
50  
51    /**
52     * The GK tensor can be constructed for monopoles (GB), dipoles or quadrupoles.
53     */
54    protected final GKMultipoleOrder multipoleOrder;
55  
56    /**
57     * The Kirkwood dielectric function for the given multipole order.
58     */
59    private final double c;
60  
61    private final GKSource gkSource;
62  
63    /**
64     * Construct a new GKTensorGlobal object.
65     *
66     * @param multipoleOrder The multipole order.
67     * @param order          The number of derivatives to complete.
68     * @param gkSource       Generate the source terms for the GK recurrence.
69     * @param Eh             Homogeneous dielectric constant.
70     * @param Es             Solvent dielectric constant.
71     */
72    public GKTensorGlobal(GKMultipoleOrder multipoleOrder, int order, GKSource gkSource,
73                          double Eh, double Es) {
74      super(order);
75      this.multipoleOrder = multipoleOrder;
76      this.gkSource = gkSource;
77  
78      // Load the dielectric function
79      c = GKSource.cn(multipoleOrder.getOrder(), Eh, Es);
80    }
81  
82    /**
83     * GK Permanent multipole energy.
84     *
85     * @param mI PolarizableMultipole at site I.
86     * @param mK PolarizableMultipole at site K.
87     * @return a double.
88     */
89    @Override
90    public double multipoleEnergy(PolarizableMultipole mI, PolarizableMultipole mK) {
91      return switch (multipoleOrder) {
92        default -> {
93          chargeIPotentialAtK(mI, 2);
94          double eK = multipoleEnergy(mK);
95          chargeKPotentialAtI(mK, 2);
96          double eI = multipoleEnergy(mI);
97          yield c * 0.5 * (eK + eI);
98        }
99        case DIPOLE -> {
100         dipoleIPotentialAtK(mI.dx, mI.dy, mI.dz, 2);
101         double eK = multipoleEnergy(mK);
102         dipoleKPotentialAtI(mK.dx, mK.dy, mK.dz, 2);
103         double eI = multipoleEnergy(mI);
104         yield c * 0.5 * (eK + eI);
105       }
106       case QUADRUPOLE -> {
107         quadrupoleIPotentialAtK(mI, 2);
108         double eK = multipoleEnergy(mK);
109         quadrupoleKPotentialAtI(mK, 2);
110         double eI = multipoleEnergy(mI);
111         yield c * 0.5 * (eK + eI);
112       }
113     };
114   }
115 
116   /**
117    * GK Permanent multipole energy and gradient.
118    *
119    * @param mI PolarizableMultipole at site I.
120    * @param mK PolarizableMultipole at site K.
121    * @param Gi Coordinate gradient at site I.
122    * @param Gk Coordinate gradient at site K.
123    * @param Ti Torque at site I.
124    * @param Tk Torque at site K.
125    * @return the permanent multipole GK energy.
126    */
127   @Override
128   public double multipoleEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
129                                            double[] Gi, double[] Gk, double[] Ti, double[] Tk) {
130     return switch (multipoleOrder) {
131       default -> monopoleEnergyAndGradient(mI, mK, Gi, Gk, Ti, Tk);
132       case DIPOLE -> dipoleEnergyAndGradient(mI, mK, Gi, Gk, Ti, Tk);
133       case QUADRUPOLE -> quadrupoleEnergyAndGradient(mI, mK, Gi, Gk, Ti, Tk);
134     };
135   }
136 
137   /**
138    * Permanent multipole energy and gradient using the GK monopole tensor.
139    *
140    * @param mI PolarizableMultipole at site I.
141    * @param mK PolarizableMultipole at site K.
142    * @param Gi Coordinate gradient at site I.
143    * @param Gk Coordinate gradient at site K.
144    * @param Ti Torque at site I.
145    * @param Tk Torque at site K.
146    * @return the permanent multipole GK energy.
147    */
148   protected double monopoleEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
149                                              double[] Gi, double[] Gk, double[] Ti, double[] Tk) {
150 
151     // Compute the potential due to a multipole component at site I.
152     chargeIPotentialAtK(mI, 3);
153     double eK = multipoleEnergy(mK);
154     multipoleGradient(mK, Gk);
155     multipoleTorque(mK, Tk);
156 
157     // Compute the potential due to a multipole component at site K.
158     chargeKPotentialAtI(mK, 3);
159     double eI = multipoleEnergy(mI);
160     multipoleGradient(mI, Gi);
161     multipoleTorque(mI, Ti);
162 
163     double scale = c * 0.5;
164     Gi[0] = scale * (Gi[0] - Gk[0]);
165     Gi[1] = scale * (Gi[1] - Gk[1]);
166     Gi[2] = scale * (Gi[2] - Gk[2]);
167     Gk[0] = -Gi[0];
168     Gk[1] = -Gi[1];
169     Gk[2] = -Gi[2];
170 
171     Ti[0] = scale * Ti[0];
172     Ti[1] = scale * Ti[1];
173     Ti[2] = scale * Ti[2];
174     Tk[0] = scale * Tk[0];
175     Tk[1] = scale * Tk[1];
176     Tk[2] = scale * Tk[2];
177 
178     return scale * (eK + eI);
179   }
180 
181   /**
182    * Permanent multipole energy and gradient using the GK dipole tensor.
183    *
184    * @param mI PolarizableMultipole at site I.
185    * @param mK PolarizableMultipole at site K.
186    * @param Gi Coordinate gradient at site I.
187    * @param Gk Coordinate gradient at site K.
188    * @param Ti Torque at site I.
189    * @param Tk Torque at site K.
190    * @return the permanent multipole GK energy.
191    */
192   protected double dipoleEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
193                                            double[] Gi, double[] Gk, double[] Ti, double[] Tk) {
194 
195     // Compute the potential due to a multipole component at site I.
196     dipoleIPotentialAtK(mI.dx, mI.dy, mI.dz, 3);
197     double eK = multipoleEnergy(mK);
198     multipoleGradient(mK, Gk);
199     multipoleTorque(mK, Tk);
200 
201     // Need the torque on site I pole due to site K multipole.
202     // Only torque on the site I dipole.
203     multipoleKPotentialAtI(mK, 1);
204     dipoleTorque(mI, Ti);
205 
206     // Compute the potential due to a multipole component at site K.
207     dipoleKPotentialAtI(mK.dx, mK.dy, mK.dz, 3);
208     double eI = multipoleEnergy(mI);
209     multipoleGradient(mI, Gi);
210     multipoleTorque(mI, Ti);
211 
212     // Need the torque on site K pole due to multipole on site I.
213     // Only torque on the site K dipole.
214     multipoleIPotentialAtK(mI, 1);
215     dipoleTorque(mK, Tk);
216 
217     double scale = c * 0.5;
218     Gi[0] = scale * (Gi[0] - Gk[0]);
219     Gi[1] = scale * (Gi[1] - Gk[1]);
220     Gi[2] = scale * (Gi[2] - Gk[2]);
221     Gk[0] = -Gi[0];
222     Gk[1] = -Gi[1];
223     Gk[2] = -Gi[2];
224 
225     Ti[0] = scale * Ti[0];
226     Ti[1] = scale * Ti[1];
227     Ti[2] = scale * Ti[2];
228     Tk[0] = scale * Tk[0];
229     Tk[1] = scale * Tk[1];
230     Tk[2] = scale * Tk[2];
231 
232     return scale * (eK + eI);
233   }
234 
235   /**
236    * Permanent multipole energy and gradient using the GK quadrupole tensor.
237    *
238    * @param mI PolarizableMultipole at site I.
239    * @param mK PolarizableMultipole at site K.
240    * @param Gi Coordinate gradient at site I.
241    * @param Gk Coordinate gradient at site K.
242    * @param Ti Torque at site I.
243    * @param Tk Torque at site K.
244    * @return the permanent multipole GK energy.
245    */
246   protected double quadrupoleEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
247                                                double[] Gi, double[] Gk, double[] Ti, double[] Tk) {
248 
249     // Compute the potential due to a multipole component at site I.
250     quadrupoleIPotentialAtK(mI, 3);
251     double eK = multipoleEnergy(mK);
252     multipoleGradient(mK, Gk);
253     multipoleTorque(mK, Tk);
254 
255     // Need the torque on site I quadrupole due to site K multipole.
256     multipoleKPotentialAtI(mK, 2);
257     quadrupoleTorque(mI, Ti);
258 
259     // Compute the potential due to a multipole component at site K.
260     quadrupoleKPotentialAtI(mK, 3);
261     double eI = multipoleEnergy(mI);
262     multipoleGradient(mI, Gi);
263     multipoleTorque(mI, Ti);
264 
265     // Need the torque on site K quadrupole due to site I multipole.
266     multipoleIPotentialAtK(mI, 2);
267     quadrupoleTorque(mK, Tk);
268 
269     double scale = c * 0.5;
270     Gi[0] = scale * (Gi[0] - Gk[0]);
271     Gi[1] = scale * (Gi[1] - Gk[1]);
272     Gi[2] = scale * (Gi[2] - Gk[2]);
273     Gk[0] = -Gi[0];
274     Gk[1] = -Gi[1];
275     Gk[2] = -Gi[2];
276 
277     Ti[0] = scale * Ti[0];
278     Ti[1] = scale * Ti[1];
279     Ti[2] = scale * Ti[2];
280     Tk[0] = scale * Tk[0];
281     Tk[1] = scale * Tk[1];
282     Tk[2] = scale * Tk[2];
283 
284     return scale * (eK + eI);
285   }
286 
287   /**
288    * GK Permanent multipole Born grad.
289    *
290    * @param mI PolarizableMultipole at site I.
291    * @param mK PolarizableMultipole at site K.
292    * @return a double.
293    */
294   public double multipoleEnergyBornGrad(PolarizableMultipole mI, PolarizableMultipole mK) {
295     return multipoleEnergy(mI, mK);
296   }
297 
298   /**
299    * GK Polarization Energy.
300    *
301    * @param mI          PolarizableMultipole at site I.
302    * @param mK          PolarizableMultipole at site K.
303    * @param scaleEnergy This is ignored, since masking/scaling is not applied to GK interactions
304    *                    (everything is intermolecular).
305    * @return a double.
306    */
307   public double polarizationEnergy(PolarizableMultipole mI, PolarizableMultipole mK, DoubleVector scaleEnergy) {
308     return polarizationEnergy(mI, mK);
309   }
310 
311   /**
312    * GK Polarization Energy.
313    *
314    * @param mI PolarizableMultipole at site I.
315    * @param mK PolarizableMultipole at site K.
316    * @return a double.
317    */
318   public double polarizationEnergy(PolarizableMultipole mI, PolarizableMultipole mK) {
319     return switch (multipoleOrder) {
320       default -> {
321         // Find the GK charge potential of site I at site K.
322         chargeIPotentialAtK(mI, 1);
323         // Energy of induced dipole K in the field of permanent charge I.
324         double eK = polarizationEnergy(mK);
325         // Find the GK charge potential of site K at site I.
326         chargeKPotentialAtI(mK, 1);
327         // Energy of induced dipole I in the field of permanent charge K.
328         double eI = polarizationEnergy(mI);
329         yield c * 0.5 * (eK + eI);
330       }
331       case DIPOLE -> {
332         // Find the GK dipole potential of site I at site K.
333         dipoleIPotentialAtK(mI.dx, mI.dy, mI.dz, 1);
334         // Energy of induced dipole K in the field of permanent dipole I.
335         double eK = polarizationEnergy(mK);
336         // Find the GK induced dipole potential of site I at site K.
337         dipoleIPotentialAtK(mI.ux, mI.uy, mI.uz, 2);
338         // Energy of permanent multipole K in the field of induced dipole I.
339         eK += 0.5 * multipoleEnergy(mK);
340         // Find the GK dipole potential of site K at site I.
341         dipoleKPotentialAtI(mK.dx, mK.dy, mK.dz, 1);
342         // Energy of induced dipole I in the field of permanent dipole K.
343         double eI = polarizationEnergy(mI);
344         // Find the GK induced dipole potential of site K at site I.
345         dipoleKPotentialAtI(mK.ux, mK.uy, mK.uz, 2);
346         // Energy of permanent multipole I in the field of induced dipole K.
347         eI += 0.5 * multipoleEnergy(mI);
348         yield c * 0.5 * (eK + eI);
349       }
350       case QUADRUPOLE -> {
351         // Find the GK quadrupole potential of site I at site K.
352         quadrupoleIPotentialAtK(mI, 1);
353         // Energy of induced dipole K in the field of permanent quadrupole I.
354         double eK = polarizationEnergy(mK);
355         // Find the GK quadrupole potential of site K at site I.
356         quadrupoleKPotentialAtI(mK, 1);
357         // Energy of induced dipole I in the field of permanent quadrupole K.
358         double eI = polarizationEnergy(mI);
359         yield c * 0.5 * (eK + eI);
360       }
361     };
362   }
363 
364   /**
365    * GK Polarization Energy.
366    *
367    * @param mI PolarizableMultipole at site I.
368    * @param mK PolarizableMultipole at site K.
369    * @return a double.
370    */
371   public double polarizationEnergyBorn(PolarizableMultipole mI, PolarizableMultipole mK) {
372     return switch (multipoleOrder) {
373       default -> {
374         // Find the GK charge potential of site I at site K.
375         chargeIPotentialAtK(mI, 1);
376         // Energy of induced dipole K in the field of permanent charge I.
377         double eK = polarizationEnergyS(mK);
378         // Find the GK charge potential of site K at site I.
379         chargeKPotentialAtI(mK, 1);
380         // Energy of induced dipole I in the field of permanent charge K.
381         double eI = polarizationEnergyS(mI);
382         yield c * 0.5 * (eK + eI);
383       }
384       case DIPOLE -> {
385         // Find the GK dipole potential of site I at site K.
386         dipoleIPotentialAtK(mI.dx, mI.dy, mI.dz, 1);
387         // Energy of induced dipole K in the field of permanent dipole I.
388         double eK = polarizationEnergyS(mK);
389         // Find the GK induced dipole potential of site I at site K.
390         dipoleIPotentialAtK(mI.sx, mI.sy, mI.sz, 2);
391         // Energy of permanent multipole K in the field of induced dipole I.
392         eK += 0.5 * multipoleEnergy(mK);
393         // Find the GK dipole potential of site K at site I.
394         dipoleKPotentialAtI(mK.dx, mK.dy, mK.dz, 1);
395         // Energy of induced dipole I in the field of permanent dipole K.
396         double eI = polarizationEnergyS(mI);
397         // Find the GK induced dipole potential of site K at site I.
398         dipoleKPotentialAtI(mK.sx, mK.sy, mK.sz, 2);
399         // Energy of permanent multipole I in the field of induced dipole K.
400         eI += 0.5 * multipoleEnergy(mI);
401         yield c * 0.5 * (eK + eI);
402       }
403       case QUADRUPOLE -> {
404         // Find the GK quadrupole potential of site I at site K.
405         quadrupoleIPotentialAtK(mI, 1);
406         // Energy of induced dipole K in the field of permanent quadrupole I.
407         double eK = polarizationEnergyS(mK);
408         // Find the GK quadrupole potential of site K at site I.
409         quadrupoleKPotentialAtI(mK, 1);
410         // Energy of induced dipole I in the field of permanent quadrupole K.
411         double eI = polarizationEnergyS(mI);
412         yield c * 0.5 * (eK + eI);
413       }
414     };
415   }
416 
417   /**
418    * Polarization Energy and Gradient.
419    *
420    * @param mI            PolarizableMultipole at site I.
421    * @param mK            PolarizableMultipole at site K.
422    * @param inductionMask This is ignored, since masking/scaling is not applied to GK
423    *                      interactions (everything is intermolecular).
424    * @param energyMask    This is ignored, since masking/scaling is not applied to GK interactions
425    *                      (everything is intermolecular).
426    * @param mutualMask    This should be set to zero for direction polarization.
427    * @param Gi            an array of {@link double} objects.
428    * @param Ti            an array of {@link double} objects.
429    * @param Tk            an array of {@link double} objects.
430    * @return a double.
431    */
432   @Override
433   public double polarizationEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
434                                               double inductionMask, double energyMask, double mutualMask, double[] Gi, double[] Ti,
435                                               double[] Tk) {
436     return switch (multipoleOrder) {
437       default -> monopolePolarizationEnergyAndGradient(mI, mK, Gi);
438       case DIPOLE -> dipolePolarizationEnergyAndGradient(mI, mK, mutualMask, Gi, Ti, Tk);
439       case QUADRUPOLE -> quadrupolePolarizationEnergyAndGradient(mI, mK, Gi, Ti, Tk);
440     };
441   }
442 
443   /**
444    * Monopole Polarization Energy and Gradient.
445    *
446    * @param mI PolarizableMultipole at site I.
447    * @param mK PolarizableMultipole at site K.
448    * @param Gi an array of {@link double} objects.
449    * @return a double.
450    */
451   public double monopolePolarizationEnergyAndGradient(PolarizableMultipole mI,
452                                                       PolarizableMultipole mK, double[] Gi) {
453 
454     // Find the permanent multipole potential at site k.
455     chargeIPotentialAtK(mI, 2);
456     // Energy of induced dipole k in the field of multipole i.
457     double eK = polarizationEnergy(mK);
458     // Derivative with respect to moving atom k.
459     Gi[0] = -(mK.sx * E200 + mK.sy * E110 + mK.sz * E101);
460     Gi[1] = -(mK.sx * E110 + mK.sy * E020 + mK.sz * E011);
461     Gi[2] = -(mK.sx * E101 + mK.sy * E011 + mK.sz * E002);
462 
463     // Find the permanent multipole potential and derivatives at site i.
464     chargeKPotentialAtI(mK, 2);
465     // Energy of induced dipole i in the field of multipole k.
466     double eI = polarizationEnergy(mI);
467     // Derivative with respect to moving atom i.
468     Gi[0] += (mI.sx * E200 + mI.sy * E110 + mI.sz * E101);
469     Gi[1] += (mI.sx * E110 + mI.sy * E020 + mI.sz * E011);
470     Gi[2] += (mI.sx * E101 + mI.sy * E011 + mI.sz * E002);
471 
472     double scale = c * 0.5;
473     Gi[0] *= scale;
474     Gi[1] *= scale;
475     Gi[2] *= scale;
476 
477     // Total polarization energy.
478     return scale * (eI + eK);
479   }
480 
481   /**
482    * Dipole Polarization Energy and Gradient.
483    *
484    * @param mI         PolarizableMultipole at site I.
485    * @param mK         PolarizableMultipole at site K.
486    * @param mutualMask This should be set to zero for direction polarization.
487    * @param Gi         an array of {@link double} objects.
488    * @param Ti         an array of {@link double} objects.
489    * @param Tk         an array of {@link double} objects.
490    * @return a double.
491    */
492   public double dipolePolarizationEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
493                                                     double mutualMask, double[] Gi, double[] Ti, double[] Tk) {
494 
495     // Find the permanent multipole potential and derivatives at site k.
496     dipoleIPotentialAtK(mI.dx, mI.dy, mI.dz, 2);
497     // Energy of induced dipole k in the field of multipole i.
498     double eK = polarizationEnergy(mK);
499     // Derivative with respect to moving atom k.
500     Gi[0] = -(mK.sx * E200 + mK.sy * E110 + mK.sz * E101);
501     Gi[1] = -(mK.sx * E110 + mK.sy * E020 + mK.sz * E011);
502     Gi[2] = -(mK.sx * E101 + mK.sy * E011 + mK.sz * E002);
503     // Find the potential at K due to the averaged induced dipole at site i.
504     dipoleIPotentialAtK(mI.sx, mI.sy, mI.sz, 2);
505     dipoleTorque(mK, Tk);
506 
507     // Find the GK induced dipole potential of site I at site K.
508     dipoleIPotentialAtK(mI.ux, mI.uy, mI.uz, 3);
509     // Energy of permanent multipole K in the field of induced dipole I.
510     eK += 0.5 * multipoleEnergy(mK);
511     double[] G = new double[3];
512     multipoleGradient(mK, G);
513     Gi[0] -= G[0];
514     Gi[1] -= G[1];
515     Gi[2] -= G[2];
516     multipoleTorque(mK, Tk);
517 
518     // Find the permanent multipole potential and derivatives at site i.
519     dipoleKPotentialAtI(mK.dx, mK.dy, mK.dz, 2);
520     // Energy of induced dipole i in the field of multipole k.
521     double eI = polarizationEnergy(mI);
522     // Derivative with respect to moving atom i.
523     Gi[0] += (mI.sx * E200 + mI.sy * E110 + mI.sz * E101);
524     Gi[1] += (mI.sx * E110 + mI.sy * E020 + mI.sz * E011);
525     Gi[2] += (mI.sx * E101 + mI.sy * E011 + mI.sz * E002);
526     // Find the potential at I due to the averaged induced dipole at k.
527     dipoleKPotentialAtI(mK.sx, mK.sy, mK.sz, 2);
528     dipoleTorque(mI, Ti);
529 
530     // Find the GK induced dipole potential of site K at site I.
531     dipoleKPotentialAtI(mK.ux, mK.uy, mK.uz, 3);
532     // Energy of permanent multipole I in the field of induced dipole K.
533     eI += 0.5 * multipoleEnergy(mI);
534     G = new double[3];
535     multipoleGradient(mI, G);
536     Gi[0] += G[0];
537     Gi[1] += G[1];
538     Gi[2] += G[2];
539     multipoleTorque(mI, Ti);
540 
541     // Get the induced-induced portion of the force (Ud . dC/dX . Up).
542     // This contribution does not exist for direct polarization (mutualMask == 0.0).
543     if (mutualMask != 0.0) {
544       // Find the potential and its derivatives at k due to induced dipole i.
545       dipoleIPotentialAtK(mI.ux, mI.uy, mI.uz, 2);
546       Gi[0] -= mutualMask * (mK.px * E200 + mK.py * E110 + mK.pz * E101);
547       Gi[1] -= mutualMask * (mK.px * E110 + mK.py * E020 + mK.pz * E011);
548       Gi[2] -= mutualMask * (mK.px * E101 + mK.py * E011 + mK.pz * E002);
549 
550       // Find the potential and its derivatives at i due to induced dipole k.
551       dipoleKPotentialAtI(mK.ux, mK.uy, mK.uz, 2);
552       Gi[0] += mutualMask * (mI.px * E200 + mI.py * E110 + mI.pz * E101);
553       Gi[1] += mutualMask * (mI.px * E110 + mI.py * E020 + mI.pz * E011);
554       Gi[2] += mutualMask * (mI.px * E101 + mI.py * E011 + mI.pz * E002);
555     }
556 
557     // Total polarization energy.
558     double scale = c * 0.5;
559     double energy = scale * (eI + eK);
560     Gi[0] *= scale;
561     Gi[1] *= scale;
562     Gi[2] *= scale;
563     Ti[0] *= scale;
564     Ti[1] *= scale;
565     Ti[2] *= scale;
566     Tk[0] *= scale;
567     Tk[1] *= scale;
568     Tk[2] *= scale;
569 
570     return energy;
571   }
572 
573   /**
574    * Quadrupole Polarization Energy and Gradient.
575    *
576    * @param mI PolarizableMultipole at site I.
577    * @param mK PolarizableMultipole at site K.
578    * @param Gi an array of {@link double} objects.
579    * @param Ti an array of {@link double} objects.
580    * @param Tk an array of {@link double} objects.
581    * @return a double.
582    */
583   public double quadrupolePolarizationEnergyAndGradient(PolarizableMultipole mI, PolarizableMultipole mK,
584                                                         double[] Gi, double[] Ti, double[] Tk) {
585 
586     // Find the permanent multipole potential and derivatives at site k.
587     quadrupoleIPotentialAtK(mI, 2);
588     // Energy of induced dipole k in the field of multipole i.
589     double eK = polarizationEnergy(mK);
590     // Derivative with respect to moving atom k.
591     Gi[0] = -(mK.sx * E200 + mK.sy * E110 + mK.sz * E101);
592     Gi[1] = -(mK.sx * E110 + mK.sy * E020 + mK.sz * E011);
593     Gi[2] = -(mK.sx * E101 + mK.sy * E011 + mK.sz * E002);
594 
595     // Find the permanent multipole potential and derivatives at site i.
596     quadrupoleKPotentialAtI(mK, 2);
597     // Energy of induced dipole i in the field of multipole k.
598     double eI = polarizationEnergy(mI);
599     // Derivative with respect to moving atom i.
600     Gi[0] += (mI.sx * E200 + mI.sy * E110 + mI.sz * E101);
601     Gi[1] += (mI.sx * E110 + mI.sy * E020 + mI.sz * E011);
602     Gi[2] += (mI.sx * E101 + mI.sy * E011 + mI.sz * E002);
603 
604     double scale = c * 0.5;
605     Gi[0] *= scale;
606     Gi[1] *= scale;
607     Gi[2] *= scale;
608 
609     // Find the potential and its derivatives at K due to the averaged induced dipole at site i.
610     dipoleIPotentialAtK(scale * mI.sx, scale * mI.sy, scale * mI.sz, 2);
611     quadrupoleTorque(mK, Tk);
612 
613     // Find the potential and its derivatives at I due to the averaged induced dipole at k.
614     dipoleKPotentialAtI(scale * mK.sx, scale * mK.sy, scale * mK.sz, 2);
615     quadrupoleTorque(mI, Ti);
616 
617     // Total polarization energy.
618     return scale * (eI + eK);
619   }
620 
621   /**
622    * GK Direct Polarization Born grad.
623    *
624    * @param mI PolarizableMultipole at site I.
625    * @param mK PolarizableMultipole at site K.
626    * @return Partial derivative of the Polarization energy with respect to a Born grad.
627    */
628   public double polarizationEnergyBornGrad(PolarizableMultipole mI, PolarizableMultipole mK) {
629     return 2.0 * polarizationEnergyBorn(mI, mK);
630   }
631 
632   /**
633    * GK Mutual Polarization Contribution to the Born grad.
634    *
635    * @param mI PolarizableMultipole at site I.
636    * @param mK PolarizableMultipole at site K.
637    * @return Mutual Polarization contribution to the partial derivative with respect to a Born grad.
638    */
639   public double mutualPolarizationEnergyBornGrad(PolarizableMultipole mI, PolarizableMultipole mK) {
640     double db = 0.0;
641     if (multipoleOrder == GKMultipoleOrder.DIPOLE) {
642       // Find the potential and its derivatives at k due to induced dipole i.
643       dipoleIPotentialAtK(mI.ux, mI.uy, mI.uz, 2);
644       db = 0.5 * (mK.px * E100 + mK.py * E010 + mK.pz * E001);
645 
646       // Find the potential and its derivatives at i due to induced dipole k.
647       dipoleKPotentialAtI(mK.ux, mK.uy, mK.uz, 2);
648       db += 0.5 * (mI.px * E100 + mI.py * E010 + mI.pz * E001);
649     }
650     return c * db;
651   }
652 
653   /**
654    * Generate source terms for the Kirkwood version of the Challacombe et al. recursion.
655    */
656   @Override
657   protected void source(double[] work) {
658     gkSource.source(work, multipoleOrder);
659   }
660 
661 }