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