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-2024.
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.potential.nonbonded.pme;
39  
40  import static ffx.potential.parameters.MultipoleType.t000;
41  import static ffx.potential.parameters.MultipoleType.t001;
42  import static ffx.potential.parameters.MultipoleType.t002;
43  import static ffx.potential.parameters.MultipoleType.t003;
44  import static ffx.potential.parameters.MultipoleType.t010;
45  import static ffx.potential.parameters.MultipoleType.t011;
46  import static ffx.potential.parameters.MultipoleType.t012;
47  import static ffx.potential.parameters.MultipoleType.t020;
48  import static ffx.potential.parameters.MultipoleType.t021;
49  import static ffx.potential.parameters.MultipoleType.t030;
50  import static ffx.potential.parameters.MultipoleType.t100;
51  import static ffx.potential.parameters.MultipoleType.t101;
52  import static ffx.potential.parameters.MultipoleType.t102;
53  import static ffx.potential.parameters.MultipoleType.t110;
54  import static ffx.potential.parameters.MultipoleType.t111;
55  import static ffx.potential.parameters.MultipoleType.t120;
56  import static ffx.potential.parameters.MultipoleType.t200;
57  import static ffx.potential.parameters.MultipoleType.t201;
58  import static ffx.potential.parameters.MultipoleType.t210;
59  import static ffx.potential.parameters.MultipoleType.t300;
60  import static org.apache.commons.math3.util.FastMath.sqrt;
61  
62  import edu.rit.pj.IntegerForLoop;
63  import edu.rit.pj.IntegerSchedule;
64  import edu.rit.pj.ParallelRegion;
65  import edu.rit.pj.ParallelTeam;
66  import edu.rit.pj.reduction.SharedDouble;
67  import ffx.crystal.Crystal;
68  import ffx.numerics.atomic.AtomicDoubleArray3D;
69  import ffx.numerics.multipole.MultipoleTensor;
70  import ffx.potential.bonded.Atom;
71  import ffx.potential.extended.ExtendedSystem;
72  import ffx.potential.nonbonded.ReciprocalSpace;
73  import java.util.logging.Level;
74  import java.util.logging.Logger;
75  
76  /**
77   * Parallel evaluation of the PME reciprocal space energy and gradient.
78   *
79   * @author Michael J. Schnieders
80   * @since 1.0
81   */
82  public class ReciprocalEnergyRegion extends ParallelRegion {
83  
84    private static final Logger logger = Logger.getLogger(ReciprocalEnergyRegion.class.getName());
85  
86    private static final double SQRT_PI = sqrt(Math.PI);
87    private static final int tensorCount = MultipoleTensor.tensorCount(3);
88    private final double electric;
89    private final double aewald1;
90    private final double aewald2;
91    private final double aewald3;
92    private final double aewald4;
93    private final double oneThird;
94    private final double twoThirds;
95    private final int maxThreads;
96    private final SharedDouble inducedDipoleSelfEnergy;
97    private final SharedDouble inducedDipoleRecipEnergy;
98    private final PermanentReciprocalEnergyLoop[] permanentReciprocalEnergyLoop;
99    private final InducedDipoleReciprocalEnergyLoop[] inducedDipoleReciprocalEnergyLoop;
100   /** Dimensions of [nsymm][nAtoms][3] */
101   public double[][][] inducedDipole;
102   public double[][][] inducedDipoleCR;
103   /** Convenience reference to inducedDipole[0] */
104   private double[][] ind;
105   /** Convenience reference to inducedDipoleCR[0] */
106   private double[][] indCR;
107   private double nfftX, nfftY, nfftZ;
108   private double[][] multipole;
109   private double[][] fracMultipole;
110   private double[][] fracMultipolePhi;
111   private double[][] fracInducedDipolePhi;
112   private double[][] fracInducedDipolePhiCR;
113   private double permanentSelfEnergy;
114   private double permanentReciprocalEnergy;
115   /** An ordered array of atoms in the system. */
116   private Atom[] atoms;
117   /** Unit cell and spacegroup information. */
118   private Crystal crystal;
119   /**
120    * When computing the polarization energy at Lambda there are 3 pieces.
121    *
122    * <p>1.) Upol(1) = The polarization energy computed normally (ie. system with ligand).
123    *
124    * <p>2.) Uenv = The polarization energy of the system without the ligand.
125    *
126    * <p>3.) Uligand = The polarization energy of the ligand by itself.
127    *
128    * <p>Upol(L) = L*Upol(1) + (1-L)*(Uenv + Uligand)
129    *
130    * <p>Set the "use" array to true for all atoms for part 1. Set the "use" array to true for all
131    * atoms except the ligand for part 2. Set the "use" array to true only for the ligand atoms for
132    * part 3.
133    *
134    * <p>The "use" array can also be employed to turn off atoms for computing the electrostatic
135    * energy of sub-structures.
136    */
137   private boolean[] use;
138   /** Dimensions of [nsymm][nAtoms][10] */
139   private double[][][] globalMultipole;
140   private double[][][] globalFracMultipole;
141   private double[][][] titrationMultipole;
142   private double[][][] tautomerMultipole;
143   private ExtendedSystem extendedSystem = null;
144   private boolean esvTerm = false;
145   private double[][] cartMultipolePhi;
146   private double[][] cartesianDipolePhi;
147   private double[][] cartesianDipolePhiCR;
148   /** Reciprocal space instance. */
149   private ReciprocalSpace reciprocalSpace;
150   private Polarization polarization;
151   /** Atomic Gradient array. */
152   private AtomicDoubleArray3D grad;
153   /** Atomic Torque array. */
154   private AtomicDoubleArray3D torque;
155   /** Partial derivative of the gradient with respect to Lambda. */
156   private AtomicDoubleArray3D lambdaGrad;
157   /** Partial derivative of the torque with respect to Lambda. */
158   private AtomicDoubleArray3D lambdaTorque;
159   /** If true, compute coordinate gradient. */
160   private boolean gradient;
161   /**
162    * If lambdaTerm is true, some ligand atom interactions with the environment are being turned
163    * on/off.
164    */
165   private boolean lambdaTerm;
166   /** Partial derivative with respect to Lambda. */
167   private SharedDouble shareddEdLambda;
168   /** Second partial derivative with respect to Lambda. */
169   private SharedDouble sharedd2EdLambda2;
170   // Alchemical control
171   private double permanentScale;
172   private double dlPowPerm;
173   private double d2lPowPerm;
174   private double polarizationScale;
175   private double dlPowPol;
176   private double d2lPowPol;
177   private double dEdLSign;
178 
179   public ReciprocalEnergyRegion(int nt, double aewald, double electric) {
180     permanentReciprocalEnergyLoop = new PermanentReciprocalEnergyLoop[nt];
181     inducedDipoleReciprocalEnergyLoop = new InducedDipoleReciprocalEnergyLoop[nt];
182     inducedDipoleSelfEnergy = new SharedDouble();
183     inducedDipoleRecipEnergy = new SharedDouble();
184     maxThreads = nt;
185 
186     this.electric = electric;
187     aewald1 = -electric * aewald / SQRT_PI;
188     aewald2 = 2.0 * aewald * aewald;
189     aewald3 = -2.0 / 3.0 * electric * aewald * aewald * aewald / SQRT_PI;
190     aewald4 = -2.0 * aewald3;
191     oneThird = 1.0 / 3.0;
192     twoThirds = 2.0 / 3.0;
193   }
194 
195   /**
196    * Execute the ReciprocalEnergyRegion with the passed ParallelTeam.
197    *
198    * @param parallelTeam The ParallelTeam instance to execute with.
199    */
200   public void executeWith(ParallelTeam parallelTeam) {
201     try {
202       parallelTeam.execute(this);
203     } catch (Exception e) {
204       String message = " Exception computing the electrostatic energy.\n";
205       logger.log(Level.SEVERE, message, e);
206     }
207   }
208 
209   @Override
210   public void finish() {
211     /*
212      The permanent multipole self energy contributions are large
213      enough that rounding differences that result from threads
214      finishing in different orders removes deterministic behavior.
215     */
216     permanentSelfEnergy = 0.0;
217     permanentReciprocalEnergy = 0.0;
218     for (int i = 0; i < maxThreads; i++) {
219       permanentSelfEnergy += permanentReciprocalEnergyLoop[i].eSelf;
220       permanentReciprocalEnergy += permanentReciprocalEnergyLoop[i].eRecip;
221     }
222   }
223 
224   public double getInducedDipoleReciprocalEnergy() {
225     return inducedDipoleRecipEnergy.get();
226   }
227 
228   public double getInducedDipoleSelfEnergy() {
229     return inducedDipoleSelfEnergy.get();
230   }
231 
232   public double getPermanentReciprocalEnergy() {
233     return permanentReciprocalEnergy;
234   }
235 
236   public double getPermanentSelfEnergy() {
237     return permanentSelfEnergy;
238   }
239 
240   public void init(
241       Atom[] atoms,
242       Crystal crystal,
243       boolean gradient,
244       boolean lambdaTerm,
245       boolean esvTerm,
246       boolean[] use,
247       double[][][] globalMultipole,
248       double[][][] globalFracMultipole,
249       double[][][] titrationMultipole,
250       double[][][] tautomerMultipole,
251       double[][] cartMultipolePhi,
252       double[][] fracMultipolePhi,
253       Polarization polarization,
254       double[][][] inducedDipole,
255       double[][][] inducedDipoleCR,
256       double[][] cartesianDipolePhi,
257       double[][] cartesianDipolePhiCR,
258       double[][] fracInducedDipolePhi,
259       double[][] fracInducedDipolePhiCR,
260       ReciprocalSpace reciprocalSpace,
261       AlchemicalParameters alchemicalParameters,
262       ExtendedSystem extendedSystem,
263       AtomicDoubleArray3D grad,
264       AtomicDoubleArray3D torque,
265       AtomicDoubleArray3D lambdaGrad,
266       AtomicDoubleArray3D lambdaTorque,
267       SharedDouble shareddEdLambda,
268       SharedDouble sharedd2EdLambda2
269       ) {
270     this.atoms = atoms;
271     this.crystal = crystal;
272     this.gradient = gradient;
273     this.lambdaTerm = lambdaTerm;
274     this.esvTerm = esvTerm;
275     this.use = use;
276     // Permanent
277     this.globalMultipole = globalMultipole;
278     this.globalFracMultipole = globalFracMultipole;
279     this.titrationMultipole = titrationMultipole;
280     this.tautomerMultipole = tautomerMultipole;
281     this.cartMultipolePhi = cartMultipolePhi;
282     this.fracMultipolePhi = fracMultipolePhi;
283     // Polarization
284     this.polarization = polarization;
285     this.inducedDipole = inducedDipole;
286     this.inducedDipoleCR = inducedDipoleCR;
287     this.cartesianDipolePhi = cartesianDipolePhi;
288     this.cartesianDipolePhiCR = cartesianDipolePhiCR;
289     this.fracInducedDipolePhi = fracInducedDipolePhi;
290     this.fracInducedDipolePhiCR = fracInducedDipolePhiCR;
291     this.reciprocalSpace = reciprocalSpace;
292     this.extendedSystem = extendedSystem;
293     // Alchemical control
294     this.permanentScale = alchemicalParameters.permanentScale;
295     this.dlPowPerm = alchemicalParameters.dlPowPerm;
296     this.d2lPowPerm = alchemicalParameters.d2lPowPerm;
297     this.polarizationScale = alchemicalParameters.polarizationScale;
298     this.dlPowPol = alchemicalParameters.dlPowPol;
299     this.d2lPowPol = alchemicalParameters.d2lPowPol;
300     this.dEdLSign = alchemicalParameters.dEdLSign;
301     // Output
302     this.grad = grad;
303     this.torque = torque;
304     this.lambdaGrad = lambdaGrad;
305     this.lambdaTorque = lambdaTorque;
306     this.shareddEdLambda = shareddEdLambda;
307     this.sharedd2EdLambda2 = sharedd2EdLambda2;
308   }
309 
310   @Override
311   public void run() throws Exception {
312     int threadIndex = getThreadIndex();
313     if (permanentReciprocalEnergyLoop[threadIndex] == null) {
314       permanentReciprocalEnergyLoop[threadIndex] = new PermanentReciprocalEnergyLoop();
315       inducedDipoleReciprocalEnergyLoop[threadIndex] = new InducedDipoleReciprocalEnergyLoop();
316     }
317     try {
318       int nAtoms = atoms.length;
319       execute(0, nAtoms - 1, permanentReciprocalEnergyLoop[threadIndex]);
320       if (polarization != Polarization.NONE) {
321         execute(0, nAtoms - 1, inducedDipoleReciprocalEnergyLoop[threadIndex]);
322       }
323     } catch (Exception e) {
324       String message =
325           "Fatal exception computing the real space field in thread " + threadIndex + "\n";
326       logger.log(Level.SEVERE, message, e);
327     }
328   }
329 
330   @Override
331   public void start() {
332     multipole = globalMultipole[0];
333     fracMultipole = globalFracMultipole[0];
334     ind = inducedDipole[0];
335     indCR = inducedDipoleCR[0];
336     inducedDipoleSelfEnergy.set(0.0);
337     inducedDipoleRecipEnergy.set(0.0);
338     nfftX = reciprocalSpace.getXDim();
339     nfftY = reciprocalSpace.getYDim();
340     nfftZ = reciprocalSpace.getZDim();
341   }
342 
343   private class PermanentReciprocalEnergyLoop extends IntegerForLoop {
344 
345     int threadID;
346     double eSelf;
347     double eRecip;
348 
349     @Override
350     public void finish() {
351       eSelf *= permanentScale;
352       eRecip *= permanentScale * 0.5 * electric;
353     }
354 
355     @Override
356     public void run(int lb, int ub) throws Exception {
357 
358       // Permanent multipole self energy and gradient.
359       for (int i = lb; i <= ub; i++) {
360         if (use[i]) {
361           double[] in = globalMultipole[0][i];
362           double cii = in[t000] * in[t000];
363           double dii = in[t100] * in[t100] + in[t010] * in[t010] + in[t001] * in[t001];
364           double qii = in[t200] * in[t200] + in[t020] * in[t020] + in[t002] * in[t002]
365               + 2.0 * (in[t110] * in[t110] + in[t101] * in[t101] + in[t011] * in[t011]);
366           eSelf += aewald1 * (cii + aewald2 * (dii / 3.0 + 2.0 * aewald2 * qii / 45.0));
367 
368           if (esvTerm && extendedSystem.isTitrating(i)) {
369             double[] indot = titrationMultipole[0][i];
370             double ciidot = indot[t000] * in[t000];
371             double diidot = indot[t100] * in[t100] + indot[t010] * in[t010] + indot[t001] * in[t001];
372             double qiidot = indot[t200] * in[t200] + indot[t020] * in[t020] + indot[t002] * in[t002]
373                 + 2.0 * (indot[t110] * in[t110] + indot[t101] * in[t101] + indot[t011] * in[t011]);
374             double eSelfTitrDot =
375                 2.0 * aewald1 * (ciidot + aewald2 * (diidot / 3.0 + 2.0 * aewald2 * qiidot / 45.0));
376             double eSelfTautDot = 0.0;
377             if (extendedSystem.isTautomerizing(i)) {
378               indot = tautomerMultipole[0][i];
379               ciidot = indot[t000] * in[t000];
380               diidot = indot[t100] * in[t100] + indot[t010] * in[t010] + indot[t001] * in[t001];
381               qiidot = indot[t200] * in[t200] + indot[t020] * in[t020] + indot[t002] * in[t002]
382                   + 2.0 * (indot[t110] * in[t110] + indot[t101] * in[t101] + indot[t011] * in[t011]);
383               eSelfTautDot = 2.0 * aewald1 * (ciidot + aewald2 * (diidot / 3.0
384                   + 2.0 * aewald2 * qiidot / 45.0));
385             }
386             double factor = permanentScale;
387             extendedSystem.addPermElecDeriv(i, eSelfTitrDot * factor, eSelfTautDot * factor);
388           }
389         }
390       }
391       if (lambdaTerm) {
392         shareddEdLambda.addAndGet(eSelf * dlPowPerm * dEdLSign);
393         sharedd2EdLambda2.addAndGet(eSelf * d2lPowPerm * dEdLSign);
394       }
395 
396       // Permanent multipole reciprocal space energy and gradient.
397       final double[][] recip = crystal.getUnitCell().A;
398 
399       double dUdL = 0.0;
400       double d2UdL2 = 0.0;
401       for (int i = lb; i <= ub; i++) {
402         if (use[i]) {
403           final double[] phi = cartMultipolePhi[i];
404           final double[] mpole = multipole[i];
405           final double[] fmpole = fracMultipole[i];
406           double e = mpole[t000] * phi[t000]
407               + mpole[t100] * phi[t100] + mpole[t010] * phi[t010] + mpole[t001] * phi[t001]
408               + oneThird * (mpole[t200] * phi[t200] + mpole[t020] * phi[t020]
409               + mpole[t002] * phi[t002]
410               + 2.0 * (mpole[t110] * phi[t110] + mpole[t101] * phi[t101] + mpole[t011] * phi[t011]));
411           eRecip += e;
412           if (esvTerm && extendedSystem.isTitrating(i)) {
413             double[] mpoleDot = titrationMultipole[0][i];
414             double edotTitr = mpoleDot[t000] * phi[t000]
415                 + mpoleDot[t100] * phi[t100] + mpoleDot[t010] * phi[t010]
416                 + mpoleDot[t001] * phi[t001]
417                 + oneThird * (mpoleDot[t200] * phi[t200] + mpoleDot[t020] * phi[t020]
418                 + mpoleDot[t002] * phi[t002]
419                 + 2.0 * (mpoleDot[t110] * phi[t110] + mpoleDot[t101] * phi[t101]
420                 + mpoleDot[t011] * phi[t011]));
421             double edotTaut = 0.0;
422             if (extendedSystem.isTautomerizing(i)) {
423               mpoleDot = tautomerMultipole[0][i];
424               edotTaut = mpoleDot[t000] * phi[t000]
425                   + mpoleDot[t100] * phi[t100] + mpoleDot[t010] * phi[t010]
426                   + mpoleDot[t001] * phi[t001]
427                   + oneThird * (mpoleDot[t200] * phi[t200] + mpoleDot[t020] * phi[t020]
428                   + mpoleDot[t002] * phi[t002]
429                   + 2.0 * (mpoleDot[t110] * phi[t110] + mpoleDot[t101] * phi[t101]
430                   + mpoleDot[t011] * phi[t011]));
431             }
432             double factor = permanentScale * electric;
433             extendedSystem.addPermElecDeriv(i, edotTitr * factor, edotTaut * factor);
434           }
435           if (gradient || lambdaTerm) {
436             final double[] fPhi = fracMultipolePhi[i];
437             double gx = fmpole[t000] * fPhi[t100]
438                 + fmpole[t100] * fPhi[t200] + fmpole[t010] * fPhi[t110] + fmpole[t001] * fPhi[t101]
439                 + fmpole[t200] * fPhi[t300] + fmpole[t020] * fPhi[t120] + fmpole[t002] * fPhi[t102]
440                 + fmpole[t110] * fPhi[t210] + fmpole[t101] * fPhi[t201] + fmpole[t011] * fPhi[t111];
441             double gy = fmpole[t000] * fPhi[t010]
442                 + fmpole[t100] * fPhi[t110] + fmpole[t010] * fPhi[t020] + fmpole[t001] * fPhi[t011]
443                 + fmpole[t200] * fPhi[t210] + fmpole[t020] * fPhi[t030] + fmpole[t002] * fPhi[t012]
444                 + fmpole[t110] * fPhi[t120] + fmpole[t101] * fPhi[t111] + fmpole[t011] * fPhi[t021];
445             double gz = fmpole[t000] * fPhi[t001]
446                 + fmpole[t100] * fPhi[t101] + fmpole[t010] * fPhi[t011] + fmpole[t001] * fPhi[t002]
447                 + fmpole[t200] * fPhi[t201] + fmpole[t020] * fPhi[t021] + fmpole[t002] * fPhi[t003]
448                 + fmpole[t110] * fPhi[t111] + fmpole[t101] * fPhi[t102] + fmpole[t011] * fPhi[t012];
449             gx *= nfftX;
450             gy *= nfftY;
451             gz *= nfftZ;
452             final double dfx = recip[0][0] * gx + recip[0][1] * gy + recip[0][2] * gz;
453             final double dfy = recip[1][0] * gx + recip[1][1] * gy + recip[1][2] * gz;
454             final double dfz = recip[2][0] * gx + recip[2][1] * gy + recip[2][2] * gz;
455             // Compute dipole torques
456             double tqx = -mpole[t010] * phi[t001] + mpole[t001] * phi[t010];
457             double tqy = -mpole[t001] * phi[t100] + mpole[t100] * phi[t001];
458             double tqz = -mpole[t100] * phi[t010] + mpole[t010] * phi[t100];
459             // Compute quadrupole torques
460             tqx -= twoThirds * (
461                 mpole[t110] * phi[t101] + mpole[t020] * phi[t011] + mpole[t011] * phi[t002]
462                     - mpole[t101] * phi[t110] - mpole[t011] * phi[t020] - mpole[t002] * phi[t011]);
463             tqy -= twoThirds * (
464                 mpole[t101] * phi[t200] + mpole[t011] * phi[t110] + mpole[t002] * phi[t101]
465                     - mpole[t200] * phi[t101] - mpole[t110] * phi[t011] - mpole[t101] * phi[t002]);
466             tqz -= twoThirds * (
467                 mpole[t200] * phi[t110] + mpole[t110] * phi[t020] + mpole[t101] * phi[t011]
468                     - mpole[t110] * phi[t200] - mpole[t020] * phi[t110] - mpole[t011] * phi[t101]);
469             if (gradient) {
470               double factor = permanentScale * electric;
471               grad.add(threadID, i, factor * dfx, factor * dfy, factor * dfz);
472               torque.add(threadID, i, factor * tqx, factor * tqy, factor * tqz);
473             }
474             if (lambdaTerm) {
475               dUdL += dEdLSign * dlPowPerm * e;
476               d2UdL2 += dEdLSign * d2lPowPerm * e;
477               double factor = dEdLSign * dlPowPerm * electric;
478               lambdaGrad.add(threadID, i, factor * dfx, factor * dfy, factor * dfz);
479               lambdaTorque.add(threadID, i, factor * tqx, factor * tqy, factor * tqz);
480             }
481           }
482         }
483       }
484 
485       if (lambdaTerm) {
486         shareddEdLambda.addAndGet(0.5 * dUdL * electric);
487         sharedd2EdLambda2.addAndGet(0.5 * d2UdL2 * electric);
488       }
489     }
490 
491     @Override
492     public IntegerSchedule schedule() {
493       return IntegerSchedule.fixed();
494     }
495 
496     @Override
497     public void start() {
498       eSelf = 0.0;
499       eRecip = 0.0;
500       threadID = getThreadIndex();
501     }
502   }
503 
504   private class InducedDipoleReciprocalEnergyLoop extends IntegerForLoop {
505 
506     private final double[] sfPhi = new double[tensorCount];
507     private final double[] sPhi = new double[tensorCount];
508     private double eSelf;
509     private double eRecip;
510     private int threadID;
511 
512     @Override
513     public void finish() {
514       inducedDipoleSelfEnergy.addAndGet(polarizationScale * eSelf);
515       inducedDipoleRecipEnergy.addAndGet(polarizationScale * eRecip);
516     }
517 
518     @Override
519     public void run(int lb, int ub) throws Exception {
520       // Induced dipole self energy and gradient.
521       for (int i = lb; i <= ub; i++) {
522         if (use[i]) {
523           final double[] indi = ind[i];
524           final double[] multipolei = multipole[i];
525           final double dix = multipolei[t100];
526           final double diy = multipolei[t010];
527           final double diz = multipolei[t001];
528           final double dii = indi[0] * dix + indi[1] * diy + indi[2] * diz;
529           eSelf += aewald3 * dii;
530         }
531       }
532       if (lambdaTerm) {
533         shareddEdLambda.addAndGet(dEdLSign * dlPowPol * eSelf);
534         sharedd2EdLambda2.addAndGet(dEdLSign * d2lPowPol * eSelf);
535       }
536       if (gradient || esvTerm) {
537         for (int i = lb; i <= ub; i++) {
538           if (use[i]) {
539             final double[] indi = ind[i];
540             final double[] indpi = indCR[i];
541             final double[] multipolei = multipole[i];
542             final double dix = multipolei[t100];
543             final double diy = multipolei[t010];
544             final double diz = multipolei[t001];
545             final double uix = 0.5 * (indi[0] + indpi[0]);
546             final double uiy = 0.5 * (indi[1] + indpi[1]);
547             final double uiz = 0.5 * (indi[2] + indpi[2]);
548             if (gradient) {
549               final double tix = aewald4 * (diy * uiz - diz * uiy);
550               final double tiy = aewald4 * (diz * uix - dix * uiz);
551               final double tiz = aewald4 * (dix * uiy - diy * uix);
552               torque.add(threadID, i, polarizationScale * tix, polarizationScale * tiy, polarizationScale * tiz);
553               if (lambdaTerm) {
554                 double factor = dEdLSign * dlPowPol;
555                 lambdaTorque.add(threadID, i, factor * tix, factor * tiy, factor * tiz);
556               }
557             }
558             if (esvTerm && extendedSystem.isTitrating(i)) {
559               double[] titrDot = titrationMultipole[0][i];
560               double indiDotMdot = uix * titrDot[t100] + uiy * titrDot[t010] + uiz * titrDot[t001];
561               double dIndSelfTitrdLi = polarizationScale * 2.0 * aewald3 * indiDotMdot;
562               double dIndSelfTautdLi = 0.0;
563               if (extendedSystem.isTautomerizing(i)) {
564                 double[] tautDot = tautomerMultipole[0][i];
565                 indiDotMdot = uix * tautDot[t100] + uiy * tautDot[t010] + uiz * tautDot[t001];
566                 dIndSelfTautdLi = polarizationScale * 2.0 * aewald3 * indiDotMdot;
567               }
568               extendedSystem.addIndElecDeriv(i, dIndSelfTitrdLi, dIndSelfTautdLi);
569             }
570           }
571         }
572       }
573 
574       // Induced dipole reciprocal space energy and gradient.
575       for (int i = lb; i <= ub; i++) {
576         double[] fracInd = new double[3];
577         double[] fracIndCR = new double[3];
578         if (use[i]) {
579           final double[] fPhi = fracMultipolePhi[i];
580           reciprocalSpace.cartToFracInducedDipole(inducedDipole[0][i], inducedDipoleCR[0][i], fracInd, fracIndCR);
581           final double indx = fracInd[0];
582           final double indy = fracInd[1];
583           final double indz = fracInd[2];
584           eRecip += indx * fPhi[t100] + indy * fPhi[t010] + indz * fPhi[t001];
585           if (gradient || esvTerm) {
586             final double[] iPhi = cartesianDipolePhi[i];
587             final double[] iCRPhi = cartesianDipolePhiCR[i];
588             final double[] fiPhi = fracInducedDipolePhi[i];
589             final double[] fiCRPhi = fracInducedDipolePhiCR[i];
590             final double[] mpolei = multipole[i];
591             final double[] fmpolei = fracMultipole[i];
592             final double inpx = fracIndCR[0];
593             final double inpy = fracIndCR[1];
594             final double inpz = fracIndCR[2];
595             final double insx = indx + inpx;
596             final double insy = indy + inpy;
597             final double insz = indz + inpz;
598             for (int t = 0; t < tensorCount; t++) {
599               sPhi[t] = 0.5 * (iPhi[t] + iCRPhi[t]);
600               sfPhi[t] = fiPhi[t] + fiCRPhi[t];
601             }
602             double gx = insx * fPhi[t200] + insy * fPhi[t110] + insz * fPhi[t101];
603             double gy = insx * fPhi[t110] + insy * fPhi[t020] + insz * fPhi[t011];
604             double gz = insx * fPhi[t101] + insy * fPhi[t011] + insz * fPhi[t002];
605             if (polarization == Polarization.MUTUAL) {
606               gx += indx * fiCRPhi[t200] + inpx * fiPhi[t200] + indy * fiCRPhi[t110]
607                   + inpy * fiPhi[t110] + indz * fiCRPhi[t101] + inpz * fiPhi[t101];
608               gy += indx * fiCRPhi[t110] + inpx * fiPhi[t110] + indy * fiCRPhi[t020]
609                   + inpy * fiPhi[t020] + indz * fiCRPhi[t011] + inpz * fiPhi[t011];
610               gz += indx * fiCRPhi[t101] + inpx * fiPhi[t101] + indy * fiCRPhi[t011]
611                   + inpy * fiPhi[t011] + indz * fiCRPhi[t002] + inpz * fiPhi[t002];
612             }
613             gx += fmpolei[t000] * sfPhi[t100] + fmpolei[t100] * sfPhi[t200]
614                 + fmpolei[t010] * sfPhi[t110] + fmpolei[t001] * sfPhi[t101]
615                 + fmpolei[t200] * sfPhi[t300] + fmpolei[t020] * sfPhi[t120]
616                 + fmpolei[t002] * sfPhi[t102] + fmpolei[t110] * sfPhi[t210]
617                 + fmpolei[t101] * sfPhi[t201] + fmpolei[t011] * sfPhi[t111];
618             gy += fmpolei[t000] * sfPhi[t010] + fmpolei[t100] * sfPhi[t110]
619                 + fmpolei[t010] * sfPhi[t020] + fmpolei[t001] * sfPhi[t011]
620                 + fmpolei[t200] * sfPhi[t210] + fmpolei[t020] * sfPhi[t030]
621                 + fmpolei[t002] * sfPhi[t012] + fmpolei[t110] * sfPhi[t120]
622                 + fmpolei[t101] * sfPhi[t111] + fmpolei[t011] * sfPhi[t021];
623             gz += fmpolei[t000] * sfPhi[t001] + fmpolei[t100] * sfPhi[t101]
624                 + fmpolei[t010] * sfPhi[t011] + fmpolei[t001] * sfPhi[t002]
625                 + fmpolei[t200] * sfPhi[t201] + fmpolei[t020] * sfPhi[t021]
626                 + fmpolei[t002] * sfPhi[t003] + fmpolei[t110] * sfPhi[t111]
627                 + fmpolei[t101] * sfPhi[t102] + fmpolei[t011] * sfPhi[t012];
628             gx *= nfftX;
629             gy *= nfftY;
630             gz *= nfftZ;
631             double[][] recip = crystal.getUnitCell().A;
632             double dfx = recip[0][0] * gx + recip[0][1] * gy + recip[0][2] * gz;
633             double dfy = recip[1][0] * gx + recip[1][1] * gy + recip[1][2] * gz;
634             double dfz = recip[2][0] * gx + recip[2][1] * gy + recip[2][2] * gz;
635             dfx *= 0.5 * electric;
636             dfy *= 0.5 * electric;
637             dfz *= 0.5 * electric;
638             // Compute dipole torques
639             double tqx = -mpolei[t010] * sPhi[t001] + mpolei[t001] * sPhi[t010];
640             double tqy = -mpolei[t001] * sPhi[t100] + mpolei[t100] * sPhi[t001];
641             double tqz = -mpolei[t100] * sPhi[t010] + mpolei[t010] * sPhi[t100];
642             // Compute quadrupole torques
643             tqx -= twoThirds * (
644                 mpolei[t110] * sPhi[t101] + mpolei[t020] * sPhi[t011] + mpolei[t011] * sPhi[t002]
645                     - mpolei[t101] * sPhi[t110] - mpolei[t011] * sPhi[t020]
646                     - mpolei[t002] * sPhi[t011]);
647             tqy -= twoThirds * (
648                 mpolei[t101] * sPhi[t200] + mpolei[t011] * sPhi[t110] + mpolei[t002] * sPhi[t101]
649                     - mpolei[t200] * sPhi[t101] - mpolei[t110] * sPhi[t011]
650                     - mpolei[t101] * sPhi[t002]);
651             tqz -= twoThirds * (
652                 mpolei[t200] * sPhi[t110] + mpolei[t110] * sPhi[t020] + mpolei[t101] * sPhi[t011]
653                     - mpolei[t110] * sPhi[t200] - mpolei[t020] * sPhi[t110]
654                     - mpolei[t011] * sPhi[t101]);
655             tqx *= electric;
656             tqy *= electric;
657             tqz *= electric;
658             grad.add(threadID, i, polarizationScale * dfx, polarizationScale * dfy,
659                 polarizationScale * dfz);
660             torque.add(threadID, i, polarizationScale * tqx, polarizationScale * tqy,
661                 polarizationScale * tqz);
662             if (lambdaTerm) {
663               double factor = dEdLSign * dlPowPol;
664               lambdaGrad.add(threadID, i, factor * dfx, factor * dfy, factor * dfz);
665               lambdaTorque.add(threadID, i, factor * tqx, factor * tqy, factor * tqz);
666             }
667             if (esvTerm && extendedSystem.isTitrating(i)) {
668               final double[] mTitrDot = titrationMultipole[0][i];
669               double eTitrDot = mTitrDot[t000] * sPhi[t000]
670                   + mTitrDot[t100] * sPhi[t100] + mTitrDot[t010] * sPhi[t010] + mTitrDot[t001] * sPhi[t001]
671                   + oneThird * (mTitrDot[t200] * sPhi[t200] + mTitrDot[t020] * sPhi[t020] + mTitrDot[t002] * sPhi[t002]
672                   + 2.0 * (mTitrDot[t110] * sPhi[t110] + mTitrDot[t101] * sPhi[t101] + mTitrDot[t011] * sPhi[t011]));
673               double eTautDot = 0.0;
674               if (extendedSystem.isTautomerizing(i)) {
675                 final double[] mTautDot = tautomerMultipole[0][i];
676                 eTautDot = mTautDot[t000] * sPhi[t000]
677                     + mTautDot[t100] * sPhi[t100] + mTautDot[t010] * sPhi[t010] + mTautDot[t001] * sPhi[t001]
678                     + oneThird * (mTautDot[t200] * sPhi[t200] + mTautDot[t020] * sPhi[t020] + mTautDot[t002] * sPhi[t002]
679                     + 2.0 * (mTautDot[t110] * sPhi[t110] + mTautDot[t101] * sPhi[t101] + mTautDot[t011] * sPhi[t011]));
680               }
681               double factor = polarizationScale * electric;
682               extendedSystem.addIndElecDeriv(i, eTitrDot * factor, eTautDot * factor);
683             }
684           }
685         }
686       }
687       eRecip *= 0.5 * electric;
688       if (lambdaTerm) {
689         shareddEdLambda.addAndGet(dEdLSign * dlPowPol * eRecip);
690         sharedd2EdLambda2.addAndGet(dEdLSign * d2lPowPol * eRecip);
691       }
692     }
693 
694     @Override
695     public IntegerSchedule schedule() {
696       return IntegerSchedule.fixed();
697     }
698 
699     @Override
700     public void start() {
701       eSelf = 0.0;
702       eRecip = 0.0;
703       threadID = getThreadIndex();
704     }
705   }
706 }