View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.nonbonded.implicit;
39  
40  import static ffx.numerics.atomic.AtomicDoubleArray.atomicDoubleArrayFactory;
41  import static ffx.numerics.math.DoubleMath.add;
42  import static ffx.numerics.math.DoubleMath.length2;
43  import static ffx.numerics.math.DoubleMath.scale;
44  import static ffx.numerics.math.DoubleMath.sub;
45  import static java.lang.Double.compare;
46  import static java.lang.String.format;
47  import static org.apache.commons.math3.util.FastMath.PI;
48  import static org.apache.commons.math3.util.FastMath.cbrt;
49  import static org.apache.commons.math3.util.FastMath.exp;
50  import static org.apache.commons.math3.util.FastMath.pow;
51  
52  import edu.rit.pj.IntegerForLoop;
53  import edu.rit.pj.ParallelRegion;
54  import edu.rit.pj.ParallelTeam;
55  import edu.rit.pj.reduction.SharedDouble;
56  import ffx.numerics.atomic.AtomicDoubleArray;
57  import ffx.numerics.atomic.AtomicDoubleArray.AtomicDoubleArrayImpl;
58  import ffx.numerics.atomic.AtomicDoubleArray3D;
59  import ffx.potential.bonded.Atom;
60  import ffx.potential.parameters.ForceField;
61  
62  import java.util.ArrayList;
63  import java.util.Collections;
64  import java.util.List;
65  import java.util.logging.Level;
66  import java.util.logging.Logger;
67  
68  /**
69   * GaussVol implements a description molecular volume and surface area described by overlapping
70   * Gaussian spheres.
71   *
72   * <p>Ported from C++ code by Emilio Gallicchio. GaussVol is part of the AGBNP/OpenMM implicit
73   * solvent model.
74   *
75   * @author Michael J. Schnieders
76   * @see <a href="https://doi.org/10.1002/jcc.24745" target="_blank">
77   * Zhang, B., Kilburg, D., Eastman, P., Pande, V. S., and Gallicchio, E. (2017).
78   * Efficient gaussian density formulation of volume and surface areas of macromolecules on graphical processing units.
79   * Journal of Computational Chemistry, 38(10), 740-752.</a>
80   * @since 1.0
81   */
82  public class GaussVol {
83  
84    /* -------------------------------------------------------------------------- *
85     *                                 GaussVol                                   *
86     * -------------------------------------------------------------------------- *
87     * This file is part of the AGBNP/OpenMM implicit solvent model software      *
88     * implementation funded by the National Science Foundation under grant:      *
89     * NSF SI2 1440665  "SI2-SSE: High-Performance Software for Large-Scale       *
90     * Modeling of Binding Equilibria"                                            *
91     *                                                                            *
92     * copyright (c) 2016 Emilio Gallicchio                                       *
93     * Authors: Emilio Gallicchio <egallicchio@brooklyn.cuny.edu>                 *
94     * Contributors:                                                              *
95     *                                                                            *
96     *  AGBNP/OpenMM is free software: you can redistribute it and/or modify      *
97     *  it under the terms of the GNU Lesser General Public License version 3     *
98     *  as published by the Free Software Foundation.                             *
99     *                                                                            *
100    *  AGBNP/OpenMM is distributed in the hope that it will be useful,           *
101    *  but WITHOUT ANY WARRANTY; without even the implied warranty of            *
102    *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
103    *  GNU General Public License for more details.                              *
104    *                                                                            *
105    *  You should have received a copy of the GNU General Public License         *
106    *  along with AGBNP/OpenMM.  If not, see <http://www.gnu.org/licenses/>      *
107    *                                                                            *
108    * -------------------------------------------------------------------------- */
109 
110   private static final Logger logger = Logger.getLogger(GaussVol.class.getName());
111   /**
112    * Finite-Difference step size to compute surface area.
113    */
114   private static final double offset = 0.005;
115   /**
116    * Conversion factor from a sphere to a Gaussian.
117    */
118   private static final double KFC = 2.2269859253;
119 
120   private static final double PFC = 2.5;
121   /**
122    * Set this to either KFC or PFC.
123    */
124   private static final double sphereConversion = KFC;
125   /**
126    * Maximum overlap level.
127    */
128   private static final int MAX_ORDER = 16;
129   /**
130    * Volume cutoffs for the switching function (A^3).
131    */
132   private static final double ANG3 = 1.0;
133   /**
134    * Volumes smaller than VOLMINA are switched to zero.
135    */
136   private static final double VOLMINA = 0.01 * ANG3;
137   /**
138    * Volumes larger than VOLMINB are not switched.
139    */
140   private static final double VOLMINB = 0.1 * ANG3;
141   /**
142    * Smallest positive double value.
143    */
144   private static final double MIN_GVOL = Double.MIN_VALUE;
145 
146   /**
147    * Default offset applied to radii for use with Gaussian Volumes to correct for not including
148    * hydrogen atoms.
149    */
150   public static final double DEFAULT_GAUSSVOL_RADII_OFFSET = 0.0;
151   /**
152    * Default scaling applied to radii for use with Gaussian Volumes to correct for not including
153    * hydrogen atoms and general underestimation of molecular volume
154    * <p>
155    * Default set to 1.15 to match Corrigan et al. 2023 protein model
156    * Can be set to greater than 1.0 to increase the radii by a uniform percentage
157    * (ex: a radii scale of 1.25 increases all radii by 25%)
158    */
159   public static final double DEFAULT_GAUSSVOL_RADII_SCALE = 1.15;
160 
161   private static final double RMIN_TO_SIGMA = 1.0 / pow(2.0, 1.0 / 6.0);
162 
163   // Output Variables
164   private final GaussVolRegion gaussVolRegion;
165 
166   private enum GAUSSVOL_MODE {COMPUTE_TREE, RESCAN_TREE}
167 
168   private final AtomicDoubleArray3D grad;
169   private final SharedDouble totalVolume;
170   private final SharedDouble energy;
171   private final AtomicDoubleArray gradV;
172   private final AtomicDoubleArray freeVolume;
173   private final AtomicDoubleArray selfVolume;
174   private final ParallelTeam parallelTeam;
175 
176   private final double vdwRadiiOffset;
177   private final double vdwRadiiScale;
178   private final boolean includeHydrogen;
179   private final boolean useSigma;
180   private static final double FOUR_THIRDS_PI = 4.0 / 3.0 * PI;
181 
182   /**
183    * Array of Atoms in the system.
184    */
185   private final Atom[] atoms;
186   /**
187    * Number of atoms.
188    */
189   private final int nAtoms;
190   /**
191    * Atomic radii for all atoms.
192    */
193   private double[] radii;
194   /**
195    * Atomic volumes for all atoms, which are computed from atomic radii.
196    */
197   private double[] volumes;
198   /**
199    * All atomic radii with a small offset added, which are used to compute surface area using a
200    * finite-difference approach.
201    */
202   private final double[] radiiOffset;
203   /**
204    * Atomic "self" volumes for all atoms, which are computed from the radii plus offset array.
205    */
206   private final double[] volumeOffset;
207   /**
208    * Surface tension -- in our implementation these values are kept at 1.0.
209    */
210   private final double[] gammas;
211   /**
212    * Flag to denote if an atom is a hydrogen, which results in it not contributing to the volume or
213    * S.A.
214    */
215   private final boolean[] ishydrogen;
216   /**
217    * The self-volume fraction is the self-volume divided by the volume that ignores overlaps.
218    */
219   private final double[] selfVolumeFraction;
220   /**
221    * The Gaussian Overlap Tree.
222    */
223   private final GaussianOverlapTree tree;
224   /**
225    * Maximum depth that the tree reaches
226    */
227   private int maximumDepth = 0;
228   /**
229    * Total number of overlaps in overlap tree
230    */
231   private int totalNumberOfOverlaps = 0;
232   /**
233    * Surface area (Ang^2).
234    */
235   private double surfaceArea;
236   /**
237    * Volume (Ang^3).
238    */
239   private double volume;
240   /**
241    * The volume gradient, which does not include the solvent pressure constant (Ang^2).
242    */
243   private double[] volumeGradient;
244   /**
245    * The surface area gradient, which does not include the surface tension constant (Ang).
246    */
247   private double[] surfaceAreaGradient;
248 
249   /**
250    * Creates/Initializes a GaussVol instance.
251    *
252    * @param atoms        The atoms to examine.
253    * @param forceField   The ForceField in use.
254    * @param parallelTeam ParallelTeam for Parallal jobs.
255    */
256   public GaussVol(Atom[] atoms, ForceField forceField, ParallelTeam parallelTeam) {
257     this.atoms = atoms;
258     nAtoms = atoms.length;
259     tree = new GaussianOverlapTree(nAtoms);
260     radii = new double[nAtoms];
261     volumes = new double[nAtoms];
262     gammas = new double[nAtoms];
263     ishydrogen = new boolean[nAtoms];
264     radiiOffset = new double[nAtoms];
265     volumeOffset = new double[nAtoms];
266     selfVolumeFraction = new double[nAtoms];
267 
268     vdwRadiiOffset = forceField.getDouble("GAUSSVOL_RADII_OFFSET", DEFAULT_GAUSSVOL_RADII_OFFSET);
269     vdwRadiiScale = forceField.getDouble("GAUSSVOL_RADII_SCALE", DEFAULT_GAUSSVOL_RADII_SCALE);
270     includeHydrogen = forceField.getBoolean("GAUSSVOL_HYDROGEN", false);
271     useSigma = forceField.getBoolean("GAUSSVOL_USE_SIGMA", false);
272 
273     for (int i = 0; i < nAtoms; i++) {
274       updateAtom(i);
275     }
276 
277     this.parallelTeam = parallelTeam;
278     int nThreads = 1;
279     if (parallelTeam != null) {
280       nThreads = parallelTeam.getThreadCount();
281       gaussVolRegion = new GaussVolRegion(nThreads);
282     } else {
283       gaussVolRegion = null;
284     }
285 
286     totalVolume = new SharedDouble();
287     energy = new SharedDouble();
288     AtomicDoubleArrayImpl atomicDoubleArrayImpl = AtomicDoubleArrayImpl.MULTI;
289     grad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, nThreads);
290     gradV = atomicDoubleArrayFactory(atomicDoubleArrayImpl, nThreads, nAtoms);
291     freeVolume = atomicDoubleArrayFactory(atomicDoubleArrayImpl, nThreads, nAtoms);
292     selfVolume = atomicDoubleArrayFactory(atomicDoubleArrayImpl, nThreads, nAtoms);
293   }
294 
295   /**
296    * Get the radii.
297    *
298    * @return Returns the radii.
299    */
300   public double[] getRadii() {
301     return radii;
302   }
303 
304   /**
305    * Overlap between two Gaussians represented by a (V,c,a) triplet
306    *
307    * <p>V: volume of Gaussian c: position of Gaussian a: exponential coefficient
308    *
309    * <p>g(x) = V (a/pi)^(3/2) exp(-a(x-c)^2)
310    *
311    * <p>this version is based on V=V(V1,V2,r1,r2,alpha) alpha = (a1 + a2)/(a1 a2)
312    *
313    * <p>dVdr is (1/r)*(dV12/dr) dVdV is dV12/dV1 dVdalpha is dV12/dalpha d2Vdalphadr is
314    * (1/r)*d^2V12/dalpha dr d2VdVdr is (1/r) d^2V12/dV1 dr
315    *
316    * @param g1   Gaussian 1.
317    * @param g2   Gaussian 2.
318    * @param g12  Overlap Gaussian.
319    * @param dVdr is (1/r)*(dV12/dr)
320    * @param dVdV is dV12/dV1
321    * @param sfp  Derivative of volume.
322    * @return Volume.
323    */
324   private static double overlapGaussianAlpha(
325       GaussianVca g1, GaussianVca g2, GaussianVca g12, double[] dVdr, double[] dVdV, double[] sfp) {
326     double[] c1 = g1.c;
327     double[] c2 = g2.c;
328     double[] dist = new double[3];
329 
330     sub(c2, c1, dist);
331     double d2 = length2(dist);
332     double a12 = g1.a + g2.a;
333     double deltai = 1.0 / a12;
334 
335     // 1/alpha
336     double df = (g1.a) * (g2.a) * deltai;
337     double ef = exp(-df * d2);
338     double gvol = ((g1.v * g2.v) / pow(PI / df, 1.5)) * ef;
339 
340     // (1/r)*(dV/dr) w/o switching function
341     double dgvol = -2.0 * df * gvol;
342     dVdr[0] = dgvol;
343 
344     // (1/r)*(dV/dr) w/o switching function
345     double dgvolv = g1.v > 0.0 ? gvol / g1.v : 0.0;
346     dVdV[0] = dgvolv;
347 
348     // Parameters for overlap gaussian.
349     // Note that c1 and c2 are Vec3's, and the "*" operator wants the vector first and scalar
350     // second:
351     // vector2 = vector1 * scalar
352     // g12.c = ((c1 * g1.a) + (c2 * g2.a)) * deltai;
353     double[] c1a = new double[3];
354     double[] c2a = new double[3];
355     scale(c1, g1.a * deltai, c1a);
356     scale(c2, g2.a * deltai, c2a);
357     add(c1a, c2a, g12.c);
358     g12.a = a12;
359     g12.v = gvol;
360 
361     // Switching function
362     double[] sp = new double[1];
363     double s = switchingFunction(gvol, VOLMINA, VOLMINB, sp);
364     sfp[0] = sp[0] * gvol + s;
365     return s * gvol;
366   }
367 
368   /**
369    * Overlap volume switching function and 1st derivative.
370    *
371    * @param gvol    Gaussian volume.
372    * @param volmina Volume is zero below this limit.
373    * @param volminb Volume is not switched above this limit.
374    * @param sp      Switch first derivative.
375    * @return Switch value.
376    */
377   private static double switchingFunction(
378       double gvol, double volmina, double volminb, double[] sp) {
379     if (gvol > volminb) {
380       sp[0] = 0.0;
381       return 1.0;
382     } else if (gvol < volmina) {
383       sp[0] = 0.0;
384       return 0.0;
385     }
386     double swd = 1.0 / (volminb - volmina);
387     double swu = (gvol - volmina) * swd;
388     double swu2 = swu * swu;
389     double swu3 = swu * swu2;
390     sp[0] = swd * 30.0 * swu2 * (1.0 - 2.0 * swu + swu2);
391     return swu3 * (10.0 - 15.0 * swu + 6.0 * swu2);
392   }
393 
394   /**
395    * Return the self volume fraction for each atom.
396    *
397    * @return The self-volume fractions.
398    */
399   public double[] getSelfVolumeFractions() {
400     return selfVolumeFraction;
401   }
402 
403   /**
404    * Compute molecular volume and surface area.
405    *
406    * @param positions Atomic positions to use.
407    * @return The volume.
408    */
409   public double computeVolumeAndSA(double[][] positions) {
410 
411     if (parallelTeam == null || parallelTeam.getThreadCount() == 1) {
412       // Execute in the current thread.
413       for (int i = 0; i < nAtoms - 1; i++) {
414         updateAtom(i);
415       }
416 
417       // Update the overlap tree.
418       computeTree(positions);
419 
420       // Compute the volume.
421       computeVolume(totalVolume, energy, grad, gradV, freeVolume, selfVolume);
422     } else {
423       // Execute in parallel.
424       try {
425         gaussVolRegion.init(GAUSSVOL_MODE.COMPUTE_TREE, positions);
426         parallelTeam.execute(gaussVolRegion);
427       } catch (Exception e) {
428         logger.severe(" Exception evaluating GaussVol " + e);
429       }
430     }
431 
432     if (volumeGradient == null || volumeGradient.length != nAtoms * 3) {
433       volumeGradient = new double[nAtoms * 3];
434       surfaceAreaGradient = new double[nAtoms * 3];
435     }
436 
437     double selfVolumeSum = 0.0;
438     double noOverlapVolume = 0.0;
439     for (int i = 0; i < nAtoms; i++) {
440       double si = selfVolume.get(i);
441       selfVolumeSum += si;
442       double x = grad.getX(i);
443       double y = grad.getY(i);
444       double z = grad.getZ(i);
445       if (ishydrogen[i]) {
446         x = 0.0;
447         y = 0.0;
448         z = 0.0;
449         selfVolumeFraction[i] = 0.0;
450       } else {
451         double vi = volumes[i];
452         noOverlapVolume += vi;
453         selfVolumeFraction[i] = si / vi;
454       }
455       // Volume based gradient
456       int index = i * 3;
457       volumeGradient[index++] = x;
458       volumeGradient[index++] = y;
459       volumeGradient[index] = z;
460 
461       // Surface Area based gradient
462       index = i * 3;
463       surfaceAreaGradient[index++] = x;
464       surfaceAreaGradient[index++] = y;
465       surfaceAreaGradient[index] = z;
466     }
467 
468     // Log out the perfect self-volumes.
469     if (logger.isLoggable(Level.FINE)) {
470       double meanHCT = selfVolumeSum / noOverlapVolume;
471       logger.fine(format("\n Mean Self-Volume Fraction: %16.8f", meanHCT));
472       logger.fine(format(" Cube Root of the Fraction: %16.8f", cbrt(meanHCT)));
473       if (logger.isLoggable(Level.FINEST)) {
474         logger.finest(" Atomic Self-volume Fractions");
475         for (int i = 0; i < nAtoms; i++) {
476           logger.finest(format(" %6d %16.8f", i + 1, selfVolumeFraction[i]));
477         }
478       }
479     }
480 
481     // Save the total molecular volume.
482     volume = totalVolume.get();
483 
484     // Save a reference to non-offset radii and volumes.
485     double[] radiiBak = this.radii;
486     double[] volumeBak = this.volumes;
487 
488     // Load the offset radii and volumes.
489     this.radii = radiiOffset;
490     this.volumes = volumeOffset;
491 
492     // Run the volume calculation on radii that are slightly offset
493     // for finite difference calculation of surface area.
494 
495     if (parallelTeam == null || parallelTeam.getThreadCount() == 1) {
496       // Execute in the current thread.
497       // Rescan the overlap tree.
498       rescanTreeVolumes(positions);
499       // Compute the volume.
500       computeVolume(totalVolume, energy, grad, gradV, freeVolume, selfVolume);
501     } else {
502       // Execute in parallel.
503       try {
504         gaussVolRegion.init(GAUSSVOL_MODE.RESCAN_TREE, positions);
505         parallelTeam.execute(gaussVolRegion);
506       } catch (Exception e) {
507         logger.severe(" Exception evaluating GaussVol " + e);
508       }
509     }
510 
511     // Set the radii and volumes to their non-offset values.
512     this.radii = radiiBak;
513     this.volumes = volumeBak;
514 
515     double selfVolumeOffsetSum = 0;
516     for (int i = 0; i < nAtoms; i++) {
517       selfVolumeOffsetSum += selfVolume.get(i);
518       double x = grad.getX(i);
519       double y = grad.getY(i);
520       double z = grad.getZ(i);
521       if (ishydrogen[i]) {
522         x = 0.0;
523         y = 0.0;
524         z = 0.0;
525       }
526       // Surface Area based gradient
527       int index = i * 3;
528       surfaceAreaGradient[index] = (x - surfaceAreaGradient[index++]) / offset;
529       surfaceAreaGradient[index] = (y - surfaceAreaGradient[index++]) / offset;
530       surfaceAreaGradient[index] = (z - surfaceAreaGradient[index]) / offset;
531     }
532 
533     // Calculate the surface area.
534     surfaceArea = (selfVolumeOffsetSum - selfVolumeSum) / offset;
535 
536     return volume;
537   }
538 
539   /**
540    * Returns the maximum depth of the overlap tree
541    *
542    * @return maximumDepth
543    */
544   public int getMaximumDepth() {
545     return maximumDepth;
546   }
547 
548   /**
549    * Return Surface Area (A^2).
550    *
551    * @return Surface Area (A^2).
552    */
553   public double getSurfaceArea() {
554     return surfaceArea;
555   }
556 
557   public double[] getSurfaceAreaGradient() {
558     return surfaceAreaGradient;
559   }
560 
561   /**
562    * Return the total number of overlaps in the tree
563    *
564    * @return totalNumberOfOverlaps
565    */
566   public int getTotalNumberOfOverlaps() {
567     return totalNumberOfOverlaps;
568   }
569 
570   /**
571    * Return Volume (A^3).
572    *
573    * @return Volume (A^3).
574    */
575   public double getVolume() {
576     return volume;
577   }
578 
579   public double[] getVolumeGradient() {
580     return volumeGradient;
581   }
582 
583   public void allocate(Atom[] atoms) throws Exception {
584     if (atoms.length != this.atoms.length) {
585       throw new Exception(" allocate: number of atoms does not match");
586     }
587   }
588 
589   /**
590    * Constructs the tree.
591    *
592    * @param positions Current atomic positions.
593    */
594   private void computeTree(double[][] positions) {
595     tree.computeOverlapTreeR(positions, radii, volumes, gammas, ishydrogen);
596   }
597 
598   /**
599    * Returns GaussVol volume energy function and forces. Also returns gradients with respect to
600    * atomic volumes and atomic free-volumes and self-volumes.
601    *
602    * @param totalVolume Total volume.
603    * @param totalEnergy Total energy.
604    * @param grad        Atomic gradient.
605    * @param gradV       Volume gradient.
606    * @param freeVolume  Free volume.
607    * @param selfVolume  Self volume.
608    */
609   private void computeVolume(
610       SharedDouble totalVolume,
611       SharedDouble totalEnergy,
612       AtomicDoubleArray3D grad,
613       AtomicDoubleArray gradV,
614       AtomicDoubleArray freeVolume,
615       AtomicDoubleArray selfVolume) {
616     // Initialize output variables.
617     totalVolume.set(0.0);
618     totalEnergy.set(0.0);
619     grad.reset(0, 0, nAtoms - 1);
620     gradV.reset(0, 0, nAtoms - 1);
621     freeVolume.reset(0, 0, nAtoms - 1);
622     selfVolume.reset(0, 0, nAtoms - 1);
623 
624     // Compute the volume.
625     tree.computeVolume2R(totalVolume, totalEnergy, grad, gradV, freeVolume, selfVolume);
626 
627     // Reduce output variables.
628     grad.reduce(0, nAtoms - 1);
629     gradV.reduce(0, nAtoms - 1);
630     freeVolume.reduce(0, nAtoms - 1);
631     selfVolume.reduce(0, nAtoms - 1);
632 
633     for (int i = 0; i < nAtoms; ++i) {
634       if (volumes[i] > 0) {
635         gradV.set(0, i, gradV.get(i) / volumes[i]);
636       }
637     }
638   }
639 
640   /**
641    * Rescan the tree after resetting gammas, radii and volumes.
642    *
643    * @param positions Atomic coordinates.
644    */
645   private void rescanTreeVolumes(double[][] positions) {
646     tree.rescanTreeV(positions, radii, volumes, gammas, ishydrogen);
647   }
648 
649   /**
650    * Rescan the tree resetting gammas only with current values.
651    */
652   private void rescanTreeGammas() {
653     tree.rescanTreeG(gammas);
654   }
655 
656   /**
657    * Returns number of overlaps for each atom.
658    *
659    * @param nov Number of overlaps.
660    */
661   private void getStats(int[] nov) {
662     if (nov.length != nAtoms) {
663       return;
664     }
665 
666     for (int i = 0; i < nAtoms; i++) {
667       nov[i] = 0;
668     }
669     for (int atom = 0; atom < nAtoms; atom++) {
670       int slot = atom + 1;
671       nov[atom] = tree.nChildrenUnderSlotR(slot);
672     }
673   }
674 
675   /**
676    * Print the tree.
677    */
678   private void printTree() {
679     tree.printTree();
680   }
681 
682   /**
683    * 3D Gaussian, V,c,a representation.
684    */
685   private static class GaussianVca {
686 
687     // Gaussian volume
688     public double v;
689     // Gaussian exponent
690     public double a;
691     // Center
692     public double[] c = new double[3];
693   }
694 
695   /**
696    * Overlap between two Gaussians represented by a (V,c,a) triplet V: volume of Gaussian c: position
697    * of Gaussian a: exponential coefficient
698    *
699    * <p>g(x) = V (a/pi)^(3/2) exp(-a(x-c)^2)
700    *
701    * <p>This version is based on V=V(V1,V2,r1,r2,alpha) alpha = (a1 + a2)/(a1 a2) dVdr is
702    * (1/r)*(dV12/dr) dVdV is dV12/dV1 dVdalpha is dV12/dalpha d2Vdalphadr is (1/r)*d^2V12/dalpha dr
703    * d2VdVdr is (1/r) d^2V12/dV1 dr
704    */
705   private static class GaussianOverlap implements Comparable<GaussianOverlap> {
706 
707     /**
708      * level (0=root, 1=atoms, 2=2-body, 3=3-body, etc.)
709      */
710     public int level;
711     /**
712      * Gaussian representing overlap
713      */
714     public GaussianVca g;
715     /**
716      * Volume of overlap (also stores Psi1..i in GPU version)
717      */
718     public double volume;
719     /**
720      * The atomic index of the last atom of the overlap list (i, j, k, ..., atom)
721      */
722     public int atom;
723     /**
724      * Derivative wrt volume of first atom (also stores F1..i in GPU version)
725      */
726     double dvv1;
727     /**
728      * Derivative wrt position of first atom (also stores P1..i in GPU version)
729      */
730     double[] dv1;
731     /**
732      * Sum gammai for this overlap (surface tension parameter)
733      */
734     double gamma1i;
735     /**
736      * Self volume accumulator (also stores Psi'1..i in GPU version)
737      */
738     double selfVolume;
739     /**
740      * Switching function derivatives
741      */
742     double sfp;
743     /**
744      * = (Parent, atom) index in tree list of parent overlap
745      */
746     int parentIndex;
747     /**
748      * Start index in tree array of children
749      */
750     int childrenStartIndex;
751     /**
752      * Number of children.
753      */
754     int childrenCount;
755 
756     public GaussianOverlap() {
757       g = new GaussianVca();
758       dv1 = new double[3];
759     }
760 
761     public GaussianOverlap(GaussianVca g, double volume, double selfVolume, int atom) {
762       this.g = g;
763       this.volume = volume;
764       this.selfVolume = selfVolume;
765       this.atom = atom;
766 
767       dv1 = new double[3];
768     }
769 
770     /**
771      * Order by volume, larger first.
772      *
773      * @param o GaussianOverlap to compare to.
774      * @return Compare by volume.
775      */
776     @Override
777     public int compareTo(GaussianOverlap o) {
778       return compare(volume, o.volume);
779     }
780 
781     /**
782      * Print overlaps.
783      */
784     public void printOverlap() {
785       logger.info(format(" Gaussian Overlap %d: Atom: %d, Parent: %d, ChildrenStartIndex: %d, ChildrenCount: %d,"
786               + "Volume: %6.3f, Gamma: %6.3f, Gauss.a: %6.3f, Gauss.v: %6.3f, Gauss.center (%6.3f,%6.3f,%6.3f),"
787               + "dedx: %6.3f, dedy: %6.3f, dedz: %6.3f, sfp: %6.3f",
788           level, atom, parentIndex, childrenStartIndex, childrenCount, volume, gamma1i,
789           g.a, g.v, g.c[0], g.c[1], g.c[2], dv1[0], dv1[1], dv1[2], sfp));
790     }
791   }
792 
793   public void updateAtom(int i) {
794     Atom atom = atoms[i];
795     ishydrogen[i] = atom.isHydrogen();
796     if (includeHydrogen) {
797       ishydrogen[i] = false;
798     }
799     radii[i] = atom.getVDWType().radius / 2.0;
800     if (useSigma) {
801       radii[i] *= RMIN_TO_SIGMA;
802     }
803     radii[i] += vdwRadiiOffset;
804     radii[i] *= vdwRadiiScale;
805     volumes[i] = FOUR_THIRDS_PI * pow(radii[i], 3);
806     gammas[i] = 1.0;
807     radiiOffset[i] = radii[i] + offset;
808     volumeOffset[i] = FOUR_THIRDS_PI * pow(radiiOffset[i], 3);
809   }
810 
811   /**
812    * Gaussian Overlap Tree.
813    */
814   private class GaussianOverlapTree {
815 
816     /**
817      * Number of atoms.
818      */
819     int nAtoms;
820     /**
821      * The root is at index 0. Atoms are from 1 .. nAtoms.
822      */
823     List<GaussianOverlap> overlaps;
824 
825     /**
826      * GaussianOverlapTree constructor.
827      *
828      * @param nAtoms Number of atoms.
829      */
830     GaussianOverlapTree(int nAtoms) {
831       this.nAtoms = nAtoms;
832       overlaps = Collections.synchronizedList(new ArrayList<>(nAtoms + 1));
833     }
834 
835     /**
836      * Initialize the Overlap Tree.
837      *
838      * @param pos        Atomic positions.
839      * @param radii      Atomic radii.
840      * @param volumes    Atomic volumes.
841      * @param gammas     Atomic surface tensions.
842      * @param ishydrogen True if the atom is a hydrogen.
843      */
844     void initOverlapTree(
845         double[][] pos, double[] radii, double[] volumes, double[] gammas, boolean[] ishydrogen) {
846 
847       // Reset tree
848       overlaps = Collections.synchronizedList(new ArrayList<>(nAtoms + 1));
849 
850       // Slot 0 contains the master tree information, children = all of the atoms.
851       GaussianOverlap overlap = new GaussianOverlap();
852       overlap.level = 0;
853       overlap.volume = 0;
854       overlap.dvv1 = 0.;
855       overlap.selfVolume = 0;
856       overlap.sfp = 1.;
857       overlap.gamma1i = 0.;
858       overlap.parentIndex = -1;
859       overlap.atom = -1;
860       overlap.childrenStartIndex = 1;
861       overlap.childrenCount = nAtoms;
862       overlaps.add(0, overlap);
863 
864       // List of atoms start at slot 1.
865       for (int iat = 0; iat < nAtoms; iat++) {
866         overlap = new GaussianOverlap();
867         double a = sphereConversion / (radii[iat] * radii[iat]);
868         double vol = ishydrogen[iat] ? 0 : volumes[iat];
869         overlap.level = 1;
870         overlap.g.v = vol;
871         overlap.g.a = a;
872         overlap.g.c = pos[iat];
873         overlap.volume = vol;
874         overlap.dvv1 = 1; // dVi/dVi
875         overlap.selfVolume = 0;
876         overlap.sfp = 1;
877         overlap.gamma1i = gammas[iat]; // gamma[iat] / SA_DR;
878         overlap.parentIndex = 0;
879         overlap.atom = iat;
880         overlap.childrenStartIndex = -1;
881         overlap.childrenCount = -1;
882         overlaps.add(iat + 1, overlap);
883       }
884     }
885 
886     /**
887      * Add Children.
888      *
889      * @param parentIndex      Parent index.
890      * @param childrenOverlaps Children overlaps.
891      * @return Index of the first added child.
892      */
893     int addChildren(int parentIndex, List<GaussianOverlap> childrenOverlaps) {
894 
895       // Adds children starting at the last slot
896       int startIndex = overlaps.size();
897 
898       int noverlaps = childrenOverlaps.size();
899 
900       // Retrieves address of root overlap
901       GaussianOverlap root = overlaps.get(parentIndex);
902 
903       // Registers list of children
904       root.childrenStartIndex = startIndex;
905       root.childrenCount = noverlaps;
906 
907       // Sort neighbors by overlap volume.
908       Collections.sort(childrenOverlaps);
909 
910       int rootLevel = root.level;
911       int nextLevel = rootLevel + 1;
912 
913       // Now copies the children overlaps from temp buffer.
914       for (GaussianOverlap child : childrenOverlaps) {
915         child.level = nextLevel;
916 
917         // Connect overlap to parent
918         child.parentIndex = parentIndex;
919 
920         // Reset its children indexes
921         child.childrenStartIndex = -1;
922         child.childrenCount = -1;
923 
924         // Add to tree.
925         overlaps.add(child);
926       }
927 
928       return startIndex;
929     }
930 
931     /**
932      * Scans the siblings of overlap identified by "rootIndex" to create children overlaps, returns
933      * them into the "childrenOverlaps" buffer: (root) + (atom) -> (root, atom)
934      *
935      * @param rootIndex        Root index.
936      * @param childrenOverlaps Children overlaps.
937      */
938     void computeChildren(int rootIndex, List<GaussianOverlap> childrenOverlaps) {
939       int parentIndex;
940       int siblingStart, siblingCount;
941 
942       // Reset output buffer
943       childrenOverlaps.clear();
944 
945       // Retrieves overlap.
946       GaussianOverlap root = overlaps.get(rootIndex);
947 
948       // Retrieves parent overlap.
949       parentIndex = root.parentIndex;
950 
951       // Master root? can't do computeChildren() on master root
952       if (parentIndex < 0) {
953         throw new IllegalArgumentException(" Cannot compute children of master node!");
954       }
955 
956       if (root.level >= MAX_ORDER) {
957         return; // Ignore overlaps above a certain order to cap computational cost.
958       }
959 
960       GaussianOverlap parent = overlaps.get(parentIndex);
961 
962       // Retrieves start index and count of siblings. Includes both younger and older siblings
963       siblingStart = parent.childrenStartIndex;
964       siblingCount = parent.childrenCount;
965 
966       // Parent is not initialized?
967       if (siblingStart < 0 || siblingCount < 0) {
968         throw new IllegalArgumentException(
969             format(" Parent %s of overlap %s has no sibilings.", parent, root));
970       }
971 
972       // This overlap somehow is not the child of registered parent.
973       if (rootIndex < siblingStart && rootIndex > siblingStart + siblingCount - 1) {
974         throw new IllegalArgumentException(
975             format(" Node %s is somehow not the child of its parent %s", root, parent));
976       }
977 
978       // Now loops over "younger" siblings (i<j loop) to compute new overlaps.
979       // Loop starts at the first younger sibling, and runs to the end of all siblings.
980       for (int slotj = rootIndex + 1; slotj < siblingStart + siblingCount; slotj++) {
981 
982         GaussianVca g12 = new GaussianVca();
983 
984         GaussianOverlap sibling = overlaps.get(slotj);
985         double gvol;
986         double[] dVdr = new double[1];
987         double[] dVdV = new double[1];
988         double[] sfp = new double[1];
989 
990         // Atomic gaussian of last atom of sibling.
991         int atom2 = sibling.atom;
992         GaussianVca g1 = root.g;
993 
994         // Atoms are stored in the tree at indexes 1...N
995         GaussianVca g2 = overlaps.get(atom2 + 1).g;
996         gvol = overlapGaussianAlpha(g1, g2, g12, dVdr, dVdV, sfp);
997 
998         /*
999          Create child if overlap volume is above a threshold.
1000          Due to infinite support, the Gaussian volume is never zero.
1001         */
1002         if (gvol > MIN_GVOL) {
1003           GaussianOverlap ov = new GaussianOverlap(g12, gvol, 0.0, atom2);
1004           // dv1 is the gradient of V(123..)n with respect to the position of 1
1005           // ov.dv1 = ( g2.c - g1.c ) * (-dVdr);
1006           sub(g2.c, g1.c, ov.dv1);
1007           scale(ov.dv1, -dVdr[0], ov.dv1);
1008 
1009           // dvv1 is the derivative of V(123...)n with respect to V(123...)
1010           ov.dvv1 = dVdV[0];
1011           ov.sfp = sfp[0];
1012           ov.gamma1i = root.gamma1i + overlaps.get(atom2 + 1).gamma1i;
1013           childrenOverlaps.add(ov);
1014         }
1015       }
1016     }
1017 
1018     /**
1019      * Grow the tree with more children starting at the given root slot (recursive).
1020      *
1021      * @param root The root index.
1022      */
1023     private void computeAndAddChildrenR(int root) {
1024       List<GaussianOverlap> childrenOverlaps = Collections.synchronizedList(new ArrayList<>());
1025       computeChildren(root, childrenOverlaps);
1026       int nOverlaps = childrenOverlaps.size();
1027       if (nOverlaps > 0) {
1028         int startSlot = addChildren(root, childrenOverlaps);
1029         for (int ichild = startSlot; ichild < startSlot + nOverlaps; ichild++) {
1030           computeAndAddChildrenR(ichild);
1031           totalNumberOfOverlaps++;
1032         }
1033       }
1034     }
1035 
1036     /**
1037      * Compute the overlap tree.
1038      *
1039      * @param pos        Atomic positions.
1040      * @param radii      Atomic radii.
1041      * @param volumes    Atomic volumes.
1042      * @param gammas     Atomic surface tensions.
1043      * @param ishydrogen True if the atom is a hydrogen.
1044      */
1045     void computeOverlapTreeR(
1046         double[][] pos, double[] radii, double[] volumes, double[] gammas, boolean[] ishydrogen) {
1047       initOverlapTree(pos, radii, volumes, gammas, ishydrogen);
1048       for (int slot = 1; slot <= nAtoms; slot++) {
1049         computeAndAddChildrenR(slot);
1050         totalNumberOfOverlaps++;
1051       }
1052     }
1053 
1054     /**
1055      * Compute volumes, energy of the overlap at slot and calls itself recursively to get the volumes
1056      * of the children.
1057      *
1058      * @param slot       Slot to begin from.
1059      * @param psi1i      Subtree accumulator for free volume.
1060      * @param f1i        Subtree accumulator for free volume.
1061      * @param p1i        Subtree accumulator for free volume.
1062      * @param psip1i     Subtree accumulator for self volume.
1063      * @param fp1i       Subtree accumulators for self volume.
1064      * @param pp1i       Subtree accumulators for self volume.
1065      * @param energy1i   Subtree accumulator for volume-based energy.
1066      * @param fenergy1i  Subtree accumulator for volume-based energy.
1067      * @param penergy1i  subtree accumulator for volume-based energy.
1068      * @param threadID   Thread for accumulation.
1069      * @param dr         Gradient of volume-based energy wrt to atomic positions.
1070      * @param dv         Gradient of volume-based energy wrt to atomic volumes.
1071      * @param freeVolume Atomic free volumes.
1072      * @param selfVolume Atomic self volumes.
1073      */
1074     void computeVolumeUnderSlot2R(
1075         int slot,
1076         double[] psi1i,
1077         double[] f1i,
1078         double[] p1i,
1079         double[] psip1i,
1080         double[] fp1i,
1081         double[] pp1i,
1082         double[] energy1i,
1083         double[] fenergy1i,
1084         double[] penergy1i,
1085         int threadID,
1086         AtomicDoubleArray3D dr,
1087         AtomicDoubleArray dv,
1088         AtomicDoubleArray freeVolume,
1089         AtomicDoubleArray selfVolume) {
1090 
1091       GaussianOverlap ov = overlaps.get(slot);
1092       // Keep track of overlap depth for each overlap.
1093       // If a new depth is greater than previous greatest, save depth in maximumDepth
1094       if (ov.level >= maximumDepth) {
1095         maximumDepth = ov.level;
1096       }
1097 
1098       // Whether to add volumes (e.g. selfs, 3-body overlaps) or subtract them (e.g. 2-body
1099       // overlaps, 4-body overlaps)
1100       double cf = ov.level % 2 == 0 ? -1.0 : 1.0;
1101       // Overall volume is increased/decreased by the full volume.
1102       double volcoeff = ov.level > 0 ? cf : 0;
1103       // Atomic contributions to overlap volume are evenly distributed.
1104       double volcoeffp = ov.level > 0 ? volcoeff / (double) ov.level : 0;
1105 
1106       int atom = ov.atom;
1107       double ai = overlaps.get(atom + 1).g.a;
1108       double a1i = ov.g.a;
1109       double a1 = a1i - ai;
1110 
1111       // For free volumes
1112       psi1i[0] = volcoeff * ov.volume;
1113       f1i[0] = volcoeff * ov.sfp;
1114 
1115       // For self volumes
1116       psip1i[0] = volcoeffp * ov.volume;
1117       fp1i[0] = volcoeffp * ov.sfp;
1118 
1119       // EV energy
1120       energy1i[0] = volcoeffp * ov.gamma1i * ov.volume;
1121       fenergy1i[0] = volcoeffp * ov.sfp * ov.gamma1i;
1122 
1123       // Loop over children.
1124       if (ov.childrenStartIndex >= 0) {
1125         for (int sloti = ov.childrenStartIndex;
1126              sloti < ov.childrenStartIndex + ov.childrenCount;
1127              sloti++) {
1128           double[] psi1it = new double[1];
1129           double[] f1it = new double[1];
1130           double[] p1it = new double[3];
1131           double[] psip1it = new double[1];
1132           double[] fp1it = new double[1];
1133           double[] pp1it = new double[3];
1134           double[] energy1it = new double[1];
1135           double[] fenergy1it = new double[1];
1136           double[] penergy1it = new double[3];
1137           computeVolumeUnderSlot2R(
1138               sloti,
1139               psi1it,
1140               f1it,
1141               p1it,
1142               psip1it,
1143               fp1it,
1144               pp1it,
1145               energy1it,
1146               fenergy1it,
1147               penergy1it,
1148               threadID,
1149               dr,
1150               dv,
1151               freeVolume,
1152               selfVolume);
1153           psi1i[0] += psi1it[0];
1154           f1i[0] += f1it[0];
1155           add(p1i, p1it, p1i);
1156 
1157           psip1i[0] += psip1it[0];
1158           fp1i[0] += fp1it[0];
1159           add(pp1i, pp1it, pp1i);
1160 
1161           energy1i[0] += energy1it[0];
1162           fenergy1i[0] += fenergy1it[0];
1163           add(penergy1i, penergy1it, penergy1i);
1164         }
1165       }
1166 
1167       // Skip this for the Root Level.
1168       if (ov.level > 0) {
1169         // Contributions to free and self volume of last atom
1170         freeVolume.add(threadID, atom, psi1i[0]);
1171         selfVolume.add(threadID, atom, psip1i[0]);
1172 
1173         // Contributions to energy gradients
1174         double c2 = ai / a1i;
1175 
1176         // dr[atom] += (-ov.dv1) * fenergy1i + penergy1i * c2;
1177         double[] work1 = new double[3];
1178         double[] work2 = new double[3];
1179         double[] work3 = new double[3];
1180         scale(penergy1i, c2, work1);
1181         scale(ov.dv1, -fenergy1i[0], work2);
1182         add(work1, work2, work3);
1183         dr.add(threadID, atom, work3[0], work3[1], work3[2]);
1184 
1185         // ov.g.v is the unswitched volume
1186         dv.add(threadID, atom, ov.g.v * fenergy1i[0]);
1187 
1188         // Update subtree P1..i's for parent
1189         c2 = a1 / a1i;
1190 
1191         // p1i = (ov.dv1) * f1i + p1i * c2;
1192         scale(ov.dv1, f1i[0], work1);
1193         scale(pp1i, c2, work2);
1194         add(work1, work2, p1i);
1195 
1196         // pp1i = (ov.dv1) * fp1i + pp1i * c2;
1197         scale(ov.dv1, fp1i[0], work1);
1198         scale(pp1i, c2, work2);
1199         add(work1, work2, pp1i);
1200 
1201         // penergy1i = (ov.dv1) * fenergy1i + penergy1i * c2;
1202         scale(ov.dv1, fenergy1i[0], work1);
1203         scale(penergy1i, c2, work2);
1204         add(work1, work2, penergy1i);
1205 
1206         // Update subtree F1..i's for parent
1207         f1i[0] = ov.dvv1 * f1i[0];
1208         fp1i[0] = ov.dvv1 * fp1i[0];
1209         fenergy1i[0] = ov.dvv1 * fenergy1i[0];
1210       }
1211     }
1212 
1213     /**
1214      * Recursively traverses tree and computes volumes, etc.
1215      *
1216      * @param volume     Volume.
1217      * @param energy     Energy.
1218      * @param dr         Coordinate derivatives.
1219      * @param dv         Volume derivatives.
1220      * @param freeVolume Free volume.
1221      * @param selfvolume Self volume.
1222      */
1223     void computeVolume2R(
1224         SharedDouble volume,
1225         SharedDouble energy,
1226         AtomicDoubleArray3D dr,
1227         AtomicDoubleArray dv,
1228         AtomicDoubleArray freeVolume,
1229         AtomicDoubleArray selfvolume) {
1230 
1231       double[] psi1i = new double[1];
1232       double[] f1i = new double[1];
1233       double[] p1i = new double[3];
1234       double[] psip1i = new double[1];
1235       double[] fp1i = new double[1];
1236       double[] pp1i = new double[3];
1237       double[] energy1i = new double[1];
1238       double[] fenergy1i = new double[1];
1239       double[] penergy1i = new double[3];
1240       // Only one thread for serial computation.
1241       int threadID = 0;
1242 
1243       computeVolumeUnderSlot2R(
1244           0,
1245           psi1i,
1246           f1i,
1247           p1i,
1248           psip1i,
1249           fp1i,
1250           pp1i,
1251           energy1i,
1252           fenergy1i,
1253           penergy1i,
1254           threadID,
1255           dr,
1256           dv,
1257           freeVolume,
1258           selfvolume);
1259 
1260       volume.addAndGet(psi1i[0]);
1261       energy.addAndGet(energy1i[0]);
1262     }
1263 
1264     /**
1265      * Rescan the tree to recompute the volumes. It does not modify the tree.
1266      *
1267      * @param pos        Atomic positions.
1268      * @param radii      Atomic radii.
1269      * @param volumes    Atomic volumes.
1270      * @param gammas     Atomic surface tensions.
1271      * @param ishydrogen True if the atom is a hydrogen.
1272      */
1273     void rescanTreeV(
1274         double[][] pos, double[] radii, double[] volumes, double[] gammas, boolean[] ishydrogen) {
1275       initRescanTreeV(pos, radii, volumes, gammas, ishydrogen);
1276       rescanR(0);
1277     }
1278 
1279     /**
1280      * Rescan the sub-tree to recompute the volumes. It does not modify the tree.
1281      *
1282      * @param slot The slot to begin from.
1283      */
1284     void rescanR(int slot) {
1285       int parentIndex;
1286 
1287       // This overlap.
1288       GaussianOverlap ov = overlaps.get(slot);
1289 
1290       // Recompute its own overlap by merging parent and last atom.
1291       parentIndex = ov.parentIndex;
1292       if (parentIndex > 0) {
1293         GaussianVca g12 = new GaussianVca();
1294         double[] dVdr = new double[1];
1295         double[] dVdV = new double[1];
1296         double[] sfp = new double[1];
1297 
1298         int atom = ov.atom;
1299         GaussianOverlap parent = overlaps.get(parentIndex);
1300         GaussianVca g1 = parent.g;
1301 
1302         // Atoms are stored in the tree at indexes 1...N
1303         GaussianVca g2 = overlaps.get(atom + 1).g;
1304         double gvol = overlapGaussianAlpha(g1, g2, g12, dVdr, dVdV, sfp);
1305         ov.g = g12;
1306         ov.volume = gvol;
1307 
1308         // dv1 is the gradient of V(123..)n with respect to the position of 1
1309         // ov.dv1 = ( g2.c - g1.c ) * (-dVdr);
1310         sub(g2.c, g1.c, ov.dv1);
1311         scale(ov.dv1, -dVdr[0], ov.dv1);
1312 
1313         // dvv1 is the derivative of V(123...)n with respect to V(123...)
1314         ov.dvv1 = dVdV[0];
1315         ov.sfp = sfp[0];
1316         ov.gamma1i = parent.gamma1i + overlaps.get(atom + 1).gamma1i;
1317       }
1318 
1319       // Calls itself recursively on the children.
1320       for (int slotChild = ov.childrenStartIndex;
1321            slotChild < ov.childrenStartIndex + ov.childrenCount;
1322            slotChild++) {
1323         rescanR(slotChild);
1324       }
1325     }
1326 
1327     /**
1328      * Init rescan of the tree to recompute the volumes. It does not modify the tree.
1329      *
1330      * @param pos        Atomic coodinates.
1331      * @param radii      Atomic radii.
1332      * @param volumes    Atomic volumes.
1333      * @param gammas     Atomic surface tensions.
1334      * @param ishydrogen True if the atom is a hydrogen.
1335      */
1336     void initRescanTreeV(
1337         double[][] pos, double[] radii, double[] volumes, double[] gammas, boolean[] ishydrogen) {
1338       int slot = 0;
1339       GaussianOverlap ov = overlaps.get(slot);
1340       ov.level = 0;
1341       ov.volume = 0.0;
1342       ov.dv1 = new double[3];
1343       ov.dvv1 = 0.0;
1344       ov.selfVolume = 0.0;
1345       ov.sfp = 1.0;
1346       ov.gamma1i = 0.0;
1347 
1348       slot = 1;
1349       for (int iat = 0; iat < nAtoms; iat++, slot++) {
1350         double a = sphereConversion / (radii[iat] * radii[iat]);
1351         double vol = ishydrogen[iat] ? 0.0 : volumes[iat];
1352         ov = overlaps.get(slot);
1353         ov.level = 1;
1354         ov.g.v = vol;
1355         ov.g.a = a;
1356         ov.g.c = pos[iat];
1357         ov.volume = vol;
1358         ov.dv1 = new double[3];
1359         ov.dvv1 = 1.0; // dVi/dVi
1360         ov.selfVolume = 0.0;
1361         ov.sfp = 1.0;
1362         ov.gamma1i = gammas[iat]; // gamma[iat]/SA_DR
1363       }
1364     }
1365 
1366     /**
1367      * Rescan the sub-tree to recompute the gammas. It does not modify the volumes nor the tree.
1368      *
1369      * @param slot Slot to begin from.
1370      */
1371     void rescanGammaR(int slot) {
1372 
1373       // This overlap.
1374       GaussianOverlap go = overlaps.get(slot);
1375 
1376       // Recompute its own overlap by merging parent and last atom.
1377       int parentIndex = go.parentIndex;
1378       if (parentIndex > 0) {
1379         int atom = go.atom;
1380         GaussianOverlap parent = overlaps.get(parentIndex);
1381         go.gamma1i = parent.gamma1i + overlaps.get(atom + 1).gamma1i;
1382       }
1383 
1384       // Calls itself recursively on the children.
1385       for (int slotChild = go.childrenStartIndex;
1386            slotChild < go.childrenStartIndex + go.childrenCount;
1387            slotChild++) {
1388         rescanGammaR(slotChild);
1389       }
1390     }
1391 
1392     /**
1393      * Rescan the tree to recompute the gammas only. It does not modify volumes and the tree.
1394      *
1395      * @param gammas Gamma values.
1396      */
1397     void rescanTreeG(double[] gammas) {
1398 
1399       int slot = 0;
1400       GaussianOverlap ov = overlaps.get(slot);
1401       ov.gamma1i = 0.;
1402 
1403       slot = 1;
1404       for (int iat = 0; iat < nAtoms; iat++, slot++) {
1405         ov = overlaps.get(slot);
1406         ov.gamma1i = gammas[iat];
1407       }
1408 
1409       rescanGammaR(0);
1410     }
1411 
1412     /**
1413      * Print the contents of the tree.
1414      */
1415     void printTree() {
1416       // logger.info("slot level LastAtom parent ChStart ChCount SelfV V gamma a x y z dedx dedy
1417       // dedz sfp");
1418       for (int i = 1; i <= nAtoms; i++) {
1419         printTreeR(i);
1420       }
1421     }
1422 
1423     /**
1424      * Print the contents of the tree (recursive).
1425      *
1426      * @param slot Slot to begin from.
1427      */
1428     void printTreeR(int slot) {
1429       GaussianOverlap ov = overlaps.get(slot);
1430       logger.info(format("tg:      %d ", slot));
1431       ov.printOverlap();
1432       for (int i = ov.childrenStartIndex; i < ov.childrenStartIndex + ov.childrenCount; i++) {
1433         printTreeR(i);
1434       }
1435     }
1436 
1437     /**
1438      * Counts number of overlaps under the one given.
1439      *
1440      * @param slot Slot to begin from.
1441      * @return Number of overlaps.
1442      */
1443     int nChildrenUnderSlotR(int slot) {
1444       int n = 0;
1445       if (overlaps.get(slot).childrenCount > 0) {
1446         n += overlaps.get(slot).childrenCount;
1447         // now calls itself on the children
1448         for (int i = 0; i < overlaps.get(slot).childrenCount; i++) {
1449           n += nChildrenUnderSlotR(overlaps.get(slot).childrenStartIndex + i);
1450         }
1451       }
1452       return n;
1453     }
1454   }
1455 
1456   /**
1457    * Use instances of the GaussianOverlapTree to compute the GaussVol volume in parallel.
1458    */
1459   private class GaussVolRegion extends ParallelRegion {
1460 
1461     private final InitGaussVol[] initGaussVol;
1462     private final GaussianOverlapTree[] localTree;
1463     private final ComputeTreeLoop[] computeTreeLoops;
1464     private final ComputeVolumeLoop[] computeVolumeLoops;
1465     private final RescanTreeLoop[] rescanTreeLoops;
1466     private final ReductionLoop[] reductionLoops;
1467     private GAUSSVOL_MODE mode = GAUSSVOL_MODE.COMPUTE_TREE;
1468     private double[][] coordinates = null;
1469 
1470     /**
1471      * GaussVolRegion constructor.
1472      *
1473      * @param nThreads Number of threads.
1474      */
1475     public GaussVolRegion(int nThreads) {
1476       initGaussVol = new InitGaussVol[nThreads];
1477       localTree = new GaussianOverlapTree[nThreads];
1478       computeTreeLoops = new ComputeTreeLoop[nThreads];
1479       computeVolumeLoops = new ComputeVolumeLoop[nThreads];
1480       rescanTreeLoops = new RescanTreeLoop[nThreads];
1481       reductionLoops = new ReductionLoop[nThreads];
1482     }
1483 
1484     public void init(GAUSSVOL_MODE mode, double[][] coordinates) {
1485       this.mode = mode;
1486       this.coordinates = coordinates;
1487 
1488       // Initialize output variables.
1489       totalVolume.set(0.0);
1490       energy.set(0.0);
1491       grad.reset(parallelTeam);
1492       gradV.reset(parallelTeam, 0, nAtoms - 1);
1493       freeVolume.reset(parallelTeam, 0, nAtoms - 1);
1494       selfVolume.reset(parallelTeam, 0, nAtoms - 1);
1495     }
1496 
1497     @Override
1498     public void run() throws Exception {
1499       int threadIndex = getThreadIndex();
1500       if (computeTreeLoops[threadIndex] == null) {
1501         initGaussVol[threadIndex] = new InitGaussVol();
1502         localTree[threadIndex] = new GaussianOverlapTree(nAtoms);
1503         computeTreeLoops[threadIndex] = new ComputeTreeLoop();
1504         computeVolumeLoops[threadIndex] = new ComputeVolumeLoop();
1505         rescanTreeLoops[threadIndex] = new RescanTreeLoop();
1506         reductionLoops[threadIndex] = new ReductionLoop();
1507       }
1508       try {
1509         if (mode == GAUSSVOL_MODE.COMPUTE_TREE) {
1510           execute(0, nAtoms - 1, initGaussVol[threadIndex]);
1511           execute(1, nAtoms, computeTreeLoops[threadIndex]);
1512           execute(1, nAtoms, computeVolumeLoops[threadIndex]);
1513           execute(0, nAtoms - 1, reductionLoops[threadIndex]);
1514         } else {
1515           execute(1, nAtoms, rescanTreeLoops[threadIndex]);
1516           execute(1, nAtoms, computeVolumeLoops[threadIndex]);
1517           execute(0, nAtoms - 1, reductionLoops[threadIndex]);
1518         }
1519       } catch (RuntimeException ex) {
1520         logger.warning("Runtime exception computing tree in thread: " + threadIndex);
1521         throw ex;
1522       } catch (Exception e) {
1523         String message = "Fatal exception computing tree in thread: " + threadIndex + "\n";
1524         logger.log(Level.SEVERE, message, e);
1525       }
1526     }
1527 
1528     private class InitGaussVol extends IntegerForLoop {
1529 
1530       @Override
1531       public void run(int first, int last) throws Exception {
1532         for (int i = first; i <= last; i++) {
1533           // Update the radius and volume for each atom.
1534           updateAtom(i);
1535         }
1536       }
1537     }
1538 
1539     /**
1540      * Initialize the Overlap tree for a subset of the system.
1541      */
1542     private class ComputeTreeLoop extends IntegerForLoop {
1543 
1544       @Override
1545       public void run(int first, int last) throws Exception {
1546         // Compute the overlaps for a subset of atoms.
1547         int threadIndex = getThreadIndex();
1548         for (int slot = first; slot <= last; slot++) {
1549           localTree[threadIndex].computeAndAddChildrenR(slot);
1550         }
1551       }
1552 
1553       @Override
1554       public void start() {
1555         localTree[getThreadIndex()].initOverlapTree(
1556             coordinates, radii, volumes, gammas, ishydrogen);
1557       }
1558     }
1559 
1560     /**
1561      * Compute the volume and derivatives for a subset of the system.
1562      */
1563     private class ComputeVolumeLoop extends IntegerForLoop {
1564 
1565       @Override
1566       public void run(int first, int last) throws Exception {
1567         int threadIndex = getThreadIndex();
1568         for (int slot = first; slot <= last; slot++) {
1569           double[] psi1i = new double[1];
1570           double[] f1i = new double[1];
1571           double[] p1i = new double[3];
1572           double[] psip1i = new double[1];
1573           double[] fp1i = new double[1];
1574           double[] pp1i = new double[3];
1575           double[] energy1i = new double[1];
1576           double[] fenergy1i = new double[1];
1577           double[] penergy1i = new double[3];
1578           localTree[threadIndex].computeVolumeUnderSlot2R(
1579               slot,
1580               psi1i,
1581               f1i,
1582               p1i,
1583               psip1i,
1584               fp1i,
1585               pp1i,
1586               energy1i,
1587               fenergy1i,
1588               penergy1i,
1589               threadIndex,
1590               grad,
1591               gradV,
1592               freeVolume,
1593               selfVolume);
1594           totalVolume.addAndGet(psi1i[0]);
1595           energy.addAndGet(energy1i[0]);
1596         }
1597       }
1598     }
1599 
1600     /**
1601      * Rescan the tree based on updated radii and volumes for a subset of the system.
1602      */
1603     private class RescanTreeLoop extends IntegerForLoop {
1604 
1605       @Override
1606       public void run(int first, int last) throws Exception {
1607         // Compute the overlaps for a subset of atoms.
1608         int threadIndex = getThreadIndex();
1609         for (int slot = first; slot <= last; slot++) {
1610           localTree[threadIndex].rescanR(slot);
1611         }
1612       }
1613 
1614       @Override
1615       public void start() {
1616         localTree[getThreadIndex()].initRescanTreeV(
1617             coordinates, radii, volumes, gammas, ishydrogen);
1618       }
1619     }
1620 
1621     /**
1622      * Reduce GaussVol gradient.
1623      */
1624     private class ReductionLoop extends IntegerForLoop {
1625 
1626       int threadID;
1627 
1628       @Override
1629       public void run(int lb, int ub) {
1630         grad.reduce(lb, ub);
1631         gradV.reduce(lb, ub);
1632         freeVolume.reduce(lb, ub);
1633         selfVolume.reduce(lb, ub);
1634         for (int i = lb; i <= ub; i++) {
1635           if (volumes[i] > 0) {
1636             gradV.set(0, i, gradV.get(i) / volumes[i]);
1637           }
1638         }
1639       }
1640 
1641       @Override
1642       public void start() {
1643         threadID = getThreadIndex();
1644       }
1645     }
1646   }
1647 }