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