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.potential.nonbonded.pme;
39  
40  import edu.rit.pj.IntegerForLoop;
41  import edu.rit.pj.IntegerSchedule;
42  import edu.rit.pj.ParallelRegion;
43  import edu.rit.pj.ParallelTeam;
44  import ffx.crystal.Crystal;
45  import ffx.crystal.SymOp;
46  import ffx.numerics.atomic.AtomicDoubleArray3D;
47  import ffx.potential.bonded.Atom;
48  import ffx.potential.extended.ExtendedSystem;
49  import ffx.potential.nonbonded.ParticleMeshEwald;
50  import ffx.potential.parameters.ForceField;
51  import ffx.potential.parameters.MultipoleType;
52  import ffx.potential.parameters.MultipoleType.MultipoleFrameDefinition;
53  import ffx.potential.parameters.PolarizeType;
54  
55  import java.util.List;
56  import java.util.logging.Level;
57  import java.util.logging.Logger;
58  
59  import static ffx.potential.nonbonded.pme.AlchemicalParameters.AlchemicalMode.SCALE;
60  import static ffx.potential.parameters.MultipoleType.getRotationMatrix;
61  import static ffx.potential.parameters.MultipoleType.rotateMultipole;
62  import static ffx.potential.parameters.MultipoleType.t000;
63  import static ffx.potential.parameters.MultipoleType.t001;
64  import static ffx.potential.parameters.MultipoleType.t002;
65  import static ffx.potential.parameters.MultipoleType.t010;
66  import static ffx.potential.parameters.MultipoleType.t011;
67  import static ffx.potential.parameters.MultipoleType.t020;
68  import static ffx.potential.parameters.MultipoleType.t100;
69  import static ffx.potential.parameters.MultipoleType.t101;
70  import static ffx.potential.parameters.MultipoleType.t110;
71  import static ffx.potential.parameters.MultipoleType.t200;
72  import static org.apache.commons.math3.util.FastMath.max;
73  
74  /**
75   * Parallel initialization of accumulation arrays, expand atomic coordinates and rotation of
76   * multipoles into the global frame.
77   *
78   * @author Michael J. Schnieders
79   * @since 1.0
80   */
81  public class InitializationRegion extends ParallelRegion {
82  
83    private static final Logger logger = Logger.getLogger(InitializationRegion.class.getName());
84  
85    private final ParticleMeshEwald particleMeshEwald;
86    /**
87     * If set to false, multipoles are fixed in their local frame and torques are zero, which is useful
88     * for narrowing down discrepancies between analytic and finite-difference derivatives(default is
89     * true).
90     */
91    private final boolean rotateMultipoles;
92    /**
93     * If set to false, multipole charges are set to zero (default is true).
94     */
95    private final boolean useCharges;
96    /**
97     * If set to false, multipole dipoles are set to zero (default is true).
98     */
99    private final boolean useDipoles;
100   /**
101    * If set to false, multipole quadrupoles are set to zero (default is true).
102    */
103   private final boolean useQuadrupoles;
104   /**
105    * Initialization Loops
106    */
107   private final InitializationLoop[] initializationLoop;
108   /**
109    * Rotate Multipole Loops
110    */
111   private final RotateMultipolesLoop[] rotateMultipolesLoop;
112   /**
113    * If lambdaTerm is true, zero out the lambda gradient and torque arrays.
114    */
115   private boolean lambdaTerm;
116   /**
117    * Control direct scaling of alchemical atoms multipoles and polarizabilities.
118    */
119   private AlchemicalParameters alchemicalParameters;
120   /**
121    * If esvTerm is true, the electrostatics of some atoms are being titrated.
122    */
123   private boolean esvTerm;
124   /**
125    * If esvSystem is not null, the electrostatics of some atoms are being titrated.
126    */
127   private ExtendedSystem esvSystem;
128   /**
129    * An ordered array of atoms in the system.
130    */
131   private Atom[] atoms;
132   /**
133    * Dimensions of [nsymm][xyz][nAtoms].
134    */
135   private double[][][] coordinates;
136   /**
137    * Unit cell and spacegroup information.
138    */
139   private Crystal crystal;
140   /**
141    * Multipole frame definition.
142    */
143   private MultipoleFrameDefinition[] frame;
144   /**
145    * Multipole frame defining atoms.
146    */
147   private int[][] axisAtom;
148   /**
149    * Dimensions of [nsymm][nAtoms][10]
150    */
151   private double[][][] globalMultipole;
152   /**
153    * Dimensions of [nsymm][nAtoms][10]
154    */
155   private double[][][] titrationMultipole;
156   private double[][][] tautomerMultipole;
157   /**
158    * Polarizability of each atom
159    */
160   private double[] polarizability;
161   private double[] titrationPolarizability;
162   private double[] tautomerPolarizability;
163   private double[] thole;
164   private double[] ipdamp;
165   /**
166    * The "use" array can be employed to turn off atoms for computing the electrostatic energy of
167    * sub-structures.
168    */
169   private boolean[] use;
170   /**
171    * Neighbor lists, including atoms beyond the real space cutoff. [nsymm][nAtoms][nAllNeighbors]
172    */
173   private int[][][] neighborLists;
174   /**
175    * Neighbor lists, without atoms beyond the real space cutoff. [nSymm][nAtoms][nIncludedNeighbors]
176    */
177   private int[][][] realSpaceLists;
178 
179   private int[][][] vaporLists;
180   /**
181    * Atomic Gradient array.
182    */
183   private AtomicDoubleArray3D grad;
184   /**
185    * Atomic Torque array.
186    */
187   private AtomicDoubleArray3D torque;
188   /**
189    * Partial derivative of the gradient with respect to Lambda.
190    */
191   private AtomicDoubleArray3D lambdaGrad;
192   /**
193    * Partial derivative of the torque with respect to Lambda.
194    */
195   private AtomicDoubleArray3D lambdaTorque;
196 
197   public InitializationRegion(ParticleMeshEwald particleMeshEwald, int maxThreads,
198                               ForceField forceField) {
199     initializationLoop = new InitializationLoop[maxThreads];
200     rotateMultipolesLoop = new RotateMultipolesLoop[maxThreads];
201     useCharges = forceField.getBoolean("USE_CHARGES", true);
202     useDipoles = forceField.getBoolean("USE_DIPOLES", true);
203     useQuadrupoles = forceField.getBoolean("USE_QUADRUPOLES", true);
204     rotateMultipoles = forceField.getBoolean("ROTATE_MULTIPOLES", true);
205     this.particleMeshEwald = particleMeshEwald;
206   }
207 
208   /**
209    * Execute the InitializationRegion with the passed ParallelTeam.
210    *
211    * @param parallelTeam The ParallelTeam instance to execute with.
212    */
213   public void executeWith(ParallelTeam parallelTeam) {
214     try {
215       parallelTeam.execute(this);
216     } catch (RuntimeException e) {
217       String message = "RuntimeException expanding coordinates and rotating multipoles.\n";
218       logger.log(Level.WARNING, message, e);
219       throw e;
220     } catch (Exception e) {
221       String message = "Fatal exception expanding coordinates and rotating multipoles.\n";
222       logger.log(Level.SEVERE, message, e);
223     }
224   }
225 
226   public void init(
227       boolean lambdaTerm,
228       AlchemicalParameters alchemicalParameters,
229       ExtendedSystem esvSystem,
230       Atom[] atoms,
231       double[][][] coordinates,
232       Crystal crystal,
233       MultipoleFrameDefinition[] frame,
234       int[][] axisAtom,
235       double[][][] globalMultipole,
236       double[][][] titrationMultipole,
237       double[][][] tautomerMultipole,
238       double[] polarizability,
239       double[] titrationPolarizability,
240       double[] tautomerPolarizability,
241       double[] thole,
242       double[] ipdamp,
243       boolean[] use,
244       int[][][] neighborLists,
245       int[][][] realSpaceLists,
246       AtomicDoubleArray3D grad,
247       AtomicDoubleArray3D torque,
248       AtomicDoubleArray3D lambdaGrad,
249       AtomicDoubleArray3D lambdaTorque) {
250     this.lambdaTerm = lambdaTerm;
251     this.alchemicalParameters = alchemicalParameters;
252     this.esvSystem = esvSystem;
253     if (esvSystem != null) {
254       this.esvTerm = true;
255     }
256     this.atoms = atoms;
257     this.coordinates = coordinates;
258     this.crystal = crystal;
259     this.frame = frame;
260     this.axisAtom = axisAtom;
261     this.globalMultipole = globalMultipole;
262     this.titrationMultipole = titrationMultipole;
263     this.tautomerMultipole = tautomerMultipole;
264     this.polarizability = polarizability;
265     this.titrationPolarizability = titrationPolarizability;
266     this.tautomerPolarizability = tautomerPolarizability;
267     this.thole = thole;
268     this.ipdamp = ipdamp;
269     this.use = use;
270     this.neighborLists = neighborLists;
271     this.realSpaceLists = realSpaceLists;
272     this.vaporLists = alchemicalParameters.vaporLists;
273     this.grad = grad;
274     this.torque = torque;
275     this.lambdaGrad = lambdaGrad;
276     this.lambdaTorque = lambdaTorque;
277   }
278 
279   @Override
280   public void run() {
281 
282     int nAtoms = atoms.length;
283     int threadIndex = getThreadIndex();
284     if (initializationLoop[threadIndex] == null) {
285       initializationLoop[threadIndex] = new InitializationLoop();
286       rotateMultipolesLoop[threadIndex] = new RotateMultipolesLoop();
287     }
288     try {
289       execute(0, nAtoms - 1, initializationLoop[threadIndex]);
290       execute(0, nAtoms - 1, rotateMultipolesLoop[threadIndex]);
291     } catch (Exception e) {
292       String message = "Fatal exception initializing coordinates in thread: " + threadIndex + "\n";
293       logger.log(Level.SEVERE, message, e);
294     }
295   }
296 
297   private class InitializationLoop extends IntegerForLoop {
298 
299     private final double[] in = new double[3];
300     private final double[] out = new double[3];
301     private double[] x;
302     private double[] y;
303     private double[] z;
304     private int threadID;
305 
306     @Override
307     public void run(int lb, int ub) {
308       grad.reset(threadID, lb, ub);
309       torque.reset(threadID, lb, ub);
310       if (lambdaTerm) {
311         lambdaGrad.reset(threadID, lb, ub);
312         lambdaTorque.reset(threadID, lb, ub);
313       }
314 
315       // Initialize the local coordinate arrays.
316       for (int i = lb; i <= ub; i++) {
317         Atom atom = atoms[i];
318         x[i] = atom.getX();
319         y[i] = atom.getY();
320         z[i] = atom.getZ();
321         use[i] = atom.getUse();
322         /*
323          Real space Ewald is cutoff at ~7 A, compared to ~12 A for vdW,
324          so the number of neighbors is much more compact. A specific list for real space Ewald is filled during
325          computation of the permanent real space field that includes only evaluated interactions. Subsequent real
326          space loops, especially the SCF, then do not spend time evaluating pairwise distances outside the cutoff.
327         */
328         int size = neighborLists[0][i].length;
329         if (vaporLists != null) {
330           size = max(size, vaporLists[0][i].length);
331         }
332         if (realSpaceLists[0][i] == null || realSpaceLists[0][i].length < size) {
333           realSpaceLists[0][i] = new int[size];
334         }
335       }
336 
337       // Expand coordinates.
338       List<SymOp> symOps = crystal.spaceGroup.symOps;
339       int nSymm = symOps.size();
340       for (int iSymm = 1; iSymm < nSymm; iSymm++) {
341         SymOp symOp = symOps.get(iSymm);
342         double[] xs = coordinates[iSymm][0];
343         double[] ys = coordinates[iSymm][1];
344         double[] zs = coordinates[iSymm][2];
345         for (int i = lb; i <= ub; i++) {
346           in[0] = x[i];
347           in[1] = y[i];
348           in[2] = z[i];
349           crystal.applySymOp(in, out, symOp);
350           xs[i] = out[0];
351           ys[i] = out[1];
352           zs[i] = out[2];
353           int size = neighborLists[iSymm][i].length;
354           if (realSpaceLists[iSymm][i] == null || realSpaceLists[iSymm][i].length < size) {
355             realSpaceLists[iSymm][i] = new int[size];
356           }
357         }
358       }
359     }
360 
361     @Override
362     public IntegerSchedule schedule() {
363       return IntegerSchedule.fixed();
364     }
365 
366     @Override
367     public void start() {
368       x = coordinates[0][0];
369       y = coordinates[0][1];
370       z = coordinates[0][2];
371       threadID = getThreadIndex();
372     }
373   }
374 
375   private class RotateMultipolesLoop extends IntegerForLoop {
376 
377     // Local variables
378     private final double[] localOrigin = new double[3];
379     private final double[][] frameCoords = new double[4][3];
380     private final double[][] rotmat = new double[3][3];
381     private final double[] tempDipole = new double[3];
382     private final double[][] tempQuadrupole = new double[3][3];
383     private final double[] dipole = new double[3];
384     private final double[][] quadrupole = new double[3][3];
385 
386     @Override
387     public void run(int lb, int ub) {
388       List<SymOp> symOps = crystal.spaceGroup.symOps;
389       int nSymm = symOps.size();
390       for (int iSymm = 0; iSymm < nSymm; iSymm++) {
391         final double[] x = coordinates[iSymm][0];
392         final double[] y = coordinates[iSymm][1];
393         final double[] z = coordinates[iSymm][2];
394         for (int ii = lb; ii <= ub; ii++) {
395           Atom atom = atoms[ii];
396           double chargeScale = 1.0;
397           double dipoleScale = 1.0;
398           double quadrupoleScale = 1.0;
399           double polarizabilityScale = 1.0;
400           if (!useCharges) {
401             chargeScale = 0.0;
402           }
403           if (!useDipoles) {
404             dipoleScale = 0.0;
405           }
406           if (!useQuadrupoles) {
407             quadrupoleScale = 0.0;
408           }
409           if (!atom.getElectrostatics()) {
410             chargeScale = 0.0;
411             dipoleScale = 0.0;
412             quadrupoleScale = 0.0;
413             polarizabilityScale = 0.0;
414           }
415 
416           /*
417            * If the alchemical mode is SCALE and the atom is alchemical, scale the multipole and
418            * polarizability by the appropriate lambda value.
419            */
420           if (alchemicalParameters.mode == SCALE && atom.applyLambda()) {
421             chargeScale *= alchemicalParameters.permLambda;
422             dipoleScale *= alchemicalParameters.permLambda;
423             quadrupoleScale *= alchemicalParameters.permLambda;
424             polarizabilityScale *= alchemicalParameters.polLambda;
425           }
426 
427           // Collect the MultipoleType for Atom i.
428           MultipoleType multipoleType = particleMeshEwald.getMultipoleType(ii);
429           double[] in = multipoleType.getMultipole();
430           // Update the frame
431           frame[ii] = multipoleType.frameDefinition;
432           // Update the axis defining atom.
433           axisAtom[ii] = atom.getAxisAtomIndices();
434 
435           if (rotateMultipoles) {
436             // Local frame origin is the location of the current atomic multipole atom.
437             localOrigin[0] = x[ii];
438             localOrigin[1] = y[ii];
439             localOrigin[2] = z[ii];
440 
441             // Collect coordinates of the frame defining atoms.
442             int[] referenceSites = axisAtom[ii];
443             int nSites = 0;
444             if (referenceSites != null) {
445               nSites = referenceSites.length;
446             }
447             for (int i = 0; i < nSites; i++) {
448               int index = referenceSites[i];
449               frameCoords[i][0] = x[index];
450               frameCoords[i][1] = y[index];
451               frameCoords[i][2] = z[index];
452             }
453 
454             // Load the dipole for rotation.
455             tempDipole[0] = in[t100];
456             tempDipole[1] = in[t010];
457             tempDipole[2] = in[t001];
458             // Load the quadrupole for rotation.
459             tempQuadrupole[0][0] = in[t200];
460             tempQuadrupole[1][1] = in[t020];
461             tempQuadrupole[2][2] = in[t002];
462             tempQuadrupole[0][1] = in[t110];
463             tempQuadrupole[0][2] = in[t101];
464             tempQuadrupole[1][2] = in[t011];
465             tempQuadrupole[1][0] = in[t110];
466             tempQuadrupole[2][0] = in[t101];
467             tempQuadrupole[2][1] = in[t011];
468             // Check for chiral flipping.
469 
470             boolean needsChiralInversion = false;
471             /*
472             boolean needsChiralInversion = checkMultipoleChirality(frame[ii], localOrigin, frameCoords);
473             if (needsChiralInversion) {
474               // Flip the sign of the Y-dipole
475               tempDipole[1] = -tempDipole[1];
476               // Flip the sign of the XY-quadrupole
477               tempQuadrupole[0][1] = -tempQuadrupole[0][1];
478               tempQuadrupole[1][0] = -tempQuadrupole[1][0];
479               // Flip the sign of the YZ-quadrupole
480               tempQuadrupole[1][2] = -tempQuadrupole[1][2];
481               tempQuadrupole[2][1] = -tempQuadrupole[2][1];
482             }
483             */
484             getRotationMatrix(frame[ii], localOrigin, frameCoords, rotmat);
485             rotateMultipole(rotmat, tempDipole, tempQuadrupole, dipole, quadrupole);
486             double[] out = globalMultipole[iSymm][ii];
487 
488             // Set the charge.
489             out[t000] = in[0] * chargeScale;
490             // Set the dipole in the global frame.
491             out[t100] = dipole[0] * dipoleScale;
492             out[t010] = dipole[1] * dipoleScale;
493             out[t001] = dipole[2] * dipoleScale;
494             // Set the quadrupole in the global frame.
495             out[t200] = quadrupole[0][0] * quadrupoleScale;
496             out[t020] = quadrupole[1][1] * quadrupoleScale;
497             out[t002] = quadrupole[2][2] * quadrupoleScale;
498             out[t110] = quadrupole[0][1] * quadrupoleScale;
499             out[t101] = quadrupole[0][2] * quadrupoleScale;
500             out[t011] = quadrupole[1][2] * quadrupoleScale;
501             /* For ESV atoms, also rotate and scale the Mdot multipole. */
502             if (esvTerm && esvSystem.isTitrating(ii)) {
503               double[] esvMultipoleTitrDot = new double[10];
504               double[] esvMultipoleTautDot = new double[10];
505               double titrationLambda = esvSystem.getTitrationLambda(ii);
506               double tautomerLambda = esvSystem.getTautomerLambda(ii);
507               esvMultipoleTitrDot = esvSystem.getTitrationUtils()
508                   .getMultipoleTitrationDeriv(atom, titrationLambda, tautomerLambda, esvMultipoleTitrDot);
509               esvMultipoleTautDot = esvSystem.getTitrationUtils()
510                   .getMultipoleTautomerDeriv(atom, titrationLambda, tautomerLambda, esvMultipoleTautDot);
511               double tempCharge = esvMultipoleTitrDot[0];
512               // Load the dipole for rotation.
513               tempDipole[0] = esvMultipoleTitrDot[t100];
514               tempDipole[1] = esvMultipoleTitrDot[t010];
515               tempDipole[2] = esvMultipoleTitrDot[t001];
516               // Load the quadrupole for rotation.
517               tempQuadrupole[0][0] = esvMultipoleTitrDot[t200];
518               tempQuadrupole[1][1] = esvMultipoleTitrDot[t020];
519               tempQuadrupole[2][2] = esvMultipoleTitrDot[t002];
520               tempQuadrupole[0][1] = esvMultipoleTitrDot[t110];
521               tempQuadrupole[0][2] = esvMultipoleTitrDot[t101];
522               tempQuadrupole[1][2] = esvMultipoleTitrDot[t011];
523               tempQuadrupole[1][0] = esvMultipoleTitrDot[t110];
524               tempQuadrupole[2][0] = esvMultipoleTitrDot[t101];
525               tempQuadrupole[2][1] = esvMultipoleTitrDot[t011];
526               if (needsChiralInversion) {
527                 tempDipole[1] = -tempDipole[1];
528                 tempQuadrupole[0][1] = -tempQuadrupole[0][1];
529                 tempQuadrupole[1][0] = -tempQuadrupole[1][0];
530                 tempQuadrupole[1][2] = -tempQuadrupole[1][2];
531                 tempQuadrupole[2][1] = -tempQuadrupole[2][1];
532               }
533               rotateMultipole(rotmat, tempDipole, tempQuadrupole, dipole, quadrupole);
534               out = titrationMultipole[iSymm][ii];
535               out[t000] = tempCharge * chargeScale;
536               // Load the dipole in the global frame.
537               out[t100] = dipole[0] * dipoleScale;
538               out[t010] = dipole[1] * dipoleScale;
539               out[t001] = dipole[2] * dipoleScale;
540               // Load the quadrupole in the global frame.
541               out[t200] = quadrupole[0][0] * quadrupoleScale;
542               out[t020] = quadrupole[1][1] * quadrupoleScale;
543               out[t002] = quadrupole[2][2] * quadrupoleScale;
544               out[t110] = quadrupole[0][1] * quadrupoleScale;
545               out[t101] = quadrupole[0][2] * quadrupoleScale;
546               out[t011] = quadrupole[1][2] * quadrupoleScale;
547 
548               tempCharge = esvMultipoleTautDot[0];
549               // Load the dipole for rotation.
550               tempDipole[0] = esvMultipoleTautDot[t100];
551               tempDipole[1] = esvMultipoleTautDot[t010];
552               tempDipole[2] = esvMultipoleTautDot[t001];
553               // Load the quadrupole for rotation.
554               tempQuadrupole[0][0] = esvMultipoleTautDot[t200];
555               tempQuadrupole[1][1] = esvMultipoleTautDot[t020];
556               tempQuadrupole[2][2] = esvMultipoleTautDot[t002];
557               tempQuadrupole[0][1] = esvMultipoleTautDot[t110];
558               tempQuadrupole[0][2] = esvMultipoleTautDot[t101];
559               tempQuadrupole[1][2] = esvMultipoleTautDot[t011];
560               tempQuadrupole[1][0] = esvMultipoleTautDot[t110];
561               tempQuadrupole[2][0] = esvMultipoleTautDot[t101];
562               tempQuadrupole[2][1] = esvMultipoleTautDot[t011];
563               if (needsChiralInversion) {
564                 tempDipole[1] = -tempDipole[1];
565                 tempQuadrupole[0][1] = -tempQuadrupole[0][1];
566                 tempQuadrupole[1][0] = -tempQuadrupole[1][0];
567                 tempQuadrupole[1][2] = -tempQuadrupole[1][2];
568                 tempQuadrupole[2][1] = -tempQuadrupole[2][1];
569               }
570               rotateMultipole(rotmat, tempDipole, tempQuadrupole, dipole, quadrupole);
571               out = tautomerMultipole[iSymm][ii];
572               out[t000] = tempCharge * chargeScale;
573               // Load the dipole in the global frame.
574               out[t100] = dipole[0] * dipoleScale;
575               out[t010] = dipole[1] * dipoleScale;
576               out[t001] = dipole[2] * dipoleScale;
577               // Load the quadrupole in the global frame.
578               out[t200] = quadrupole[0][0] * quadrupoleScale;
579               out[t020] = quadrupole[1][1] * quadrupoleScale;
580               out[t002] = quadrupole[2][2] * quadrupoleScale;
581               out[t110] = quadrupole[0][1] * quadrupoleScale;
582               out[t101] = quadrupole[0][2] * quadrupoleScale;
583               out[t011] = quadrupole[1][2] * quadrupoleScale;
584             }
585           } else {
586             // No multipole rotation for isolating torque vs. non-torque pieces of the multipole
587             // energy gradient.
588             double[] out = globalMultipole[iSymm][ii];
589             out[t000] = in[t000] * chargeScale;
590             out[t100] = in[t100] * dipoleScale;
591             out[t010] = in[t010] * dipoleScale;
592             out[t001] = in[t001] * dipoleScale;
593             out[t200] = in[t200] * quadrupoleScale;
594             out[t020] = in[t020] * quadrupoleScale;
595             out[t002] = in[t002] * quadrupoleScale;
596             out[t110] = in[t110] * quadrupoleScale;
597             out[t101] = in[t101] * quadrupoleScale;
598             out[t011] = in[t011] * quadrupoleScale;
599             /* For ESV atoms, also scale the Mdot multipole. */
600             if (esvTerm && esvSystem.isTitrating(ii)) {
601               double[] esvMultipoleTitrDot = new double[10];
602               double titrationLambda = esvSystem.getTitrationLambda(ii);
603               double tautomerLambda = esvSystem.getTautomerLambda(ii);
604               esvMultipoleTitrDot = esvSystem.getTitrationUtils()
605                   .getMultipoleTitrationDeriv(atom, titrationLambda,
606                       tautomerLambda, esvMultipoleTitrDot);
607               in = esvMultipoleTitrDot;
608               out = titrationMultipole[iSymm][ii];
609               out[t000] = in[t000] * chargeScale;
610               out[t100] = in[t100] * dipoleScale;
611               out[t010] = in[t010] * dipoleScale;
612               out[t001] = in[t001] * dipoleScale;
613               out[t200] = in[t200] * quadrupoleScale;
614               out[t020] = in[t020] * quadrupoleScale;
615               out[t002] = in[t002] * quadrupoleScale;
616               out[t110] = in[t110] * quadrupoleScale;
617               out[t101] = in[t101] * quadrupoleScale;
618               out[t011] = in[t011] * quadrupoleScale;
619 
620               double[] esvMultipoleTautDot = new double[10];
621               esvMultipoleTautDot = esvSystem.getTitrationUtils()
622                   .getMultipoleTautomerDeriv(atom, titrationLambda,
623                       tautomerLambda, esvMultipoleTautDot);
624               in = esvMultipoleTautDot;
625               out = tautomerMultipole[iSymm][ii];
626               out[t000] = in[t000] * chargeScale;
627               out[t100] = in[t100] * dipoleScale;
628               out[t010] = in[t010] * dipoleScale;
629               out[t001] = in[t001] * dipoleScale;
630               out[t200] = in[t200] * quadrupoleScale;
631               out[t020] = in[t020] * quadrupoleScale;
632               out[t002] = in[t002] * quadrupoleScale;
633               out[t110] = in[t110] * quadrupoleScale;
634               out[t101] = in[t101] * quadrupoleScale;
635               out[t011] = in[t011] * quadrupoleScale;
636             }
637           }
638 
639           // Load the polarizability.
640           PolarizeType polarizeType = particleMeshEwald.getPolarizeType(ii);
641           if (polarizeType != null) {
642             polarizability[ii] = polarizeType.polarizability * polarizabilityScale;
643             if (esvTerm && esvSystem.isTitrating(ii) && (esvSystem.isTitratingHydrogen(ii)
644                 || esvSystem.isTitratingHeavy(ii))) {
645               titrationPolarizability[ii] = 0.0;
646               tautomerPolarizability[ii] = 0.0;
647               double titrationLambda = esvSystem.getTitrationLambda(ii);
648               double tautomerLambda = esvSystem.getTautomerLambda(ii);
649               titrationPolarizability[ii] = esvSystem.getTitrationUtils()
650                   .getPolarizabilityTitrationDeriv(atom, titrationLambda, tautomerLambda);
651               tautomerPolarizability[ii] = esvSystem.getTitrationUtils()
652                   .getPolarizabilityTautomerDeriv(atom, titrationLambda, tautomerLambda);
653             }
654             thole[ii] = polarizeType.thole;
655             ipdamp[ii] = polarizeType.pdamp;
656             if (!(ipdamp[ii] > 0.0)) {
657               ipdamp[ii] = Double.POSITIVE_INFINITY;
658             } else {
659               ipdamp[ii] = 1.0 / ipdamp[ii];
660             }
661           } else {
662             polarizability[ii] = 0.0;
663             thole[ii] = 0.0;
664             ipdamp[ii] = Double.POSITIVE_INFINITY;
665           }
666         }
667       }
668     }
669 
670     @Override
671     public IntegerSchedule schedule() {
672       return IntegerSchedule.fixed();
673     }
674   }
675 }