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;
39  
40  import edu.rit.pj.BarrierAction;
41  import edu.rit.pj.IntegerForLoop;
42  import edu.rit.pj.IntegerSchedule;
43  import edu.rit.pj.ParallelRegion;
44  import edu.rit.pj.ParallelTeam;
45  import edu.rit.pj.reduction.SharedDouble;
46  import edu.rit.pj.reduction.SharedInteger;
47  import ffx.crystal.Crystal;
48  import ffx.crystal.SymOp;
49  import ffx.numerics.atomic.AtomicDoubleArray.AtomicDoubleArrayImpl;
50  import ffx.numerics.atomic.AtomicDoubleArray3D;
51  import ffx.numerics.switching.MultiplicativeSwitch;
52  import ffx.potential.bonded.Atom;
53  import ffx.potential.bonded.Bond;
54  import ffx.potential.bonded.LambdaInterface;
55  import ffx.potential.extended.ExtendedSystem;
56  import ffx.potential.parameters.AtomType;
57  import ffx.potential.parameters.ForceField;
58  import ffx.potential.parameters.VDWType;
59  import ffx.utilities.FFXProperty;
60  
61  import java.util.Arrays;
62  import java.util.List;
63  import java.util.logging.Level;
64  import java.util.logging.Logger;
65  
66  import static ffx.potential.parameters.ForceField.toEnumForm;
67  import static ffx.utilities.PropertyGroup.NonBondedCutoff;
68  import static ffx.utilities.PropertyGroup.VanDerWaalsFunctionalForm;
69  import static java.lang.Double.isNaN;
70  import static java.lang.String.format;
71  import static java.util.Arrays.fill;
72  import static org.apache.commons.math3.util.FastMath.PI;
73  import static org.apache.commons.math3.util.FastMath.max;
74  import static org.apache.commons.math3.util.FastMath.min;
75  import static org.apache.commons.math3.util.FastMath.pow;
76  import static org.apache.commons.math3.util.FastMath.sqrt;
77  
78  /**
79   * The Van der Waals class computes Van der Waals interaction in parallel using a
80   * {@link ffx.potential.nonbonded.NeighborList} for any {@link ffx.crystal.Crystal}. The repulsive
81   * power (e.g. 12), attractive power (e.g. 6) and buffering (e.g. for the AMOEBA buffered-14-7) can
82   * all be specified such that both Lennard-Jones and AMOEBA are supported.
83   *
84   * @author Michael J. Schnieders
85   * @since 1.0
86   */
87  public class VanDerWaals implements MaskingInterface, LambdaInterface {
88  
89    private static final Logger logger = Logger.getLogger(VanDerWaals.class.getName());
90    private static final byte HARD = 0;
91    private static final byte SOFT = 1;
92    private static final byte XX = 0;
93    private static final byte YY = 1;
94    private static final byte ZZ = 2;
95    private final boolean doLongRangeCorrection;
96    /**
97     * The ParallelTeam used to evaluate van der Waals interactions.
98     */
99    private final ParallelTeam parallelTeam;
100   private final int threadCount;
101   private final IntegerSchedule pairwiseSchedule;
102   private final SharedInteger sharedInteractions;
103   private final SharedDouble sharedEnergy;
104   private final SharedDouble shareddEdL;
105   private final SharedDouble sharedd2EdL2;
106   private final VanDerWaalsRegion vanDerWaalsRegion;
107   private final VanDerWaalsForm vdwForm;
108   @FFXProperty(name = "vdwindex", clazz = String.class, propertyGroup = VanDerWaalsFunctionalForm, defaultValue = "class",
109       description = """ 
110           [CLASS / TYPE]
111           Specifies whether van der Waals parameters are provided for atom classes or atom types.
112           While most force fields are indexed by atom class, in OPLS models the vdW values are indexed by atom type.
113           The default in the absence of the vdwindex property is to index vdW parameters by atom class.
114           """)
115   private final String vdwIndex;
116   @FFXProperty(name = "vdw-taper", propertyGroup = NonBondedCutoff, defaultValue = "0.9", description = """
117       Allows modification of the cutoff windows for van der Waals potential energy interactions. "
118       The default value in the absence of the vdw-taper keyword is to begin the cutoff window
119       at 0.9 of the vdw cutoff distance.
120       """)
121   private final double vdwTaper;
122 
123   private final NonbondedCutoff nonbondedCutoff;
124   private final MultiplicativeSwitch multiplicativeSwitch;
125   /**
126    * Boundary conditions and crystal symmetry.
127    */
128   private Crystal crystal;
129   /**
130    * An array of all atoms in the system.
131    */
132   private Atom[] atoms;
133   /**
134    * Specification of the molecular index for each atom.
135    */
136   private int[] molecule;
137   /**
138    * Flag to indicate the atom is treated by a neural network.
139    */
140   private boolean[] neuralNetwork;
141   /**
142    * The Force Field that defines the Van der Waals interactions.
143    */
144   private ForceField forceField;
145   /**
146    * An array of whether each atom in the system should be used in the calculations.
147    */
148   private boolean[] use = null;
149   /**
150    * A local convenience variable equal to atoms.length.
151    */
152   private int nAtoms;
153   /**
154    * A local convenience variable equal to the number of crystal symmetry operators.
155    */
156   private int nSymm;
157   /**
158    * **************************************************************** Lambda variables.
159    */
160   private boolean gradient;
161   private boolean lambdaTerm;
162   private boolean esvTerm;
163   private boolean[] isSoft;
164   /**
165    * There are 2 softCore arrays of length nAtoms.
166    *
167    * <p>The first is used for atoms in the outer loop that are hard. This mask equals: false for
168    * inner loop hard atoms true for inner loop soft atoms
169    *
170    * <p>The second is used for atoms in the outer loop that are soft. This mask equals: true for
171    * inner loop hard atoms false for inner loop soft atoms
172    */
173   private boolean[][] softCore;
174   /**
175    * Turn on inter-molecular softcore interactions using molecular index.
176    */
177   private boolean intermolecularSoftcore = false;
178 
179   // *************************************************************************
180   // Coordinate arrays.
181   /**
182    * Turn on intra-molecular softcore interactions using molecular index.
183    */
184   private boolean intramolecularSoftcore = false;
185   /**
186    * Current value of the lambda state variable.
187    */
188   private double lambda = 1.0;
189   /**
190    * The vdW energy is fully turned on when this value is reached.
191    */
192   private double vdwLambdaEnd = 1.0;
193   /**
194    * Exponent on lambda (beta).
195    */
196   private double vdwLambdaExponent = 3.0;
197   /**
198    * Offset in Angstroms (alpha).
199    */
200   private double vdwLambdaAlpha = 0.25;
201 
202   /**
203    * Polymorphic inner class to set sc1,sc2,dsc1,etc only when necessary. [nThreads]
204    */
205   private LambdaFactors[] lambdaFactors = null;
206   /**
207    * alpha * (1 - lambda)^2
208    */
209   private double sc1 = 0.0;
210   /**
211    * lambda^lambdaExponent
212    */
213   private double sc2 = 1.0;
214   private double dsc1dL = 0.0;
215   private double dsc2dL = 0.0;
216   private double d2sc1dL2 = 0.0;
217   private double d2sc2dL2 = 0.0;
218   /**
219    * Generalized extended system variables.
220    */
221   private ExtendedSystem esvSystem;
222 
223   /**
224    * A local copy of atomic coordinates, including reductions on the hydrogen atoms.
225    */
226   private double[] coordinates;
227   /**
228    * Reduced coordinates of size: [nSymm][nAtoms * 3]
229    */
230   private double[][] reduced;
231 
232   private double[] reducedXYZ;
233   /**
234    * Neighbor lists for each atom. Size: [nSymm][nAtoms][nNeighbors]
235    */
236   private int[][][] neighborLists;
237   /**
238    * A local reference to the atom class of each atom in the system.
239    */
240   private int[] atomClass;
241   /**
242    * Hydrogen atom vdW sites are located toward their heavy atom relative to their nucleus. This is a
243    * look-up that gives the heavy atom index for each hydrogen.
244    */
245   private int[] reductionIndex;
246 
247   private int[][] mask12;
248   private int[][] mask13;
249   private int[][] mask14;
250   /**
251    * Each hydrogen vdW site is located a fraction of the way from the heavy atom nucleus to the
252    * hydrogen nucleus (~0.9).
253    */
254   private double[] reductionValue;
255 
256   private boolean reducedHydrogen;
257   private double longRangeCorrection;
258   private AtomicDoubleArrayImpl atomicDoubleArrayImpl;
259   /**
260    * Cartesian coordinate gradient.
261    */
262   private AtomicDoubleArray3D grad;
263   /**
264    * Lambda derivative of the Cartesian coordinate gradient.
265    */
266   private AtomicDoubleArray3D lambdaGrad;
267   /**
268    * The neighbor-list includes 1-2 and 1-3 interactions, which are masked out in the Van der Waals
269    * energy code. The AMOEBA force field includes 1-4 interactions fully.
270    */
271   private NeighborList neighborList;
272   /**
273    * Force building of the neighbor list.
274    */
275   private boolean forceNeighborListRebuild = true;
276 
277   public VanDerWaals() {
278     // Empty constructor for use with VanDerWaalsTornado
279     doLongRangeCorrection = false;
280     parallelTeam = null;
281     threadCount = 0;
282     pairwiseSchedule = null;
283     sharedInteractions = null;
284     sharedEnergy = null;
285     shareddEdL = null;
286     sharedd2EdL2 = null;
287     vanDerWaalsRegion = null;
288     vdwForm = null;
289     nonbondedCutoff = null;
290     multiplicativeSwitch = null;
291     vdwIndex = null;
292     vdwTaper = VanDerWaalsForm.DEFAULT_VDW_TAPER;
293   }
294 
295   /**
296    * The VanDerWaals class constructor.
297    *
298    * @param atoms              the Atom array to do Van Der Waals calculations on.
299    * @param molecule           the molecule number for each atom.
300    * @param neuralNetwork      an array of flags to indicate the atom is treated by a neural network.
301    * @param crystal            The boundary conditions.
302    * @param forceField         the ForceField parameters to apply.
303    * @param parallelTeam       The parallel environment.
304    * @param vdwCutoff          a double.
305    * @param neighborListCutoff a double.
306    * @since 1.0
307    */
308   public VanDerWaals(
309       Atom[] atoms,
310       int[] molecule,
311       boolean[] neuralNetwork,
312       Crystal crystal,
313       ForceField forceField,
314       ParallelTeam parallelTeam,
315       double vdwCutoff,
316       double neighborListCutoff) {
317     this.atoms = atoms;
318     this.molecule = molecule;
319     this.neuralNetwork = neuralNetwork;
320     this.crystal = crystal;
321     this.parallelTeam = parallelTeam;
322     this.forceField = forceField;
323     nAtoms = atoms.length;
324     nSymm = crystal.spaceGroup.getNumberOfSymOps();
325     vdwForm = new VanDerWaalsForm(forceField);
326 
327     vdwIndex = forceField.getString("VDWINDEX", "Class");
328     reducedHydrogen = forceField.getBoolean("REDUCE_HYDROGENS", true);
329 
330     // Lambda parameters.
331     lambdaTerm = forceField.getBoolean("VDW_LAMBDATERM",
332         forceField.getBoolean("LAMBDATERM", false));
333     if (lambdaTerm) {
334       shareddEdL = new SharedDouble();
335       sharedd2EdL2 = new SharedDouble();
336       vdwLambdaAlpha = forceField.getDouble("VDW_LAMBDA_ALPHA", 0.25);
337       vdwLambdaExponent = forceField.getDouble("VDW_LAMBDA_EXPONENT", 3.0);
338       vdwLambdaEnd = forceField.getDouble("VDW_LAMBDA_END", 1.0);
339       if (vdwLambdaAlpha < 0.0) {
340         logger.warning(format(
341             " Invalid value %8.3g for vdw-lambda-alpha; must be greater than or equal to 0. Resetting to 0.25.",
342             vdwLambdaAlpha));
343         vdwLambdaAlpha = 0.25;
344       }
345       if (vdwLambdaExponent < 1.0) {
346         logger.warning(format(
347             " Invalid value %8.3g for vdw-lambda-exponent; must be greater than or equal to 1. Resetting to 3.",
348             vdwLambdaExponent));
349         vdwLambdaExponent = 3.0;
350       }
351 
352       vdwLambdaEnd = forceField.getDouble("VDW_LAMBDA_END", 1.0);
353       if (vdwLambdaEnd < 0.0 || vdwLambdaEnd > 1.0) {
354         logger.warning(format(
355             " Invalid value %8.3g for vdw-lambda-end; must be between 0 and 1. Resetting to 1.", vdwLambdaEnd));
356         vdwLambdaEnd = 1.0;
357       }
358       intermolecularSoftcore = forceField.getBoolean("INTERMOLECULAR_SOFTCORE", false);
359       intramolecularSoftcore = forceField.getBoolean("INTRAMOLECULAR_SOFTCORE", false);
360     } else {
361       shareddEdL = null;
362       sharedd2EdL2 = null;
363     }
364 
365     // Parallel constructs.
366     threadCount = parallelTeam.getThreadCount();
367     sharedInteractions = new SharedInteger();
368     sharedEnergy = new SharedDouble();
369     doLongRangeCorrection = forceField.getBoolean("VDW_CORRECTION", false);
370     vanDerWaalsRegion = new VanDerWaalsRegion();
371 
372     // Define how force arrays will be accumulated.
373     atomicDoubleArrayImpl = AtomicDoubleArrayImpl.MULTI;
374     String value = forceField.getString("ARRAY_REDUCTION", "MULTI");
375     try {
376       atomicDoubleArrayImpl = AtomicDoubleArrayImpl.valueOf(toEnumForm(value));
377     } catch (Exception e) {
378       logger.info(format(
379           " Unrecognized ARRAY-REDUCTION %s; defaulting to %s", value, atomicDoubleArrayImpl));
380     }
381 
382     // Allocate coordinate arrays and set up reduction indices and values.
383     initAtomArrays();
384 
385     /*
386      Define the multiplicative switch, which sets vdW energy to zero
387      at the cutoff distance using a window that begin at 90% of the
388      cutoff distance.
389     */
390     double taper = forceField.getDouble("VDW_TAPER", VanDerWaalsForm.DEFAULT_VDW_TAPER);
391     vdwTaper = taper * vdwCutoff;
392     double buff = 2.0;
393     nonbondedCutoff = new NonbondedCutoff(vdwCutoff, vdwTaper, buff);
394     multiplicativeSwitch = new MultiplicativeSwitch(vdwTaper, vdwCutoff);
395     neighborList = new NeighborList(crystal, atoms, neighborListCutoff, buff, parallelTeam);
396     pairwiseSchedule = neighborList.getPairwiseSchedule();
397     neighborLists = new int[nSymm][][];
398 
399     // Reduce and expand the coordinates of the asymmetric unit. Then build the first neighbor-list.
400     buildNeighborList(atoms);
401 
402     // Then, optionally, prevent that neighbor list from ever updating.
403     neighborList.setDisableUpdates(forceField.getBoolean("DISABLE_NEIGHBOR_UPDATES", false));
404 
405     logger.info(toString());
406   }
407 
408   /**
409    * {@inheritDoc}
410    *
411    * <p>Apply masking rules for 1-2, 1-3 and 1-4 interactions.
412    */
413   @Override
414   public void applyMask(final int i, final boolean[] is14, final double[]... masks) {
415     double[] mask = masks[0];
416     var m12 = mask12[i];
417     for (int value : m12) {
418       mask[value] = vdwForm.scale12;
419     }
420     var m13 = mask13[i];
421     for (int value : m13) {
422       mask[value] = vdwForm.scale13;
423     }
424     var m14 = mask14[i];
425     for (int value : m14) {
426       mask[value] = vdwForm.scale14;
427       is14[value] = true;
428     }
429   }
430 
431   /**
432    * attachExtendedSystem.
433    *
434    * @param system a {@link ffx.potential.extended.ExtendedSystem} object.
435    */
436   public void attachExtendedSystem(ExtendedSystem system) {
437     if (system == null) {
438       logger.severe("Tried to attach null extended system.");
439     }
440     esvTerm = true;
441     esvSystem = system;
442 
443     // Launch shared lambda/esvLambda initializers if missed (ie. !lambdaTerm) in constructor.
444     vdwLambdaAlpha = forceField.getDouble("VDW_LAMBDA_ALPHA", 0.05);
445     vdwLambdaExponent = forceField.getDouble("VDW_LAMBDA_EXPONENT", 1.0);
446     if (vdwLambdaExponent != 1.0) {
447       logger.warning(
448           format(
449               "ESVs are compatible only with a vdwLambdaExponent of unity!"
450                   + " (found %g, resetting to 1.0)",
451               vdwLambdaExponent));
452       vdwLambdaExponent = 1.0;
453     }
454     if (vdwLambdaAlpha < 0.0) {
455       vdwLambdaAlpha = 0.05;
456     }
457 
458     intermolecularSoftcore = forceField.getBoolean("INTERMOLECULAR_SOFTCORE", false);
459     intramolecularSoftcore = forceField.getBoolean("INTRAMOLECULAR_SOFTCORE", false);
460 
461     Atom[] atomsExt = esvSystem.getExtendedAtoms();
462     int[] moleculeExt = esvSystem.getExtendedMolecule();
463 
464     // The combination of an extended system and neural networks are not supported.
465     boolean[] nn = new boolean[atomsExt.length];
466     Arrays.fill(nn, false);
467     for (Atom a : atomsExt) {
468       if (a.isNeuralNetwork()) {
469         logger.severe(
470             "The combination of an extended system and neural networks are not supported.");
471       }
472     }
473 
474     setAtoms(atomsExt, moleculeExt, nn);
475   }
476 
477   /**
478    * Get the ExtendedSystem instance.
479    *
480    * @return The ExtendedSystem is returned.
481    */
482   public ExtendedSystem getExtendedSystem() {
483     return esvSystem;
484   }
485 
486   /**
487    * destroy.
488    *
489    * @throws java.lang.Exception if any.
490    */
491   public void destroy() throws Exception {
492     if (neighborList != null) {
493       neighborList.destroy();
494     }
495   }
496 
497   /**
498    * The energy routine may be called repeatedly.
499    *
500    * @param gradient If true, gradients with respect to atomic coordinates are computed.
501    * @param print    If true, there is verbose printing.
502    * @return The energy.
503    * @since 1.0
504    */
505   public double energy(boolean gradient, boolean print) {
506     this.gradient = gradient;
507 
508     try {
509       parallelTeam.execute(vanDerWaalsRegion);
510     } catch (Exception e) {
511       String message = " Fatal exception expanding coordinates.\n";
512       logger.log(Level.SEVERE, message, e);
513     }
514     return sharedEnergy.get();
515   }
516 
517   /**
518    * If true, there are alchemical atoms impacted by the lambda state variable.
519    * @return True if there are alchemical atoms.
520    */
521   public boolean getLambdaTerm() {
522     return lambdaTerm;
523   }
524 
525   /**
526    * If true, intra-molecular interactions are annihilated by the lambda state variable.
527    */
528   public boolean getIntramolecularSoftcore() {
529     return intramolecularSoftcore;
530   }
531 
532   /**
533    * getAlpha.
534    *
535    * @return a double.
536    */
537   public double getAlpha() {
538     return vdwLambdaAlpha;
539   }
540 
541   /**
542    * getBeta.
543    *
544    * @return a double.
545    */
546   public double getBeta() {
547     return vdwLambdaExponent;
548   }
549 
550   /**
551    * Get the buffer size.
552    *
553    * @return The buffer.
554    * @since 1.0
555    */
556   public double getBuffer() {
557     return nonbondedCutoff.buff;
558   }
559 
560   /**
561    * Return use of the long-range vdW correction.
562    *
563    * @return True if it is on.
564    */
565   public boolean getDoLongRangeCorrection() {
566     return doLongRangeCorrection;
567   }
568 
569   /**
570    * Get the total Van der Waals potential energy.
571    *
572    * @return The energy.
573    * @since 1.0
574    */
575   public double getEnergy() {
576     return sharedEnergy.get();
577   }
578 
579   /**
580    * Get the number of interacting pairs.
581    *
582    * @return The interaction count.
583    * @since 1.0
584    */
585   public int getInteractions() {
586     return sharedInteractions.get();
587   }
588 
589   /**
590    * {@inheritDoc}
591    */
592   @Override
593   public double getLambda() {
594     return lambda;
595   }
596 
597   /**
598    * {@inheritDoc}
599    */
600   @Override
601   public void setLambda(double lambda) {
602     assert (lambda >= 0.0 && lambda <= 1.0);
603     if (!lambdaTerm) {
604       return;
605     }
606     // Scale lambda if vdwLambdaEnd is less than 1.0.
607     if (lambda > vdwLambdaEnd) {
608       lambda = 1.0;
609     } else {
610       lambda = lambda / vdwLambdaEnd;
611     }
612     this.lambda = lambda;
613 
614     // Softcore constant in vdW denominator sc1 = alpha * (1-L)^2
615     sc1 = vdwLambdaAlpha * (1.0 - lambda) * (1.0 - lambda);
616     // d(sc1)/dL = -2 * alpha * (1-L)
617     dsc1dL = -2.0 * vdwLambdaAlpha * (1.0 - lambda);
618     // d^2(sc1)/dL^2 = 2 * alpha
619     d2sc1dL2 = 2.0 * vdwLambdaAlpha;
620     // Multiplicative vdW function: sc2 = L^beta
621     sc2 = pow(lambda, vdwLambdaExponent);
622     // d(sc2)/dL = beta * L^(beta-1)
623     dsc2dL = vdwLambdaExponent * pow(lambda, vdwLambdaExponent - 1.0);
624     if (vdwLambdaExponent >= 2.0) {
625       // d^2(sc2)/dL^2 = beta * (beta-1) * L^(beta-2)
626       d2sc2dL2 = vdwLambdaExponent * (vdwLambdaExponent - 1.0) * pow(lambda, vdwLambdaExponent - 2.0);
627     } else {
628       d2sc2dL2 = 0.0;
629     }
630 
631     // If LambdaFactors are in OST mode, update them now.
632     if (!esvTerm) {
633       for (LambdaFactors lf : lambdaFactors) {
634         lf.setFactors();
635       }
636     }
637 
638     initSoftCore();
639 
640     // Redo the long range correction.
641     if (doLongRangeCorrection) {
642       longRangeCorrection = computeLongRangeCorrection();
643     } else {
644       longRangeCorrection = 0.0;
645     }
646   }
647 
648   /**
649    * Getter for the field <code>bondMask</code>.
650    *
651    * @return the mask12 array.
652    */
653   public int[][] getMask12() {
654     return mask12;
655   }
656 
657   /**
658    * Getter for the field <code>angleMask</code>.
659    *
660    * @return the mask13 array.
661    */
662   public int[][] getMask13() {
663     return mask13;
664   }
665 
666   /**
667    * Getter for the field <code>torsionMask</code>.
668    *
669    * @return the mask14 array.
670    */
671   public int[][] getMask14() {
672     return mask14;
673   }
674 
675   /**
676    * Allow sharing the of the VanDerWaals NeighborList with ParticleMeshEwald.
677    *
678    * @return The NeighborList.
679    */
680   public NeighborList getNeighborList() {
681     return neighborList;
682   }
683 
684   /**
685    * Getter for the field <code>neighborLists</code>.
686    *
687    * @return an array of int.
688    */
689   public int[][][] getNeighborLists() {
690     return neighborLists;
691   }
692 
693   /**
694    * Get details of the non-bonded cutoff.
695    *
696    * @return a {@link ffx.potential.nonbonded.NonbondedCutoff} object.
697    */
698   public NonbondedCutoff getNonbondedCutoff() {
699     return nonbondedCutoff;
700   }
701 
702   /**
703    * Get the reduction index.
704    *
705    * @return the reductionIndex array.
706    */
707   public int[] getReductionIndex() {
708     return reductionIndex;
709   }
710 
711   /**
712    * getVDWForm.
713    *
714    * @return a {@link ffx.potential.nonbonded.VanDerWaalsForm} object.
715    */
716   public VanDerWaalsForm getVDWForm() {
717     return vdwForm;
718   }
719 
720   /**
721    * {@inheritDoc}
722    */
723   @Override
724   public double getd2EdL2() {
725     if (sharedd2EdL2 == null || !lambdaTerm) {
726       return 0.0;
727     }
728     return sharedd2EdL2.get();
729   }
730 
731   /**
732    * {@inheritDoc}
733    */
734   @Override
735   public double getdEdL() {
736     if (shareddEdL == null || !lambdaTerm) {
737       return 0.0;
738     }
739     return shareddEdL.get();
740   }
741 
742   /**
743    * {@inheritDoc}
744    */
745   @Override
746   public void getdEdXdL(double[] lambdaGradient) {
747     if (lambdaGrad == null || !lambdaTerm) {
748       return;
749     }
750     int index = 0;
751     for (int i = 0; i < nAtoms; i++) {
752       if (atoms[i].isActive()) {
753         lambdaGradient[index++] += lambdaGrad.getX(i);
754         lambdaGradient[index++] += lambdaGrad.getY(i);
755         lambdaGradient[index++] += lambdaGrad.getZ(i);
756       }
757     }
758   }
759 
760   /**
761    * {@inheritDoc}
762    *
763    * <p>Remove the masking rules for 1-2, 1-3 and 1-4 interactions.
764    */
765   @Override
766   public void removeMask(final int i, final boolean[] is14, final double[]... masks) {
767     double[] mask = masks[0];
768     var m12 = mask12[i];
769     for (int value : m12) {
770       mask[value] = 1.0;
771     }
772     var m13 = mask13[i];
773     for (int value : m13) {
774       mask[value] = 1.0;
775     }
776     var m14 = mask14[i];
777     for (int value : m14) {
778       mask[value] = 1.0;
779       is14[value] = false;
780     }
781   }
782 
783   /**
784    * Set the atoms and molecule arrays, and rebuild the neighbor list.
785    *
786    * @param atoms         an array of Atom objects representing the atoms in the system.
787    * @param molecule      an array of integers indicating the molecule index for each atom.
788    * @param neuralNetwork an array of flags to indicate if the atom is treated by a neural
789    *                      network.
790    */
791   public void setAtoms(Atom[] atoms, int[] molecule, boolean[] neuralNetwork) {
792     this.atoms = atoms;
793     this.nAtoms = atoms.length;
794     this.molecule = molecule;
795     this.neuralNetwork = neuralNetwork;
796 
797     if (nAtoms != molecule.length) {
798       logger.warning("Atom and molecule arrays are of different lengths.");
799       throw new IllegalArgumentException();
800     }
801 
802     initAtomArrays();
803     buildNeighborList(atoms);
804   }
805 
806   /**
807    * If the crystal being passed in is not equal to the current crystal, then some Van der Waals data
808    * structures may need to updated. If <code>nSymm</code> has changed, update arrays dimensioned by
809    * nSymm. Finally, rebuild the neighbor-lists.
810    *
811    * @param crystal The new crystal instance defining the symmetry and boundary conditions.
812    */
813   public void setCrystal(Crystal crystal) {
814     if (this.crystal.equals(crystal)) {
815       return;
816     }
817     this.crystal = crystal;
818     int newNSymm = crystal.spaceGroup.getNumberOfSymOps();
819     if (nSymm != newNSymm) {
820       nSymm = newNSymm;
821       // Allocate memory if necessary.
822       if (reduced == null || reduced.length < nSymm) {
823         reduced = new double[nSymm][nAtoms * 3];
824         reducedXYZ = reduced[0];
825         neighborLists = new int[nSymm][][];
826       }
827     }
828     neighborList.setCrystal(crystal);
829     forceNeighborListRebuild = true;
830     try {
831       parallelTeam.execute(vanDerWaalsRegion);
832     } catch (Exception e) {
833       String message = " Fatal exception expanding coordinates.\n";
834       logger.log(Level.SEVERE, message, e);
835     }
836   }
837 
838   /**
839    * {@inheritDoc}
840    */
841   @Override
842   public String toString() {
843     StringBuffer sb = new StringBuffer("\n  Van der Waals\n");
844     if (multiplicativeSwitch.getSwitchStart() != Double.POSITIVE_INFINITY) {
845       sb.append(format("   Switch Start:                         %6.3f (A)\n",
846           multiplicativeSwitch.getSwitchStart()));
847       sb.append(format("   Cut-Off:                              %6.3f (A)",
848           multiplicativeSwitch.getSwitchEnd()));
849     } else {
850       sb.append("   Cut-Off:                                NONE");
851     }
852 
853     sb.append(format("\n   Long-Range Correction:                %6B", doLongRangeCorrection));
854     if (!reducedHydrogen) {
855       sb.append(format("\n   Reduce Hydrogen:                      %6B", reducedHydrogen));
856     }
857     if (lambdaTerm) {
858       sb.append("\n   Alchemical Parameters\n");
859       sb.append(format("    Softcore Alpha:                       %5.3f\n", vdwLambdaAlpha));
860       sb.append(format("    Lambda Exponent:                      %5.3f\n", vdwLambdaExponent));
861       sb.append(format("    Lambda End:                           %5.3f", vdwLambdaEnd));
862     }
863     return sb.toString();
864   }
865 
866   /**
867    * Allocate coordinate arrays and set up reduction indices and values.
868    */
869   private void initAtomArrays() {
870     if (esvTerm) {
871       atoms = esvSystem.getExtendedAtoms();
872       nAtoms = atoms.length;
873     }
874     if (atomClass == null || nAtoms > atomClass.length || lambdaTerm || esvTerm) {
875       atomClass = new int[nAtoms];
876       coordinates = new double[nAtoms * 3];
877       reduced = new double[nSymm][nAtoms * 3];
878       reducedXYZ = reduced[0];
879       reductionIndex = new int[nAtoms];
880       reductionValue = new double[nAtoms];
881       mask12 = new int[nAtoms][];
882       mask13 = new int[nAtoms][];
883       mask14 = new int[nAtoms][];
884       use = new boolean[nAtoms];
885       isSoft = new boolean[nAtoms];
886       softCore = new boolean[2][nAtoms];
887       grad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, threadCount);
888       if (lambdaTerm) {
889         lambdaGrad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, threadCount);
890       } else {
891         lambdaGrad = null;
892       }
893     }
894 
895     // Initialize all atoms to be used in the energy.
896     fill(use, true);
897     fill(isSoft, false);
898     fill(softCore[HARD], false);
899     fill(softCore[SOFT], false);
900 
901     lambdaFactors = new LambdaFactors[threadCount];
902     for (int i = 0; i < threadCount; i++) {
903       if (lambdaTerm) {
904         lambdaFactors[i] = new LambdaFactorsOST();
905       } else {
906         lambdaFactors[i] = new LambdaFactors();
907       }
908     }
909 
910     for (int i = 0; i < nAtoms; i++) {
911       Atom ai = atoms[i];
912       assert (i == ai.getXyzIndex() - 1);
913       double[] xyz = ai.getXYZ(null);
914       int i3 = i * 3;
915       coordinates[i3 + XX] = xyz[XX];
916       coordinates[i3 + YY] = xyz[YY];
917       coordinates[i3 + ZZ] = xyz[ZZ];
918 
919       VDWType vdwType = ai.getVDWType();
920       if (vdwType == null) {
921         // Find the vdW parameters from the AtomType.
922         AtomType atomType = ai.getAtomType();
923         if (atomType == null) {
924           logger.severe(ai.toString());
925           return;
926         }
927 
928         if (vdwIndex.equalsIgnoreCase("Type")) {
929           atomClass[i] = atomType.type;
930         } else {
931           atomClass[i] = atomType.atomClass;
932         }
933         vdwType = forceField.getVDWType(Integer.toString(atomClass[i]));
934 
935         if (vdwType == null) {
936           logger.info(" No VdW type for atom class " + atomClass[i]);
937           logger.severe(" No VdW type for atom " + ai);
938           return;
939         }
940         ai.setVDWType(vdwType);
941       }
942 
943       atomClass[i] = vdwType.atomClass;
944 
945       List<Bond> bonds = ai.getBonds();
946       int numBonds = bonds.size();
947       if (reducedHydrogen && vdwType.reductionFactor > 0.0 && numBonds == 1) {
948         Bond bond = bonds.get(0);
949         Atom heavyAtom = bond.get1_2(ai);
950         // Atom indexes start at 1
951         reductionIndex[i] = heavyAtom.getIndex() - 1;
952         reductionValue[i] = vdwType.reductionFactor;
953       } else {
954         reductionIndex[i] = i;
955         reductionValue[i] = 0.0;
956       }
957 
958       // Collect 1-2 interactions.
959       List<Atom> n12 = ai.get12List();
960       mask12[i] = new int[n12.size()];
961       int j = 0;
962       for (Atom a12 : n12) {
963         mask12[i][j++] = a12.getIndex() - 1;
964       }
965 
966       // Collect 1-3 interactions.
967       List<Atom> n13 = ai.get13List();
968       mask13[i] = new int[n13.size()];
969       j = 0;
970       for (Atom a13 : n13) {
971         mask13[i][j++] = a13.getIndex() - 1;
972       }
973 
974       // Collect 1-4 interactions.
975       List<Atom> n14 = ai.get14List();
976       mask14[i] = new int[n14.size()];
977       j = 0;
978       for (Atom a14 : n14) {
979         mask14[i][j++] = a14.getIndex() - 1;
980       }
981     }
982   }
983 
984   /**
985    * buildNeighborList.
986    *
987    * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
988    */
989   private void buildNeighborList(Atom[] atoms) {
990     neighborList.setAtoms(atoms);
991     forceNeighborListRebuild = true;
992     try {
993       parallelTeam.execute(vanDerWaalsRegion);
994     } catch (Exception e) {
995       String message = " Fatal exception expanding coordinates.\n";
996       logger.log(Level.SEVERE, message, e);
997     }
998     forceNeighborListRebuild = false;
999   }
1000 
1001   /**
1002    * Computes the long range van der Waals correction to the energy via numerical integration
1003    *
1004    * @return Long range correction (kcal/mol)
1005    * @see "M. P. Allen and D. J. Tildesley, "Computer Simulation of Liquids, 2nd Ed.", Oxford
1006    * University Press, 2017, Section 2.8"
1007    */
1008   private double computeLongRangeCorrection() {
1009     // Need to treat esvLambda chain terms below before you can do this.
1010     if (esvTerm) {
1011       throw new UnsupportedOperationException();
1012     }
1013 
1014     // Only applicable under periodic boundary conditions.
1015     if (crystal.aperiodic()) {
1016       return 0.0;
1017     }
1018 
1019     /*
1020      Integrate to maxR = 100 Angstroms or ~33 sigma.
1021      nDelta integration steps: (100.0 - cutoff) * 2.0
1022      rDelta integration step size: (100.0 - cutoff) / nDelta
1023     */
1024     int stepsPerAngstrom = 2;
1025     double range = 100.0;
1026     int nDelta = (int) ((double) stepsPerAngstrom * (range - nonbondedCutoff.cut));
1027     double rDelta = (range - nonbondedCutoff.cut) / (double) nDelta;
1028     double offset = nonbondedCutoff.cut - 0.5 * rDelta;
1029     double oneMinLambda = 1.0 - lambda;
1030 
1031     // Count the number of classes and their frequencies
1032     int maxClass = vdwForm.maxClass;
1033     int[] radCount = new int[maxClass + 1];
1034     int[] softRadCount = new int[maxClass + 1];
1035     fill(radCount, 0);
1036     fill(softRadCount, 0);
1037     for (int i = 0; i < nAtoms; i++) {
1038       radCount[atomClass[i]]++;
1039       if (isSoft[i]) {
1040         softRadCount[atomClass[i]]++;
1041       }
1042     }
1043 
1044     double total = 0.0;
1045     // Loop over vdW classes.
1046     for (int i = 1; i < maxClass + 1; i++) {
1047       for (int j = i; j < maxClass + 1; j++) {
1048         if (radCount[i] == 0 || radCount[j] == 0) {
1049           continue;
1050         }
1051         double irv = vdwForm.getCombinedInverseRmin(i, j);
1052         double ev = vdwForm.getCombinedEps(i, j);
1053         if (isNaN(irv) || irv == 0 || isNaN(ev)) {
1054           continue;
1055         }
1056         double sume = 0.0;
1057         for (int k = 1; k <= nDelta; k++) {
1058           double r = offset + (double) k * rDelta;
1059           double r2 = r * r;
1060           final double rho = r * irv;
1061           final double rho3 = rho * rho * rho;
1062           final double rhod = rho + vdwForm.delta;
1063           final double rhod3 = rhod * rhod * rhod;
1064           double t1, t2;
1065           switch (vdwForm.vdwType) {
1066             case LENNARD_JONES -> {
1067               final double rho6 = rho3 * rho3;
1068               final double rhod6 = rhod3 * rhod3;
1069               t1 = vdwForm.t1n / rhod6;
1070               t2 = vdwForm.gamma1 / (rho6 + vdwForm.gamma) - 2.0;
1071             }
1072             case BUFFERED_14_7 -> {
1073               final double rho7 = rho3 * rho3 * rho;
1074               final double rhod7 = rhod3 * rhod3 * rhod;
1075               t1 = vdwForm.t1n / rhod7;
1076               t2 = vdwForm.gamma1 / (rho7 + vdwForm.gamma) - 2.0;
1077             }
1078             default -> {
1079               // Arbitrary power of "N" for the attractive dispersion.
1080               final double rhoN = pow(rho, vdwForm.dispersivePower);
1081               final double rhodN = pow(rhod, vdwForm.dispersivePower);
1082               t1 = vdwForm.t1n / rhodN;
1083               t2 = vdwForm.gamma1 / (rhoN + vdwForm.gamma) - 2.0;
1084             }
1085           }
1086           final double eij = ev * t1 * t2;
1087           /*
1088            Apply one minus the multiplicative switch if the
1089            interaction distance is less than the end of the
1090            switching window.
1091           */
1092           double taper = 1.0;
1093           if (r2 < nonbondedCutoff.off2) {
1094             double r3 = r * r2;
1095             double r4 = r2 * r2;
1096             double r5 = r2 * r3;
1097             taper = multiplicativeSwitch.taper(r, r2, r3, r4, r5);
1098             taper = 1.0 - taper;
1099           }
1100 
1101           double jacobian = 4.0 * PI * r2;
1102           double e = jacobian * eij * taper;
1103           if (j != i) {
1104             sume += e;
1105           } else {
1106             sume += 0.5 * e;
1107           }
1108         }
1109         double trapezoid = rDelta * sume;
1110 
1111         // Normal correction
1112         total += radCount[i] * radCount[j] * trapezoid;
1113         // Correct for softCore vdW that are being turned off.
1114         if (lambda < 1.0) {
1115           total -= (softRadCount[i] * radCount[j] + (radCount[i] - softRadCount[i]) * softRadCount[j])
1116               * oneMinLambda * trapezoid;
1117         }
1118       }
1119     }
1120 
1121     // Divide by the volume of the asymmetric unit.
1122     Crystal unitCell = crystal.getUnitCell();
1123     double asymmetricUnitVolume = unitCell.volume / unitCell.getNumSymOps();
1124     total /= asymmetricUnitVolume;
1125     logger.fine(format(" Long-range vdW correction: %16.8f", total));
1126     return total;
1127   }
1128 
1129   /**
1130    * Log the Van der Waals interaction.
1131    *
1132    * @param i    Atom i.
1133    * @param k    Atom j.
1134    * @param minr The minimum energy vdW separation distance.
1135    * @param r    The distance rij.
1136    * @param eij  The interaction energy.
1137    * @since 1.0
1138    */
1139   private void log(int i, int k, double minr, double r, double eij) {
1140     Atom ai = atoms[i];
1141     Atom ak = atoms[k];
1142     logger.info(format("VDW %6d-%s %d %6d-%s %d %10.4f  %10.4f  %10.4f",
1143         ai.getIndex(), ai.getAtomType().name, ai.getVDWType().atomClass,
1144         ak.getIndex(), ak.getAtomType().name, ak.getVDWType().atomClass,
1145         minr, r, eij));
1146     logger.info(format("    %d %s\n    %d %s",
1147         i + 1, Arrays.toString(ai.getXYZ(null)),
1148         k + 1, Arrays.toString(ak.getXYZ(null))));
1149   }
1150 
1151   private void initSoftCore() {
1152 
1153     boolean rebuild = false;
1154 
1155     for (int i = 0; i < nAtoms; i++) {
1156       boolean soft = atoms[i].applyLambda();
1157       if (soft != isSoft[i]) {
1158         isSoft[i] = soft;
1159         rebuild = true;
1160       }
1161     }
1162 
1163     if (!rebuild) {
1164       return;
1165     }
1166 
1167     // Initialize the softcore atom masks.
1168     for (int i = 0; i < nAtoms; i++) {
1169       if (isSoft[i]) {
1170         // Outer loop atom hard, inner loop atom soft.
1171         softCore[HARD][i] = true;
1172         // Both soft: full intramolecular ligand interactions.
1173         softCore[SOFT][i] = false;
1174       } else {
1175         // Both hard: full interaction between atoms.
1176         softCore[HARD][i] = false;
1177         // Outer loop atom soft, inner loop atom hard.
1178         softCore[SOFT][i] = true;
1179       }
1180     }
1181   }
1182 
1183   /**
1184    * The trick: The setFactors(i,k) method is called every time through the inner VdW loop, avoiding
1185    * an "if (esv)" branch statement. A plain OST run will have an object of type LambdaFactorsOST
1186    * instead, which contains an empty version of setFactors(i,k). The OST version sets new factors
1187    * only on lambda updates, in setLambda().
1188    */
1189   public static class LambdaFactors {
1190 
1191     double sc1 = 0.0;
1192     double dsc1dL = 0.0;
1193     double d2sc1dL2 = 0.0;
1194     double sc2 = 1.0;
1195     double dsc2dL = 0.0;
1196     double d2sc2dL2 = 0.0;
1197 
1198     /**
1199      * Overriden by the OST version which updates only during setLambda().
1200      */
1201     public void setFactors() {
1202     }
1203   }
1204 
1205   public class LambdaFactorsOST extends LambdaFactors {
1206 
1207     @Override
1208     public void setFactors() {
1209       sc1 = VanDerWaals.this.sc1;
1210       dsc1dL = VanDerWaals.this.dsc1dL;
1211       d2sc1dL2 = VanDerWaals.this.d2sc1dL2;
1212       sc2 = VanDerWaals.this.sc2;
1213       dsc2dL = VanDerWaals.this.dsc2dL;
1214       d2sc2dL2 = VanDerWaals.this.d2sc2dL2;
1215     }
1216   }
1217 
1218   private class VanDerWaalsRegion extends ParallelRegion {
1219 
1220     private final InitializationLoop[] initializationLoop;
1221     private final ExpandLoop[] expandLoop;
1222     private final VanDerWaalsLoop[] vanDerWaalsLoop;
1223     private final ReductionLoop[] reductionLoop;
1224     private final NeighborListBarrier neighborListAction;
1225 
1226     /**
1227      * Timing variables.
1228      */
1229     private long initLoopTotalTime, vdWLoopTotalTime, reductionLoopTimeTotal;
1230     private long neighborListTotalTime, vdwTimeTotal;
1231 
1232     VanDerWaalsRegion() {
1233       initializationLoop = new InitializationLoop[threadCount];
1234       expandLoop = new ExpandLoop[threadCount];
1235       vanDerWaalsLoop = new VanDerWaalsLoop[threadCount];
1236       reductionLoop = new ReductionLoop[threadCount];
1237       neighborListAction = new NeighborListBarrier();
1238     }
1239 
1240     @Override
1241     public void finish() {
1242       forceNeighborListRebuild = false;
1243       vdwTimeTotal += System.nanoTime();
1244       // Log timings.
1245       if (logger.isLoggable(Level.FINE)) {
1246         logger.fine(format("\n Neighbor-List:  %7.4f (sec)", neighborListTotalTime * 1e-9));
1247         logger.fine(format(" Van der Waals:  %7.4f (sec)", (vdwTimeTotal - neighborListTotalTime) * 1e-9));
1248         logger.fine(" Thread    Init    Energy  Reduce  Total     Counts");
1249         long initMax = 0;
1250         long vdwMax = 0;
1251         long reductionMax = 0;
1252         long initMin = Long.MAX_VALUE;
1253         long vdwMin = Long.MAX_VALUE;
1254         long reductionMin = Long.MAX_VALUE;
1255         int countMin = Integer.MAX_VALUE;
1256         int countMax = 0;
1257         for (int i = 0; i < threadCount; i++) {
1258           int count = vanDerWaalsLoop[i].getCount();
1259           long initTime = initializationLoop[i].time + expandLoop[i].time;
1260           long vdWTime = vanDerWaalsLoop[i].time;
1261           long reduceTime = reductionLoop[i].time;
1262           long totalTime = initTime + vdWTime + reduceTime;
1263           logger.fine(format("    %3d   %7.4f %7.4f %7.4f %7.4f %10d", i,
1264               initTime * 1e-9, vdWTime * 1e-9, reduceTime * 1e-9, totalTime * 1e-9, count));
1265           initMax = max(initTime, initMax);
1266           vdwMax = max(vdWTime, vdwMax);
1267           reductionMax = max(reduceTime, reductionMax);
1268           countMax = max(countMax, count);
1269           initMin = min(initTime, initMin);
1270           vdwMin = min(vdWTime, vdwMin);
1271           reductionMin = min(reduceTime, reductionMin);
1272           countMin = min(countMin, count);
1273         }
1274         long totalMin = initMin + vdwMin + reductionMin;
1275         long totalMax = initMax + vdwMax + reductionMax;
1276         long totalActual = initLoopTotalTime + vdWLoopTotalTime + reductionLoopTimeTotal;
1277         logger.fine(format(" Min      %7.4f %7.4f %7.4f %7.4f %10d",
1278             initMin * 1e-9, vdwMin * 1e-9, reductionMin * 1e-9, totalMin * 1e-9, countMin));
1279         logger.fine(format(" Max      %7.4f %7.4f %7.4f %7.4f %10d",
1280             initMax * 1e-9, vdwMax * 1e-9, reductionMax * 1e-9, totalMax * 1e-9, countMax));
1281         logger.fine(format(" Delta    %7.4f %7.4f %7.4f %7.4f %10d",
1282             (initMax - initMin) * 1e-9, (vdwMax - vdwMin) * 1e-9,
1283             (reductionMax - reductionMin) * 1e-9, (totalMax - totalMin) * 1e-9,
1284             (countMax - countMin)));
1285         logger.fine(format(" Actual   %7.4f %7.4f %7.4f %7.4f %10d\n",
1286             initLoopTotalTime * 1e-9, vdWLoopTotalTime * 1e-9, reductionLoopTimeTotal * 1e-9,
1287             totalActual * 1e-9, sharedInteractions.get()));
1288       }
1289     }
1290 
1291     @Override
1292     public void run() throws Exception {
1293       int threadIndex = getThreadIndex();
1294 
1295       // Locally initialize the Loops to help with NUMA?
1296       if (initializationLoop[threadIndex] == null) {
1297         initializationLoop[threadIndex] = new InitializationLoop();
1298         expandLoop[threadIndex] = new ExpandLoop();
1299         vanDerWaalsLoop[threadIndex] = new VanDerWaalsLoop();
1300         reductionLoop[threadIndex] = new ReductionLoop();
1301       }
1302 
1303       // Initialize and expand coordinates.
1304       try {
1305         if (threadIndex == 0) {
1306           initLoopTotalTime = -System.nanoTime();
1307         }
1308         execute(0, nAtoms - 1, initializationLoop[threadIndex]);
1309         execute(0, nAtoms - 1, expandLoop[threadIndex]);
1310         if (threadIndex == 0) {
1311           initLoopTotalTime += System.nanoTime();
1312         }
1313       } catch (RuntimeException ex) {
1314         logger.warning("Runtime exception expanding coordinates in thread: " + threadIndex);
1315         throw ex;
1316       } catch (Exception e) {
1317         String message = "Fatal exception expanding coordinates in thread: " + threadIndex + "\n";
1318         logger.log(Level.SEVERE, message, e);
1319       }
1320 
1321       // Build the neighbor-list (if necessary) using reduced coordinates.
1322       barrier(neighborListAction);
1323       if (forceNeighborListRebuild) {
1324         return;
1325       }
1326 
1327       // Compute Van der Waals energy and gradient.
1328       try {
1329         if (threadIndex == 0) {
1330           vdWLoopTotalTime = -System.nanoTime();
1331         }
1332         execute(0, nAtoms - 1, vanDerWaalsLoop[threadIndex]);
1333         if (threadIndex == 0) {
1334           vdWLoopTotalTime += System.nanoTime();
1335         }
1336       } catch (RuntimeException ex) {
1337         logger.warning("Runtime exception evaluating van der Waals energy in thread: " + threadIndex);
1338         throw ex;
1339       } catch (Exception e) {
1340         String message = "Fatal exception evaluating van der Waals energy in thread: " + threadIndex + "\n";
1341         logger.log(Level.SEVERE, message, e);
1342       }
1343 
1344       // Reduce derivatives.
1345       if (gradient || lambdaTerm) {
1346         try {
1347           if (threadIndex == 0) {
1348             reductionLoopTimeTotal = -System.nanoTime();
1349           }
1350           execute(0, nAtoms - 1, reductionLoop[threadIndex]);
1351           if (threadIndex == 0) {
1352             reductionLoopTimeTotal += System.nanoTime();
1353           }
1354         } catch (RuntimeException ex) {
1355           logger.warning(
1356               "Runtime exception reducing van der Waals gradient in thread: " + threadIndex);
1357           throw ex;
1358         } catch (Exception e) {
1359           String message =
1360               "Fatal exception reducing van der Waals gradient in thread: " + threadIndex + "\n";
1361           logger.log(Level.SEVERE, message, e);
1362         }
1363       }
1364     }
1365 
1366     /**
1367      * {@inheritDoc}
1368      *
1369      * @since 1.0
1370      */
1371     @Override
1372     public void start() {
1373       // Start timing.
1374       vdwTimeTotal = -System.nanoTime();
1375 
1376       // Initialize the shared variables.
1377       if (doLongRangeCorrection) {
1378         longRangeCorrection = computeLongRangeCorrection();
1379         sharedEnergy.set(longRangeCorrection);
1380       } else {
1381         sharedEnergy.set(0.0);
1382       }
1383       sharedInteractions.set(0);
1384       if (lambdaTerm) {
1385         shareddEdL.set(0.0);
1386         sharedd2EdL2.set(0.0);
1387       }
1388       if (esvTerm) {
1389         esvSystem.initEsvVdw();
1390         lambdaFactors = new LambdaFactors[threadCount];
1391         for (int i = 0; i < threadCount; i++) {
1392           lambdaFactors[i] = new LambdaFactors();
1393         }
1394       }
1395 
1396       grad.alloc(nAtoms);
1397       if (lambdaTerm) {
1398         lambdaGrad.alloc(nAtoms);
1399       }
1400     }
1401 
1402     /**
1403      * Update the local coordinate array and initialize reduction variables.
1404      */
1405     private class InitializationLoop extends IntegerForLoop {
1406 
1407       private int threadID;
1408       protected long time;
1409 
1410       @Override
1411       public void run(int lb, int ub) {
1412         for (int i = lb, i3 = 3 * lb; i <= ub; i++, i3 += 3) {
1413           Atom atom = atoms[i];
1414           coordinates[i3 + XX] = atom.getX();
1415           coordinates[i3 + YY] = atom.getY();
1416           coordinates[i3 + ZZ] = atom.getZ();
1417           use[i] = atom.getUse();
1418 
1419           // Load the vdw type.
1420           VDWType vdwType = atom.getVDWType();
1421           if (vdwType == null) {
1422             logger.severe(" No VdW type for atom " + atom);
1423             return;
1424           }
1425 
1426           // Update the atom class.
1427           atomClass[i] = vdwType.atomClass;
1428 
1429           // Set reduction values.
1430           List<Bond> bonds = atom.getBonds();
1431           int numBonds = bonds.size();
1432           if (reducedHydrogen && vdwType.reductionFactor > 0.0 && numBonds == 1) {
1433             Bond bond = bonds.get(0);
1434             Atom heavyAtom = bond.get1_2(atom);
1435             // Atom indexes start at 1
1436             reductionIndex[i] = heavyAtom.getIndex() - 1;
1437             reductionValue[i] = vdwType.reductionFactor;
1438           } else {
1439             reductionIndex[i] = i;
1440             reductionValue[i] = 0.0;
1441           }
1442 
1443           // Collect 1-2 interactions.
1444           List<Atom> n12 = atom.get12List();
1445           if (mask12[i] == null || mask12[i].length != n12.size()) {
1446             mask12[i] = new int[n12.size()];
1447           }
1448           int j = 0;
1449           for (Atom a12 : n12) {
1450             mask12[i][j++] = a12.getIndex() - 1;
1451           }
1452 
1453           // Collect 1-3 interactions.
1454           List<Atom> n13 = atom.get13List();
1455           if (mask13[i] == null || mask13[i].length != n13.size()) {
1456             mask13[i] = new int[n13.size()];
1457           }
1458           j = 0;
1459           for (Atom a13 : n13) {
1460             mask13[i][j++] = a13.getIndex() - 1;
1461           }
1462 
1463           // Collect 1-4 interactions.
1464           List<Atom> n14 = atom.get14List();
1465           if (mask14[i] == null || mask14[i].length != n14.size()) {
1466             mask14[i] = new int[n14.size()];
1467           }
1468           j = 0;
1469           for (Atom a14 : n14) {
1470             mask14[i][j++] = a14.getIndex() - 1;
1471           }
1472         }
1473         if (gradient) {
1474           grad.reset(threadID, lb, ub);
1475         }
1476         if (lambdaTerm) {
1477           lambdaGrad.reset(threadID, lb, ub);
1478         }
1479       }
1480 
1481       @Override
1482       public IntegerSchedule schedule() {
1483         return IntegerSchedule.fixed();
1484       }
1485 
1486       @Override
1487       public void finish() {
1488         time += System.nanoTime();
1489       }
1490 
1491       @Override
1492       public void start() {
1493         threadID = getThreadIndex();
1494         time = -System.nanoTime();
1495       }
1496     }
1497 
1498     private class ExpandLoop extends IntegerForLoop {
1499 
1500       private final double[] in = new double[3];
1501       private final double[] out = new double[3];
1502       private long time;
1503 
1504       @Override
1505       public void finish() {
1506         time += System.nanoTime();
1507       }
1508 
1509       @Override
1510       public void run(int lb, int ub) {
1511         /*
1512          Set up the local coordinate array for the asymmetric unit,
1513          applying reduction factors to the hydrogen atoms.
1514         */
1515         for (int i = lb; i <= ub; i++) {
1516           int i3 = i * 3;
1517           int iX = i3 + XX;
1518           int iY = i3 + YY;
1519           int iZ = i3 + ZZ;
1520           double x = coordinates[iX];
1521           double y = coordinates[iY];
1522           double z = coordinates[iZ];
1523           int redIndex = reductionIndex[i];
1524           if (redIndex >= 0) {
1525             int r3 = redIndex * 3;
1526             double rx = coordinates[r3++];
1527             double ry = coordinates[r3++];
1528             double rz = coordinates[r3];
1529             double a = reductionValue[i];
1530             reducedXYZ[iX] = a * (x - rx) + rx;
1531             reducedXYZ[iY] = a * (y - ry) + ry;
1532             reducedXYZ[iZ] = a * (z - rz) + rz;
1533             in[0] = reducedXYZ[iX];
1534             in[1] = reducedXYZ[iY];
1535             in[2] = reducedXYZ[iZ];
1536             atoms[i].setRedXYZ(in);
1537           } else {
1538             reducedXYZ[iX] = x;
1539             reducedXYZ[iY] = y;
1540             reducedXYZ[iZ] = z;
1541           }
1542         }
1543 
1544         List<SymOp> symOps = crystal.spaceGroup.symOps;
1545 
1546         if (symOps.size() != nSymm) {
1547           String message = format(" Programming Error: nSymm %d != symOps.size %d", nSymm, symOps.size());
1548           logger.log(Level.WARNING, message);
1549           logger.log(Level.WARNING, " Replicates\n{0}", crystal.toString());
1550           logger.log(Level.WARNING, " Unit Cell\n{0}", crystal.getUnitCell().toString());
1551         }
1552 
1553         double sp2 = crystal.getSpecialPositionCutoff();
1554         sp2 *= sp2;
1555         for (int iSymOp = 1; iSymOp < nSymm; iSymOp++) {
1556           SymOp symOp = symOps.get(iSymOp);
1557           double[] xyz = reduced[iSymOp];
1558           for (int i = lb; i <= ub; i++) {
1559             int i3 = i * 3;
1560             int iX = i3 + XX;
1561             int iY = i3 + YY;
1562             int iZ = i3 + ZZ;
1563             in[0] = reducedXYZ[iX];
1564             in[1] = reducedXYZ[iY];
1565             in[2] = reducedXYZ[iZ];
1566             crystal.applySymOp(in, out, symOp);
1567             xyz[iX] = out[0];
1568             xyz[iY] = out[1];
1569             xyz[iZ] = out[2];
1570 
1571             // Check if the atom is at a special position.
1572             double dx = in[0] - out[0];
1573             double dy = in[1] - out[1];
1574             double dz = in[2] - out[2];
1575             double r2 = dx * dx + dy * dy + dz * dz;
1576             if (r2 < sp2 && logger.isLoggable(Level.FINEST)) {
1577               logger.log(Level.FINEST, " Atom may be at a special position: {0}", atoms[i].toString());
1578             }
1579           }
1580         }
1581       }
1582 
1583       @Override
1584       public IntegerSchedule schedule() {
1585         return IntegerSchedule.fixed();
1586       }
1587 
1588       @Override
1589       public void start() {
1590         time = -System.nanoTime();
1591       }
1592 
1593     }
1594 
1595     /**
1596      * The Van der Waals loop class contains methods and thread local variables used to evaluate the
1597      * Van der Waals energy and gradients with respect to atomic coordinates.
1598      *
1599      * @author Michael J. Schnieders
1600      * @since 1.0
1601      */
1602     private class VanDerWaalsLoop extends IntegerForLoop {
1603       protected long time;
1604       private final double[] dx_local;
1605       private final double[][] transOp;
1606       private int count;
1607       private double energy;
1608       private int threadID;
1609       private double dEdL;
1610       private double d2EdL2;
1611       private double[] mask;
1612       private boolean[] vdw14;
1613       private LambdaFactors lambdaFactorsLocal;
1614 
1615       VanDerWaalsLoop() {
1616         super();
1617         dx_local = new double[3];
1618         transOp = new double[3][3];
1619       }
1620 
1621       @Override
1622       public void finish() {
1623         // Reduce the energy, interaction count and gradients into the shared variables.
1624         sharedEnergy.addAndGet(energy);
1625         sharedInteractions.addAndGet(count);
1626         if (lambdaTerm) {
1627           shareddEdL.addAndGet(dEdL);
1628           sharedd2EdL2.addAndGet(d2EdL2);
1629         }
1630         time += System.nanoTime();
1631       }
1632 
1633       public int getCount() {
1634         return count;
1635       }
1636 
1637       @Override
1638       public void run(int lb, int ub) {
1639         double e = 0.0;
1640         double[] xyzS = reduced[0];
1641         // neighborLists array: [nSymm][nAtoms][nNeighbors]
1642         int[][] list = neighborLists[0];
1643         double[] esvVdwPrefactori = new double[3];
1644         double[] esvVdwPrefactork = new double[3];
1645         for (int i = lb; i <= ub; i++) {
1646           if (!use[i]) {
1647             continue;
1648           }
1649           // Flag to indicate if atom i is effected by an extended system variable.
1650           final boolean esvi = esvTerm && esvSystem.isTitratingHydrogen(i);
1651           if (esvTerm) {
1652             esvSystem.getVdwPrefactor(i, esvVdwPrefactori);
1653           }
1654           int i3 = i * 3;
1655           // Atomic coordinates of atom i, including application of reduction factors.
1656           final double xi = reducedXYZ[i3++];
1657           final double yi = reducedXYZ[i3++];
1658           final double zi = reducedXYZ[i3];
1659           // If this hydrogen atomic position is reduced, this is the index of its heavy atom.
1660           final int redi = reductionIndex[i];
1661           // If this hydrogen atomic position is reduced, this is the reduction factor.
1662           final double redv = reductionValue[i];
1663           final double rediv = 1.0 - redv;
1664           final int classI = atomClass[i];
1665           // Molecule index for atom i.
1666           final int moleculei = molecule[i];
1667           // Neural network identity.
1668           boolean iNN = neuralNetwork[i];
1669           // Zero out local gradient accumulation variables.
1670           double gxi = 0.0;
1671           double gyi = 0.0;
1672           double gzi = 0.0;
1673           // Zero out local gradient accumulation variables for reduced atoms.
1674           double gxredi = 0.0;
1675           double gyredi = 0.0;
1676           double gzredi = 0.0;
1677           // Zero out local variables for accumulation of dU/dX/dL.
1678           double lxi = 0.0;
1679           double lyi = 0.0;
1680           double lzi = 0.0;
1681           // Zero out local variables for accumulation of dU/dX/dL for reduced atoms.
1682           double lxredi = 0.0;
1683           double lyredi = 0.0;
1684           double lzredi = 0.0;
1685           // Collect information about special vdW 1-4 interactions,
1686           // and fill the mask array due to application of 1-2, 1-3 and 1-4 vdW scale factors.
1687           applyMask(i, vdw14, mask);
1688           // Default is that the outer loop atom is hard.
1689           boolean[] softCorei = softCore[HARD];
1690           if (isSoft[i]) {
1691             softCorei = softCore[SOFT];
1692           }
1693           // Loop over the neighbor list.
1694           final int[] neighbors = list[i];
1695           for (final int k : neighbors) {
1696             // Check that atom k is in use.
1697             // Do not compute vdW interactions between asymmetric unit neural network atoms.
1698             if (!use[k] || (iNN && neuralNetwork[k])) {
1699               continue;
1700             }
1701             // Flag to indicate if atom k is effected by an extended system variable.
1702             final boolean esvk = esvTerm && esvSystem.isTitratingHydrogen(k);
1703             if (esvTerm) {
1704               esvSystem.getVdwPrefactor(k, esvVdwPrefactork);
1705             }
1706             int k3 = k * 3;
1707             // Atomic coordinates of atom k, including application of reduction factors.
1708             final double xk = xyzS[k3++];
1709             final double yk = xyzS[k3++];
1710             final double zk = xyzS[k3];
1711             // Compute the separation vector between atom i and k.
1712             dx_local[0] = xi - xk;
1713             dx_local[1] = yi - yk;
1714             dx_local[2] = zi - zk;
1715             // Apply the minimum image convention (if periodic).
1716             final double r2 = crystal.image(dx_local);
1717             int classK = atomClass[k];
1718             double irv = vdwForm.getCombinedInverseRmin(classI, classK);
1719             if (vdw14[k]) {
1720               // Some force fields such as CHARMM have special 1-4 vdW parameters.
1721               irv = vdwForm.getCombinedInverseRmin14(classI, classK);
1722             }
1723             if (r2 <= nonbondedCutoff.off2 && mask[k] > 0 && irv > 0) {
1724               // Compute the separation distance.
1725               final double r = sqrt(r2);
1726               // Check if i and k are part of the same molecule.
1727               boolean sameMolecule = (moleculei == molecule[k]);
1728               boolean soft = softCorei[k];
1729               // If both atoms are softcore, respect the intermolecularSoftcore and
1730               // intramolecularSoftcore flags.
1731               if (isSoft[i] && isSoft[k]) {
1732                 if (intermolecularSoftcore && !sameMolecule) {
1733                   soft = true;
1734                 } else if (intramolecularSoftcore && sameMolecule) {
1735                   soft = true;
1736                 }
1737               }
1738               // Hide these global variable names for thread safety.
1739               final double sc1, dsc1dL, d2sc1dL2;
1740               final double sc2, dsc2dL, d2sc2dL2;
1741               if (soft) {
1742                 /*
1743                 The setFactors(i,k) method is empty unless ESVs are present.
1744                 If OST lambda present, lambdaFactors will already have been updated during setLambda().
1745                 */
1746                 sc1 = lambdaFactorsLocal.sc1;
1747                 dsc1dL = lambdaFactorsLocal.dsc1dL;
1748                 d2sc1dL2 = lambdaFactorsLocal.d2sc1dL2;
1749                 sc2 = lambdaFactorsLocal.sc2;
1750                 dsc2dL = lambdaFactorsLocal.dsc2dL;
1751                 d2sc2dL2 = lambdaFactorsLocal.d2sc2dL2;
1752               } else {
1753                 sc1 = 0.0;
1754                 dsc1dL = 0.0;
1755                 d2sc1dL2 = 0.0;
1756                 sc2 = 1.0;
1757                 dsc2dL = 0.0;
1758                 d2sc2dL2 = 0.0;
1759               }
1760               final double alpha = sc1;
1761               final double lambda5 = sc2;
1762               /*
1763                Calculate Van der Waals interaction energy. Notation from
1764                "The structure, thermodynamics, and solubility of organic
1765                crystals from simulation with a polarizable force field."
1766                J. Chem. Theory Comput. 8, 1721–1736 (2012).
1767               */
1768               double ev = mask[k] * vdwForm.getCombinedEps(classI, classK);
1769               if (vdw14[k]) {
1770                 ev = mask[k] * vdwForm.getCombinedEps14(classI, classK);
1771               }
1772               final double eps_lambda = ev * lambda5;
1773               final double rho = r * irv;
1774               final double rhoDisp1 = vdwForm.rhoDisp1(rho);
1775               final double rhoDisp = rhoDisp1 * rho;
1776               final double rhoDelta1 = vdwForm.rhoDelta1(rho + vdwForm.delta);
1777               final double rhoDelta = rhoDelta1 * (rho + vdwForm.delta);
1778               final double alphaRhoDelta = alpha + rhoDelta;
1779               final double alphaRhoDispGamma = alpha + rhoDisp + vdwForm.gamma;
1780               final double t1d = 1.0 / alphaRhoDelta;
1781               final double t2d = 1.0 / alphaRhoDispGamma;
1782               final double t1 = vdwForm.t1n * t1d;
1783               final double t2a = vdwForm.gamma1 * t2d;
1784               final double t2 = t2a - 2.0;
1785               double eik = eps_lambda * t1 * t2;
1786               /*
1787                Apply a multiplicative switch if the interaction
1788                distance is greater than the beginning of the taper.
1789               */
1790               double taper = 1.0;
1791               double dtaper = 0.0;
1792               if (r2 > nonbondedCutoff.cut2) {
1793                 final double r3 = r2 * r;
1794                 final double r4 = r2 * r2;
1795                 final double r5 = r2 * r3;
1796                 taper = multiplicativeSwitch.taper(r, r2, r3, r4, r5);
1797                 dtaper = multiplicativeSwitch.dtaper(r, r2, r3, r4);
1798               }
1799               double esvik = 1.0;
1800               if (esvi || esvk) {
1801                 esvik = esvVdwPrefactori[0] * esvVdwPrefactork[0];
1802               }
1803               e += eik * taper * esvik;
1804               // log(i, k, ev, r, eik * taper * esvik);
1805               count++;
1806               if (!gradient && !soft) {
1807                 continue;
1808               }
1809               final int redk = reductionIndex[k];
1810               final double red = reductionValue[k];
1811               final double redkv = 1.0 - red;
1812               final double dt1d_dr = vdwForm.repDispPower * rhoDelta1 * irv;
1813               final double dt2d_dr = vdwForm.dispersivePower * rhoDisp1 * irv;
1814               final double dt1_dr = t1 * dt1d_dr * t1d;
1815               final double dt2_dr = t2a * dt2d_dr * t2d;
1816               double dedr = -eps_lambda * (dt1_dr * t2 + t1 * dt2_dr);
1817               final double ir = 1.0 / r;
1818               final double drdx = dx_local[0] * ir;
1819               final double drdy = dx_local[1] * ir;
1820               final double drdz = dx_local[2] * ir;
1821               if (gradient) {
1822                 final double dswitch = esvik * (eik * dtaper + dedr * taper);
1823                 final double dedx = dswitch * drdx;
1824                 final double dedy = dswitch * drdy;
1825                 final double dedz = dswitch * drdz;
1826                 gxi += dedx * redv;
1827                 gyi += dedy * redv;
1828                 gzi += dedz * redv;
1829                 gxredi += dedx * rediv;
1830                 gyredi += dedy * rediv;
1831                 gzredi += dedz * rediv;
1832                 grad.sub(threadID, k, red * dedx, red * dedy, red * dedz);
1833                 grad.sub(threadID, redk, redkv * dedx, redkv * dedy, redkv * dedz);
1834                 // Assign this gradient to attached ESVs.
1835                 if (esvi) {
1836                   esvSystem.addVdwDeriv(i, eik * taper, esvVdwPrefactori, esvVdwPrefactork[0]);
1837                 }
1838                 if (esvk) {
1839                   esvSystem.addVdwDeriv(k, eik * taper, esvVdwPrefactork, esvVdwPrefactori[0]);
1840                 }
1841               }
1842               if (gradient && soft) {
1843                 final double dt1 = -t1 * t1d * dsc1dL;
1844                 final double dt2 = -t2a * t2d * dsc1dL;
1845                 final double f1 = dsc2dL * t1 * t2;
1846                 final double f2 = sc2 * dt1 * t2;
1847                 final double f3 = sc2 * t1 * dt2;
1848                 final double dedl = ev * (f1 + f2 + f3);
1849                 dEdL += dedl * taper * esvik;
1850                 final double t1d2 = -dsc1dL * t1d * t1d;
1851                 final double t2d2 = -dsc1dL * t2d * t2d;
1852                 final double d2t1 = -dt1 * t1d * dsc1dL - t1 * t1d * d2sc1dL2 - t1 * t1d2 * dsc1dL;
1853                 final double d2t2 =
1854                     -dt2 * t2d * dsc1dL - t2a * t2d * d2sc1dL2 - t2a * t2d2 * dsc1dL;
1855                 final double df1 = d2sc2dL2 * t1 * t2 + dsc2dL * dt1 * t2 + dsc2dL * t1 * dt2;
1856                 final double df2 = dsc2dL * dt1 * t2 + sc2 * d2t1 * t2 + sc2 * dt1 * dt2;
1857                 final double df3 = dsc2dL * t1 * dt2 + sc2 * dt1 * dt2 + sc2 * t1 * d2t2;
1858                 final double de2dl2 = ev * (df1 + df2 + df3);
1859                 d2EdL2 += de2dl2 * taper * esvik;
1860                 final double t11 = -dsc2dL * t2 * dt1_dr;
1861                 final double t21 = -dsc2dL * t1 * dt2_dr;
1862                 final double t13 = 2.0 * sc2 * t2 * dt1_dr * dsc1dL * t1d;
1863                 final double t23 = 2.0 * sc2 * t1 * dt2_dr * dsc1dL * t2d;
1864                 final double t12 = -sc2 * dt2 * dt1_dr;
1865                 final double t22 = -sc2 * dt1 * dt2_dr;
1866                 final double dedldr = ev * (t11 + t12 + t13 + t21 + t22 + t23);
1867                 final double dswitch = esvik * (dedl * dtaper + dedldr * taper);
1868                 final double dedldx = dswitch * drdx;
1869                 final double dedldy = dswitch * drdy;
1870                 final double dedldz = dswitch * drdz;
1871                 lxi += dedldx * redv;
1872                 lyi += dedldy * redv;
1873                 lzi += dedldz * redv;
1874                 lxredi += dedldx * rediv;
1875                 lyredi += dedldy * rediv;
1876                 lzredi += dedldz * rediv;
1877                 if (lambdaTerm) {
1878                   lambdaGrad.sub(threadID, k, red * dedldx, red * dedldy, red * dedldz);
1879                   lambdaGrad.sub(threadID, redk, redkv * dedldx, redkv * dedldy, redkv * dedldz);
1880                 }
1881               }
1882             }
1883           }
1884           if (gradient) {
1885             grad.add(threadID, i, gxi, gyi, gzi);
1886             grad.add(threadID, redi, gxredi, gyredi, gzredi);
1887             if (lambdaTerm) {
1888               lambdaGrad.add(threadID, i, lxi, lyi, lzi);
1889               lambdaGrad.add(threadID, redi, lxredi, lyredi, lzredi);
1890             }
1891           }
1892           removeMask(i, vdw14, mask);
1893         }
1894         energy += e;
1895         List<SymOp> symOps = crystal.spaceGroup.symOps;
1896         for (int iSymOp = 1; iSymOp < nSymm; iSymOp++) {
1897           e = 0.0;
1898           SymOp symOp = symOps.get(iSymOp);
1899           // Compute the total transformation operator: R = ToCart * Rot * ToFrac.
1900           crystal.getTransformationOperator(symOp, transOp);
1901           xyzS = reduced[iSymOp];
1902           list = neighborLists[iSymOp];
1903 
1904           for (int i = lb; i <= ub; i++) {
1905             int i3 = i * 3;
1906             if (!use[i]) {
1907               continue;
1908             }
1909             final boolean esvi = esvTerm && esvSystem.isTitratingHydrogen(i);
1910             if (esvTerm) {
1911               esvSystem.getVdwPrefactor(i, esvVdwPrefactori);
1912             }
1913             final double xi = reducedXYZ[i3++];
1914             final double yi = reducedXYZ[i3++];
1915             final double zi = reducedXYZ[i3];
1916             final int redi = reductionIndex[i];
1917             final double redv = reductionValue[i];
1918             final double rediv = 1.0 - redv;
1919             final int classI = atomClass[i];
1920             double gxi = 0.0;
1921             double gyi = 0.0;
1922             double gzi = 0.0;
1923             double gxredi = 0.0;
1924             double gyredi = 0.0;
1925             double gzredi = 0.0;
1926             double lxi = 0.0;
1927             double lyi = 0.0;
1928             double lzi = 0.0;
1929             double lxredi = 0.0;
1930             double lyredi = 0.0;
1931             double lzredi = 0.0;
1932             // Default is that the outer loop atom is hard.
1933             boolean[] softCorei = softCore[HARD];
1934             if (isSoft[i]) {
1935               softCorei = softCore[SOFT];
1936             }
1937             // Loop over the neighbor list.
1938             final int[] neighbors = list[i];
1939             for (final int k : neighbors) {
1940               if (!use[k]) {
1941                 continue;
1942               }
1943               final boolean esvk = esvTerm && esvSystem.isTitratingHydrogen(k);
1944               if (esvTerm) {
1945                 esvSystem.getVdwPrefactor(k, esvVdwPrefactork);
1946               }
1947               // Hide these global variable names for thread safety.
1948               final double sc1, dsc1dL, d2sc1dL2;
1949               final double sc2, dsc2dL, d2sc2dL2;
1950               int k3 = k * 3;
1951               final double xk = xyzS[k3++];
1952               final double yk = xyzS[k3++];
1953               final double zk = xyzS[k3];
1954               dx_local[0] = xi - xk;
1955               dx_local[1] = yi - yk;
1956               dx_local[2] = zi - zk;
1957               final double r2 = crystal.image(dx_local);
1958               int classK = atomClass[k];
1959               final double irv = vdwForm.getCombinedInverseRmin(classI, classK);
1960               if (r2 <= nonbondedCutoff.off2 && irv > 0) {
1961                 final double selfScale = (i == k) ? 0.5 : 1.0;
1962                 final double r = sqrt(r2);
1963                 boolean soft = isSoft[i] || softCorei[k];
1964                 if (soft) {
1965                   sc1 = lambdaFactorsLocal.sc1;
1966                   dsc1dL = lambdaFactorsLocal.dsc1dL;
1967                   d2sc1dL2 = lambdaFactorsLocal.d2sc1dL2;
1968                   sc2 = lambdaFactorsLocal.sc2;
1969                   dsc2dL = lambdaFactorsLocal.dsc2dL;
1970                   d2sc2dL2 = lambdaFactorsLocal.d2sc2dL2;
1971                 } else {
1972                   sc1 = 0.0;
1973                   dsc1dL = 0.0;
1974                   d2sc1dL2 = 0.0;
1975                   sc2 = 1.0;
1976                   dsc2dL = 0.0;
1977                   d2sc2dL2 = 0.0;
1978                 }
1979                 final double alpha = sc1;
1980                 final double lambdaN = sc2;
1981                 final double ev = vdwForm.getCombinedEps(classI, classK);
1982                 final double eps_lambda = ev * lambdaN;
1983                 final double rho = r * irv;
1984                 final double rhoDisp1 = vdwForm.rhoDisp1(rho);
1985                 final double rhoDisp = rhoDisp1 * rho;
1986                 final double rhoDelta1 = vdwForm.rhoDelta1(rho + vdwForm.delta);
1987                 final double rhoDelta = rhoDelta1 * (rho + vdwForm.delta);
1988                 final double alphaRhoDelta = alpha + rhoDelta;
1989                 final double alphaRhoDispGamma = alpha + rhoDisp + vdwForm.gamma;
1990                 final double t1d = 1.0 / alphaRhoDelta;
1991                 final double t2d = 1.0 / alphaRhoDispGamma;
1992                 final double t1 = vdwForm.t1n * t1d;
1993                 final double t2a = vdwForm.gamma1 * t2d;
1994                 final double t2 = t2a - 2.0;
1995                 double eik = eps_lambda * t1 * t2;
1996                 // Apply a multiplicative switch if the interaction distance is greater than
1997                 // the beginning of the taper.
1998                 double taper = 1.0;
1999                 double dtaper = 0.0;
2000                 if (r2 > nonbondedCutoff.cut2) {
2001                   final double r3 = r2 * r;
2002                   final double r4 = r2 * r2;
2003                   final double r5 = r2 * r3;
2004                   taper = multiplicativeSwitch.taper(r, r2, r3, r4, r5);
2005                   dtaper = multiplicativeSwitch.dtaper(r, r2, r3, r4);
2006                 }
2007 
2008                 double esvik = 1.0;
2009                 if (esvi || esvk) {
2010                   esvik = esvVdwPrefactori[0] * esvVdwPrefactork[0];
2011                 }
2012                 e += selfScale * eik * taper * esvik;
2013                 count++;
2014                 if (!gradient && !soft) {
2015                   continue;
2016                 }
2017                 final int redk = reductionIndex[k];
2018                 final double red = reductionValue[k];
2019                 final double redkv = 1.0 - red;
2020                 final double dt1d_dr = vdwForm.repDispPower * rhoDelta1 * irv;
2021                 final double dt2d_dr = vdwForm.dispersivePower * rhoDisp1 * irv;
2022                 final double dt1_dr = t1 * dt1d_dr * t1d;
2023                 final double dt2_dr = t2a * dt2d_dr * t2d;
2024                 double dedr = -eps_lambda * (dt1_dr * t2 + t1 * dt2_dr);
2025 
2026                 final double ir = 1.0 / r;
2027                 double drdx = dx_local[0] * ir;
2028                 double drdy = dx_local[1] * ir;
2029                 double drdz = dx_local[2] * ir;
2030                 dedr = selfScale * esvik * (eik * dtaper + dedr * taper);
2031                 if (gradient) {
2032                   double dedx = dedr * drdx;
2033                   double dedy = dedr * drdy;
2034                   double dedz = dedr * drdz;
2035                   gxi += dedx * redv;
2036                   gyi += dedy * redv;
2037                   gzi += dedz * redv;
2038                   gxredi += dedx * rediv;
2039                   gyredi += dedy * rediv;
2040                   gzredi += dedz * rediv;
2041 
2042                   // Apply the transpose of the transformation operator.
2043                   final double dedxk = dedx * transOp[0][0] + dedy * transOp[1][0] + dedz * transOp[2][0];
2044                   final double dedyk = dedx * transOp[0][1] + dedy * transOp[1][1] + dedz * transOp[2][1];
2045                   final double dedzk = dedx * transOp[0][2] + dedy * transOp[1][2] + dedz * transOp[2][2];
2046                   grad.sub(threadID, k, red * dedxk, red * dedyk, red * dedzk);
2047                   grad.sub(threadID, redk, redkv * dedxk, redkv * dedyk, redkv * dedzk);
2048 
2049                   // Assign this gradient to attached ESVs.
2050                   if (esvi) {
2051                     esvSystem.addVdwDeriv(i, selfScale * eik * taper, esvVdwPrefactori,
2052                         esvVdwPrefactork[0]);
2053                   }
2054                   if (esvk) {
2055                     esvSystem.addVdwDeriv(k, selfScale * eik * taper, esvVdwPrefactork,
2056                         esvVdwPrefactori[0]);
2057                   }
2058                 }
2059                 if (gradient && lambdaTerm && soft) {
2060                   double dt1 = -t1 * t1d * dsc1dL;
2061                   double dt2 = -t2a * t2d * dsc1dL;
2062                   double f1 = dsc2dL * t1 * t2;
2063                   double f2 = sc2 * dt1 * t2;
2064                   double f3 = sc2 * t1 * dt2;
2065                   final double dedl = ev * (f1 + f2 + f3);
2066                   dEdL += selfScale * dedl * taper * esvik;
2067                   double t1d2 = -dsc1dL * t1d * t1d;
2068                   double t2d2 = -dsc1dL * t2d * t2d;
2069                   double d2t1 = -dt1 * t1d * dsc1dL - t1 * t1d * d2sc1dL2 - t1 * t1d2 * dsc1dL;
2070                   double d2t2 = -dt2 * t2d * dsc1dL - t2a * t2d * d2sc1dL2 - t2a * t2d2 * dsc1dL;
2071                   double df1 = d2sc2dL2 * t1 * t2 + dsc2dL * dt1 * t2 + dsc2dL * t1 * dt2;
2072                   double df2 = dsc2dL * dt1 * t2 + sc2 * d2t1 * t2 + sc2 * dt1 * dt2;
2073                   double df3 = dsc2dL * t1 * dt2 + sc2 * dt1 * dt2 + sc2 * t1 * d2t2;
2074                   double de2dl2 = ev * (df1 + df2 + df3);
2075                   d2EdL2 += selfScale * de2dl2 * taper * esvik;
2076                   double t11 = -dsc2dL * t2 * dt1_dr;
2077                   double t12 = -sc2 * dt2 * dt1_dr;
2078                   double t13 = 2.0 * sc2 * t2 * dt1_dr * dsc1dL * t1d;
2079                   double t21 = -dsc2dL * t1 * dt2_dr;
2080                   double t22 = -sc2 * dt1 * dt2_dr;
2081                   double t23 = 2.0 * sc2 * t1 * dt2_dr * dsc1dL * t2d;
2082                   double dedldr = ev * (t11 + t12 + t13 + t21 + t22 + t23);
2083                   dedldr = esvik * (dedl * dtaper + dedldr * taper);
2084                   double dedldx = selfScale * dedldr * drdx;
2085                   double dedldy = selfScale * dedldr * drdy;
2086                   double dedldz = selfScale * dedldr * drdz;
2087                   lxi += dedldx * redv;
2088                   lyi += dedldy * redv;
2089                   lzi += dedldz * redv;
2090                   lxredi += dedldx * rediv;
2091                   lyredi += dedldy * rediv;
2092                   lzredi += dedldz * rediv;
2093 
2094                   // Apply the transpose of the transformation operator.
2095                   final double dedldxk =
2096                       dedldx * transOp[0][0] + dedldy * transOp[1][0] + dedldz * transOp[2][0];
2097                   final double dedldyk =
2098                       dedldx * transOp[0][1] + dedldy * transOp[1][1] + dedldz * transOp[2][1];
2099                   final double dedldzk =
2100                       dedldx * transOp[0][2] + dedldy * transOp[1][2] + dedldz * transOp[2][2];
2101                   lambdaGrad.sub(threadID, k, red * dedldxk, red * dedldyk, red * dedldzk);
2102                   lambdaGrad.sub(threadID, redk, redkv * dedldxk, redkv * dedldyk, redkv * dedldzk);
2103                 }
2104               }
2105             }
2106             if (gradient) {
2107               grad.add(threadID, i, gxi, gyi, gzi);
2108               grad.add(threadID, redi, gxredi, gyredi, gzredi);
2109               if (lambdaTerm) {
2110                 lambdaGrad.add(threadID, i, lxi, lyi, lzi);
2111                 lambdaGrad.add(threadID, redi, lxredi, lyredi, lzredi);
2112               }
2113             }
2114           }
2115           energy += e;
2116         }
2117       }
2118 
2119       @Override
2120       public IntegerSchedule schedule() {
2121         return pairwiseSchedule;
2122       }
2123 
2124       @Override
2125       public void start() {
2126         threadID = getThreadIndex();
2127         time = -System.nanoTime();
2128         energy = 0.0;
2129         count = 0;
2130         if (lambdaTerm) {
2131           dEdL = 0.0;
2132           d2EdL2 = 0.0;
2133         }
2134         lambdaFactorsLocal = lambdaFactors[threadID];
2135         if (lambdaFactorsLocal == null) {
2136           System.exit(1);
2137         }
2138 
2139         if (mask == null || mask.length < nAtoms) {
2140           mask = new double[nAtoms];
2141           fill(mask, 1.0);
2142           vdw14 = new boolean[nAtoms];
2143           fill(vdw14, false);
2144         }
2145       }
2146 
2147     }
2148 
2149     /**
2150      * Reduce Van der Waals gradient.
2151      */
2152     private class ReductionLoop extends IntegerForLoop {
2153 
2154       protected long time;
2155       int threadID;
2156 
2157       @Override
2158       public void finish() {
2159         time += System.nanoTime();
2160       }
2161 
2162       @Override
2163       public void run(int lb, int ub) {
2164         if (gradient) {
2165           grad.reduce(lb, ub);
2166           for (int i = lb; i <= ub; i++) {
2167             Atom ai = atoms[i];
2168             ai.addToXYZGradient(grad.getX(i), grad.getY(i), grad.getZ(i));
2169           }
2170         }
2171         if (lambdaTerm) {
2172           lambdaGrad.reduce(lb, ub);
2173         }
2174       }
2175 
2176       @Override
2177       public void start() {
2178         threadID = getThreadIndex();
2179         time = -System.nanoTime();
2180       }
2181 
2182     }
2183 
2184     /**
2185      * Build the NeighborList.
2186      */
2187     private class NeighborListBarrier extends BarrierAction {
2188 
2189       @Override
2190       public void run() {
2191         neighborListTotalTime = -System.nanoTime();
2192         neighborList.buildList(reduced, neighborLists, null, forceNeighborListRebuild, false);
2193         neighborListTotalTime += System.nanoTime();
2194       }
2195     }
2196   }
2197 }