ffx.potential.nonbonded.GeneralizedKirkwood.java Source code

Java tutorial

Introduction

Here is the source code for ffx.potential.nonbonded.GeneralizedKirkwood.java

Source

/**
 * Title: Force Field X.
 *
 * Description: Force Field X - Software for Molecular Biophysics.
 *
 * Copyright: Copyright (c) Michael J. Schnieders 2001-2017.
 *
 * This file is part of Force Field X.
 *
 * Force Field X is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3 as published by
 * the Free Software Foundation.
 *
 * Force Field X is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Linking this library statically or dynamically with other modules is making a
 * combined work based on this library. Thus, the terms and conditions of the
 * GNU General Public License cover the whole combination.
 *
 * As a special exception, the copyright holders of this library give you
 * permission to link this library with independent modules to produce an
 * executable, regardless of the license terms of these independent modules, and
 * to copy and distribute the resulting executable under terms of your choice,
 * provided that you also meet, for each linked independent module, the terms
 * and conditions of the license of that module. An independent module is a
 * module which is not derived from or based on this library. If you modify this
 * library, you may extend this exception to your version of the library, but
 * you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 */
package ffx.potential.nonbonded;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.lang.String.format;
import static java.util.Arrays.fill;

import static org.apache.commons.math3.util.FastMath.PI;
import static org.apache.commons.math3.util.FastMath.abs;
import static org.apache.commons.math3.util.FastMath.acos;
import static org.apache.commons.math3.util.FastMath.asin;
import static org.apache.commons.math3.util.FastMath.atan2;
import static org.apache.commons.math3.util.FastMath.cos;
import static org.apache.commons.math3.util.FastMath.exp;
import static org.apache.commons.math3.util.FastMath.max;
import static org.apache.commons.math3.util.FastMath.min;
import static org.apache.commons.math3.util.FastMath.pow;
import static org.apache.commons.math3.util.FastMath.sin;
import static org.apache.commons.math3.util.FastMath.sqrt;
import static org.apache.commons.math3.util.FastMath.tanh;

import edu.rit.pj.IntegerForLoop;
import edu.rit.pj.ParallelRegion;
import edu.rit.pj.ParallelTeam;
import edu.rit.pj.reduction.DoubleOp;
import edu.rit.pj.reduction.SharedBooleanArray;
import edu.rit.pj.reduction.SharedDouble;
import edu.rit.pj.reduction.SharedDoubleArray;
import edu.rit.pj.reduction.SharedInteger;

import ffx.crystal.Crystal;
import ffx.numerics.VectorMath;
import ffx.potential.bonded.Angle;
import ffx.potential.bonded.Atom;
import ffx.potential.bonded.Bond;
import ffx.potential.bonded.LambdaInterface;
import ffx.potential.bonded.Torsion;
import ffx.potential.nonbonded.ParticleMeshEwald.Polarization;
import ffx.potential.parameters.AtomType;
import ffx.potential.parameters.BioType;
import ffx.potential.parameters.ForceField;
import ffx.potential.parameters.ISolvRadType;
import ffx.potential.parameters.SolventRadii;
import ffx.potential.parameters.VDWType;
import ffx.potential.utils.EnergyException;

import static ffx.potential.parameters.MultipoleType.ELECTRIC;
import static ffx.potential.parameters.MultipoleType.t000;
import static ffx.potential.parameters.MultipoleType.t001;
import static ffx.potential.parameters.MultipoleType.t002;
import static ffx.potential.parameters.MultipoleType.t010;
import static ffx.potential.parameters.MultipoleType.t011;
import static ffx.potential.parameters.MultipoleType.t020;
import static ffx.potential.parameters.MultipoleType.t100;
import static ffx.potential.parameters.MultipoleType.t101;
import static ffx.potential.parameters.MultipoleType.t110;
import static ffx.potential.parameters.MultipoleType.t200;

/**
 * This Generalized Kirkwood class implements GK for the AMOEBA polarizable
 * mutlipole force field in parallel using a {@link NeighborList}.
 *
 * @author Michael J. Schnieders<br> derived from:<br> TINKER code by Michael J.
 * Schnieders and Jay W. Ponder<br>
 * @see <a href="http://dx.doi.org/10.1021/ct7001336" target="_blank">M. J.
 * Schnieders and J. W. Ponder, Polarizable atomic multipole solutes in a
 * generalized Kirkwood continuum, Journal of Chemical Theory and Computation
 * 2007, 3, (6), 2083-2097.</a><br>
 *
 */
public class GeneralizedKirkwood implements LambdaInterface {

    private static final Logger logger = Logger.getLogger(GeneralizedKirkwood.class.getName());

    /**
     * Permittivity of water at STP.
     */
    private static final double dWater = 78.3;

    /**
     * Set of force fields for which we have fitted GK/GB radii.
     */
    private static final Set<String> fittedForceFields;
    static {
        fittedForceFields = new HashSet<>();
        String[] fitted = { "AMOEBA-PROTEIN-2013", "AMBER99SB" };
        fittedForceFields.addAll(Arrays.asList(fitted));
    }

    /**
     * Default bondi scale factor.
     */
    private static final double DEFAULT_BONDI_SCALE = 1.15;

    /**
     * The requested permittivity.
     */
    private double epsilon = dWater;
    /**
     * Kirkwood multipolar reaction field constants.
     */
    //    private static final double fc = 1.0 * (1.0 - dWater) / (0.0 + 1.0 * dWater);
    //    private static final double fd = 2.0 * (1.0 - dWater) / (1.0 + 2.0 * dWater);
    //    private static final double fq = 3.0 * (1.0 - dWater) / (2.0 + 3.0 * dWater);
    private final double fc;
    private final double fd;
    private final double fq;
    /**
     * Empirical constant that controls the GK cross-term.
     */
    private static final double gkc = 2.455;
    /**
     * Empirical scaling of the Bondi radii.
     */
    private final double bondiScale;
    /**
     * Cavitation surface tension coefficient (kcal/mol/A^2).
     */
    private final double surfaceTension;

    private final double bornaiTerm;
    private final double probe;
    private boolean use[] = null;
    private final Polarization polarization;
    private Atom atoms[];
    private double x[], y[], z[];
    private double globalMultipole[][];
    private double inducedDipole[][];
    private double inducedDipoleCR[][];
    private double baseRadius[];
    private double baseRadiusWithBondi[];
    private double overlapScale[];
    private double rDisp[];
    private double born[];
    private int nAtoms;
    /**
     * This field is because re-initializing the force field resizes some arrays
     * but not others; that second category must, when called on, be resized not
     * to the current number of atoms but to the maximum number of atoms (and
     * thus to the size of the first category of arrays).
     */
    private int maxNumAtoms;
    private final ParticleMeshEwald particleMeshEwald;
    private final ParallelTeam parallelTeam;
    private final Crystal crystal;
    private final BornRadiiRegion bornRadiiRegion;
    private final PermanentGKFieldRegion permanentGKFieldRegion;
    private final InducedGKFieldRegion inducedGKFieldRegion;
    private final GKEnergyRegion gkEnergyRegion;
    private final BornCRRegion bornGradRegion;
    private final DispersionRegion dispersionRegion;
    private final CavitationRegion cavitationRegion;

    /**
     * Gradient array for each thread.
     */
    private double grad[][][];
    /**
     * Torque array for each thread.
     */
    private double torque[][][];
    /**
     * Lambda gradient array for each thread (dU/dX/dL)
     */
    private double lambdaGrad[][][];
    /**
     * Lambda torque array for each thread.
     */
    private double lambdaTorque[][][];
    private int neighborLists[][][];
    private SharedDoubleArray sharedBornGrad;
    protected SharedDoubleArray sharedGKField[];
    protected SharedDoubleArray sharedGKFieldCR[];
    private final double cutoff;
    private final double cut2;
    private final NonPolar nonPolar;

    private double lambda = 1.0;
    private double solvationEnergy = 0.0;
    private boolean lambdaTerm = false;

    private long gkTime = 0;
    private long pmfTime = 0;
    private long dispersionTime = 0;
    private long cavitationTime = 0;
    private double dispersionEnergy = 0.0;
    private double cavitationEnergy = 0.0;
    /**
     * Use base radii defined by AtomType rather than by atomic number.
     */
    private boolean verboseRadii = false;
    /**
     * If true, prevents Born radii from updating.
     */
    private boolean fixedRadii = false;
    /**
     * Forces all atoms to be considered during Born radius updates.
     */
    private boolean bornUseAll = false;
    /**
     * Provides maps from atomtypes or biotypes to fitted GK radii (by
     * forcefield).
     */
    private boolean useFittedRadii;
    private SolventRadii solventRadii;
    private RADII_MAP_TYPE radiiMapType = RADII_MAP_TYPE.ATOMTYPE;
    /**
     * Maps radii overrides (by AtomType) specified from the command line. e.g.
     * -DradiiOverride=134r1.20,135r1.20 sets atom types 134,135 to Bondi=1.20
     */
    private final HashMap<Integer, Double> radiiOverride = new HashMap<>();
    /**
     * Maps radii overrides (by atom number) specified from the command line.
     * This takes precendence over AtomType-based overrides. e.g.
     * -DradiiOverride=1r1.20,5r1.20 sets atom numbers 1,5 to Bondi=1.20
     */
    private final HashMap<Integer, Double> radiiByNumberMap = new HashMap<>();
    private final ForceField forceField;

    private static final Level GK_WARN_LEVEL;
    static {
        String suppressGKwarnings = System.getProperty("gk-suppressWarnings");
        if (suppressGKwarnings != null && Boolean.parseBoolean(suppressGKwarnings)) {
            GK_WARN_LEVEL = Level.FINE;
        } else {
            GK_WARN_LEVEL = Level.WARNING;
        }
    }

    /**
     * <p>
     * Constructor for GeneralizedKirkwood.</p>
     *
     * @param forceField a {@link ffx.potential.parameters.ForceField} object.
     * @param atoms an array of {@link ffx.potential.bonded.Atom} objects.
     * @param particleMeshEwald a
     * {@link ffx.potential.nonbonded.ParticleMeshEwald} object.
     * @param crystal a {@link ffx.crystal.Crystal} object.
     * @param parallelTeam a {@link edu.rit.pj.ParallelTeam} object.
     */
    public GeneralizedKirkwood(ForceField forceField, Atom[] atoms, ParticleMeshEwald particleMeshEwald,
            Crystal crystal, ParallelTeam parallelTeam) {

        this.forceField = forceField;
        String forcefieldName = forceField.getString(ForceField.ForceFieldString.FORCEFIELD,
                ForceField.ForceFieldName.AMOEBA_BIO_2009.toString());
        forcefieldName = forcefieldName.replaceAll("_", "-");
        boolean doUseFitRadii = forceField.getBoolean(ForceField.ForceFieldBoolean.GK_USEFITRADII, true);
        boolean hasFittedRadii = fittedForceFields.contains(forcefieldName.toUpperCase());

        if (doUseFitRadii) {
            if (hasFittedRadii) {
                useFittedRadii = true;
                solventRadii = new SolventRadii(forcefieldName, forceField);
            }
        } else if (hasFittedRadii) {
            logger.log(Level.INFO, String.format(" (GK) Ignoring fitted radii for force field %s", forcefieldName));
        }

        boolean vRadii = forceField.getBoolean(ForceField.ForceFieldBoolean.GK_VERBOSERADII, false);
        if (vRadii) {
            logger.info(" (GK) Verbose radii enabled.");
        }
        verboseRadii = vRadii;

        try {
            this.epsilon = forceField.getDouble(ForceField.ForceFieldDouble.GK_EPSILON);
            logger.info(format(" (GK) GLOBAL dielectric constant set to %.2f", epsilon));
        } catch (Exception e) {
            this.epsilon = dWater;
        }

        double bondiScaleValue;
        try {
            bondiScaleValue = forceField.getDouble(ForceField.ForceFieldDouble.GK_BONDIOVERRIDE);
            logger.info(format(" (GK) Scaling GLOBAL bondi radii by factor: %.2f", bondiScaleValue));
        } catch (Exception ex) {
            bondiScaleValue = useFittedRadii ? solventRadii.getDefaultBondi() : DEFAULT_BONDI_SCALE;
            if (verboseRadii) {
                logger.info(format(" (GK) Scaling default GLOBAL bondi radii by factor: %.2f", bondiScaleValue));
            }
        }
        bondiScale = bondiScaleValue;

        /*String bondiOverride = System.getProperty("gk-bondiOverride");
        if (bondiOverride != null) {
        bondiScale = Double.parseDouble(bondiOverride);
        logger.info(format(" (GK) Scaling GLOBAL bondi radii by factor: %.2f", bondiScale));
        } else {
        if (useFittedRadii) {
            bondiScale = solventRadii.getDefaultBondi();
        } else {
            bondiScale = 1.15;
        }
        if (verboseRadii) {
            logger.info(format(" (GK) Scaling GLOBAL bondi radii by factor: %.2f", bondiScale));
        }
        }*/

        String radiiProp = forceField.getString(ForceField.ForceFieldString.GK_RADIIOVERRIDE, null);
        if (radiiProp != null) {
            String tokens[] = radiiProp.split(",");
            for (String token : tokens) {
                if (!token.contains("r")) {
                    logger.severe("Invalid radius override.");
                }
                int separator = token.indexOf("r");
                int type = Integer.parseInt(token.substring(0, separator));
                double factor = Double.parseDouble(token.substring(separator + 1));
                logger.info(format(" (GK) Scaling AtomType %d with bondi factor %.2f", type, factor));
                radiiOverride.put(type, factor);
            }
        }

        String radiiByNumber = forceField.getString(ForceField.ForceFieldString.GK_RADIIBYNUMBER, null);
        if (radiiByNumber != null) {
            String tokens[] = radiiByNumber.split(",");
            for (String token : tokens) {
                if (!token.contains("r")) {
                    logger.severe("Invalid radius override.");
                }
                int separator = token.indexOf("r");
                int num = Integer.parseInt(token.substring(0, separator));
                double factor = Double.parseDouble(token.substring(separator + 1));
                logger.info(format(" (GK) Scaling Atom Number %d with bondi factor %.2f", num, factor));
                radiiByNumberMap.put(num, factor);
            }
        }

        // Se the Kirkwood multipolar reaction field constants.
        fc = 1.0 * (1.0 - epsilon) / (0.0 + 1.0 * epsilon);
        fd = 2.0 * (1.0 - epsilon) / (1.0 + 2.0 * epsilon);
        fq = 3.0 * (1.0 - epsilon) / (2.0 + 3.0 * epsilon);

        this.parallelTeam = parallelTeam;
        nAtoms = atoms.length;
        maxNumAtoms = nAtoms;
        this.particleMeshEwald = particleMeshEwald;
        polarization = particleMeshEwald.polarization;
        neighborLists = particleMeshEwald.neighborLists;
        this.crystal = crystal;
        this.atoms = atoms;

        NonPolar nonpolarModel = NonPolar.CAV_DISP;
        try {
            String cavModel = forceField.getString(ForceField.ForceFieldString.CAVMODEL, "CAV_DISP").toUpperCase();
            nonpolarModel = getNonPolarModel(cavModel);
            if (nonpolarModel == NonPolar.NONE) {
                logger.info(" No non-polar term will be used.");
            }
        } catch (Exception ex) {
            nonpolarModel = NonPolar.NONE;
            logger.warning(format(" Error parsing non-polar model (set to NONE) %s", ex.toString()));
        }
        nonPolar = nonpolarModel;

        double aiTerm = 4.0 * PI;
        try {
            aiTerm *= forceField.getDouble(ForceField.ForceFieldDouble.BORNAI);
        } catch (Exception ex) {
            switch (nonPolar) {
            case BORN_SOLV:
                aiTerm *= 0.003; // Value from TINKER.
                break;
            case BORN_CAV_DISP:
                aiTerm *= 0.050; // Complete random guess.
                break;
            default:
                break;
            }
        }
        bornaiTerm = aiTerm;

        sharedGKField = new SharedDoubleArray[3];
        sharedGKFieldCR = new SharedDoubleArray[3];

        bornUseAll = forceField.getBoolean(ForceField.ForceFieldBoolean.BORN_USE_ALL, false);

        probe = forceField.getDouble(ForceField.ForceFieldDouble.PROBE_RADIUS, 1.4);

        cutoff = particleMeshEwald.getEwaldCutoff();
        cut2 = cutoff * cutoff;
        lambdaTerm = forceField.getBoolean(ForceField.ForceFieldBoolean.LAMBDATERM, false);

        initAtomArrays();

        /**
         * If polarization lambda exponent is set to 0.0, then we're running
         * Dual-Topology and the GK energy will be scaled with the overall
         * system lambda value.
         */
        double polLambdaExp = forceField.getDouble(ForceField.ForceFieldDouble.POLARIZATION_LAMBDA_EXPONENT, 1.0);
        if (polLambdaExp == 0.0) {
            lambdaTerm = false;
            logger.info(" GK lambda term set to false.");
        }

        int threadCount = parallelTeam.getThreadCount();
        bornRadiiRegion = new BornRadiiRegion(threadCount);
        permanentGKFieldRegion = new PermanentGKFieldRegion(threadCount);
        inducedGKFieldRegion = new InducedGKFieldRegion(threadCount);
        gkEnergyRegion = new GKEnergyRegion(threadCount);
        bornGradRegion = new BornCRRegion(threadCount);

        double tensionDefault = 0.08;
        switch (nonPolar) {
        case CAV:
            cavitationRegion = new CavitationRegion(threadCount);
            tensionDefault = 0.0049;
            dispersionRegion = null;
            break;
        case CAV_DISP:
            cavitationRegion = new CavitationRegion(threadCount);
            tensionDefault = 0.080;
            dispersionRegion = new DispersionRegion(threadCount);
            break;
        case BORN_CAV_DISP:
            cavitationRegion = null;
            dispersionRegion = new DispersionRegion(threadCount);
            break;
        case HYDROPHOBIC_PMF:
        case BORN_SOLV:
        case NONE:
        default:
            cavitationRegion = null;
            dispersionRegion = null;
            break;
        }

        surfaceTension = forceField.getDouble(ForceField.ForceFieldDouble.SURFACE_TENSION, tensionDefault);

        logger.info(" Continuum Solvation ");
        logger.info(format("  Generalized Kirkwood Cut-Off:        %8.3f (A)", cutoff));
        logger.info(format("  Solvent Dielectric:                  %8.3f", epsilon));
        logger.info(format("  Non-Polar Model:                     %8s", nonPolar.toString().replace('_', '-')));

        if (cavitationRegion != null) {
            logger.info(format("  Cavitation Probe Radius:             %8.3f (A)", probe));
            logger.info(format("  Cavitation Surface Tension:          %8.3f (Kcal/mol/A^2)", surfaceTension));
        }

        logger.info("");
    }

    public void setAtoms(Atom atoms[]) {
        this.atoms = atoms;
        nAtoms = atoms.length;
        maxNumAtoms = nAtoms > maxNumAtoms ? nAtoms : maxNumAtoms;
        initAtomArrays();
    }

    public void setFixedRadii(boolean fixedRadii) {
        this.fixedRadii = fixedRadii;
    }

    public boolean getFixedRadii() {
        return fixedRadii;
    }

    public void setBornUseAll(boolean bornUseAll) {
        this.bornUseAll = bornUseAll;
    }

    public boolean getBornUseAll() {
        return bornUseAll;
    }

    private void initAtomArrays() {
        if (fixedRadii) {
            fixedRadii = false;
        }
        x = particleMeshEwald.coordinates[0][0];
        y = particleMeshEwald.coordinates[0][1];
        z = particleMeshEwald.coordinates[0][2];
        globalMultipole = particleMeshEwald.globalMultipole[0];
        inducedDipole = particleMeshEwald.inducedDipole[0];
        inducedDipoleCR = particleMeshEwald.inducedDipoleCR[0];
        grad = particleMeshEwald.getGradient();
        torque = particleMeshEwald.getTorque();
        lambdaGrad = particleMeshEwald.getLambdaGradient();
        lambdaTorque = particleMeshEwald.getLambdaTorque();

        if (sharedGKField[0] == null || sharedGKField[0].length() < nAtoms) {
            sharedGKField[0] = new SharedDoubleArray(nAtoms);
            sharedGKField[1] = new SharedDoubleArray(nAtoms);
            sharedGKField[2] = new SharedDoubleArray(nAtoms);
            sharedGKFieldCR[0] = new SharedDoubleArray(nAtoms);
            sharedGKFieldCR[1] = new SharedDoubleArray(nAtoms);
            sharedGKFieldCR[2] = new SharedDoubleArray(nAtoms);
            sharedBornGrad = new SharedDoubleArray(nAtoms);
            baseRadius = new double[nAtoms];
            baseRadiusWithBondi = new double[nAtoms];
            overlapScale = new double[nAtoms];
            rDisp = new double[nAtoms];
            born = new double[nAtoms];
            use = new boolean[nAtoms];
        }

        fill(use, true);
        for (int i = 0; i < nAtoms; i++) {
            baseRadius[i] = 2.0;
            //            overlapScale[i] = 0.69;   // Original value based on small molecule parameterization.
            overlapScale[i] = 0.60; // New default value based on 2016 amino acid GK parameterization.
            if (useFittedRadii) {
                overlapScale[i] = solventRadii.getOverlapScale();
            }
            int atomicNumber = atoms[i].getAtomicNumber();
            AtomType atomType = atoms[i].getAtomType();

            switch (atomicNumber) {
            case 0:
                baseRadius[i] = 0.0;
                break;
            case 1:
                baseRadius[i] = 1.2;
                break;
            case 2:
                baseRadius[i] = 1.4;
                break;
            case 5:
                baseRadius[i] = 1.8;
                break;
            case 6:
                baseRadius[i] = 1.7;
                break;
            case 7:
                baseRadius[i] = 1.55;
                break;
            case 8:
                baseRadius[i] = 1.52;
                break;
            case 9:
                baseRadius[i] = 1.47;
                break;
            case 10:
                baseRadius[i] = 1.54;
                break;
            case 14:
                baseRadius[i] = 2.1;
                break;
            case 15:
                baseRadius[i] = 1.8;
                break;
            case 16:
                baseRadius[i] = 1.8;
                break;
            case 17:
                baseRadius[i] = 1.75;
                break;
            case 18:
                baseRadius[i] = 1.88;
                break;
            case 34:
                baseRadius[i] = 1.9;
                break;
            case 35:
                baseRadius[i] = 1.85;
                break;
            case 36:
                baseRadius[i] = 2.02;
                break;
            case 53:
                baseRadius[i] = 1.98;
                break;
            case 54:
                baseRadius[i] = 2.16;
                break;
            default:
                baseRadius[i] = 2.00;
            }

            double bondiFactor = bondiScale;

            int atomNumber = atoms[i].xyzIndex + 1;
            if (useFittedRadii) {
                // First check to see if this atom is in the hardcoded maps.
                switch (radiiMapType) {
                default:
                case ATOMTYPE:
                    // Check for hard-coded AtomType bondi factor.
                    if (solventRadii.getAtomBondiMap().containsKey(atomType.type)) {
                        bondiFactor = solventRadii.getAtomBondiMap().get(atomType.type);
                        if (verboseRadii) {
                            logger.info(String.format(
                                    " (GK) TypeToBondi: Atom %3s-%-4s (%d) with AtomType %d to %.2f (bondi factor %.4f)",
                                    atoms[i].getResidueName(), atoms[i].getName(), atomNumber, atomType.type,
                                    baseRadius[i] * bondiFactor, bondiFactor));
                        }
                    }
                    // TODO: fix these manual overrides which only apply to AM-PRO-13.
                    // Special case for ALA alpha carbon and alpha hydrogen.
                    // Many aminos use types 8,12 for their CA,HA so these can't be specified in the map.
                    if ((atoms[i].getAtomType().type == 8 || atoms[i].getAtomType().type == 12)) {
                        if (atoms[i].getResidueName().equals("ALA")) {
                            bondiFactor = 1.60;
                            if (verboseRadii) {
                                logger.info(String.format(
                                        " (GK) TypeToBondi: Atom %3s-%-4s (%d) with AtomType %d to %.2f (bondi factor %.4f)",
                                        atoms[i].getResidueName(), atoms[i].getName(), atomNumber, atomType.type,
                                        baseRadius[i] * bondiFactor, bondiFactor));
                            }
                        }
                    }
                    // Special case for CYD CB,HB.  As above, atom types for the CB+HB are shared between CYS and CYD.
                    // Currently, we only want S+HS on CYS.  So the CB,HB (types 43,44) are disabled in the table.
                    if ((atoms[i].getAtomType().type == 43 || atoms[i].getAtomType().type == 44)) {
                        if (atoms[i].getResidueName().equals("CYD")) {
                            bondiFactor = 1.02;
                            if (verboseRadii) {
                                logger.info(String.format(
                                        " (GK) TypeToBondi: Atom %3s-%-4s (%d) with AtomType %d to %.2f (bondi factor %.4f)",
                                        atoms[i].getResidueName(), atoms[i].getName(), atomNumber, atomType.type,
                                        baseRadius[i] * bondiFactor, bondiFactor));
                            }
                        }
                    }
                    break;
                case BIOTYPE:
                    Map<String, BioType> bioTypes = forceField.getBioTypeMap();
                    BioType bioType = null;
                    for (BioType one : bioTypes.values()) {
                        if (one.atomType == atomType.type) {
                            bioType = one;
                            break;
                        }
                    }
                    if (bioType == null) {
                        logger.severe(String.format("Couldn't find biotype for %s", atomType, toString()));
                    }

                    //                BioType bioType = forceField.getBioType(atoms[i].getResidueName(), atoms[i].getName());
                    //                if (bioType == null) {
                    //                    logger.info(String.format("Null biotype for atom: %3s-%-4s",
                    //                            atoms[i].getResidueName(), atoms[i].getName()));
                    //                }
                    // Check for hard-coded BioType bondi factor.
                    if (solventRadii.getBioBondiMap().containsKey(bioType.index)) {
                        double factor = solventRadii.getBioBondiMap().get(bioType.index);
                        bondiFactor = factor;
                        if (verboseRadii) {
                            logger.info(String.format(
                                    " (GK) BiotypeToBondi: Atom %3s-%-4s (%d) with BioType %d to %.2f (bondi factor %.4f)",
                                    atoms[i].getResidueName(), atoms[i].getName(), atomNumber, bioType.index,
                                    baseRadius[i] * bondiFactor, bondiFactor));
                        }
                    }
                    // TODO: fix these manual overrides which only apply to AM-PRO-13.
                    // Special case for ALA alpha carbon and alpha hydrogen.
                    // Many aminos use types 8,12 for their CA,HA so these can't be specified in the map.
                    if (bioType.index == 8 || bioType.index == 12) {
                        if (atoms[i].getResidueName().equals("ALA")) {
                            bondiFactor = 1.60;
                            if (verboseRadii) {
                                logger.info(String.format(
                                        " (GK) BiotypeToBondi: Atom %3s-%-4s (%d) with BioType %d to %.2f (bondi factor %.4f)",
                                        atoms[i].getResidueName(), atoms[i].getName(), atomNumber, bioType.index,
                                        baseRadius[i] * bondiFactor, bondiFactor));
                            }
                        }
                    }
                    // Special case for CYD CB,HB.  As above, atom types for the CB+HB are shared between CYS and CYD.
                    // Currently, we only want S+HS on CYS.  So the CB,HB (types 43,44) are disabled in the table.
                    if (bioType.index == 83 || bioType.index == 84) {
                        if (atoms[i].getResidueName().equals("CYD")) {
                            bondiFactor = 1.02;
                            if (verboseRadii) {
                                logger.info(String.format(
                                        " (GK) BiotypeToBondi: Atom %3s-%-4s (%d) with BioType %d to %.2f (bondi factor %.4f)",
                                        atoms[i].getResidueName(), atoms[i].getName(), atomNumber, bioType.index,
                                        baseRadius[i] * bondiFactor, bondiFactor));
                            }
                        }
                    }
                    break;
                }
            }
            // Next, check if this Atom has an ISolvRad forcefield parameter.
            //            if (atoms[i].getISolvRadType() != null) {
            //                bondiFactor = atoms[i].getISolvRadType().radiusScale;
            //                logger.info(String.format(" (GK) ISolvRad parameter for Atom %3s-%-4s with AtomType %d to %.2f (bondi factor %.2f)",
            //                    atoms[i].getResidueName(), atoms[i].getName(), atomType.type, baseRadius[i] * bondiFactor, bondiFactor));
            //            }
            ISolvRadType iSolvRadType = forceField.getISolvRadType(Integer.toString(atomType.type));
            if (iSolvRadType != null) {
                bondiFactor = iSolvRadType.radiusScale;
                if (verboseRadii) {
                    logger.info(String.format(
                            " (GK) ISolvRad parameter for Atom %3s-%-4s with AtomType %d to %.2f (bondi factor %.2f)",
                            atoms[i].getResidueName(), atoms[i].getName(), atomType.type,
                            baseRadius[i] * bondiFactor, bondiFactor));
                }
            }
            // Finally, check for command-line bondi factor override.
            if (radiiOverride.containsKey(atomType.type) && !radiiByNumberMap.containsKey(atomNumber)) {
                bondiFactor = radiiOverride.get(atomType.type);
                logger.info(
                        String.format(" (GK) Scaling Atom %3s-%-4s with AtomType %d to %.2f (bondi factor %.2f)",
                                atoms[i].getResidueName(), atoms[i].getName(), atomType.type,
                                baseRadius[i] * bondiFactor, bondiFactor));
            }
            if (radiiByNumberMap.containsKey(atomNumber)) {
                bondiFactor = radiiByNumberMap.get(atomNumber);
                logger.info(String.format(" (GK) Scaling Atom number %d, %3s-%-4s, with factor %.2f", atomNumber,
                        atoms[i].getResidueName(), atoms[i].getName(), bondiFactor));
            }

            baseRadiusWithBondi[i] = baseRadius[i] * bondiFactor;

        }

        // Resets verboseRadii; reduces logging messages when mutating MultiResidues.
        verboseRadii = false;

        if (dispersionRegion != null) {
            dispersionRegion.init();
        }

        if (cavitationRegion != null) {
            cavitationRegion.init();
        }

    }

    public void setUse(boolean use[]) {
        this.use = use;
    }

    /**
     * <p>
     * computeBornRadii</p>
     */
    public void computeBornRadii() {

        /**
         * Born radii are fixed.
         */
        if (fixedRadii) {
            return;
        }

        try {
            parallelTeam.execute(bornRadiiRegion);
        } catch (Exception e) {
            String message = "Fatal exception computing Born radii.";
            logger.log(Level.SEVERE, message, e);
        }

        for (int i = 0; i < nAtoms; i++) {
            if (use[i]) {
                double borni = born[i];
                if (Double.isInfinite(borni) || Double.isNaN(borni)) {
                    //logger.severe(String.format(" %s\n Born radii %d %8.3f", atoms[i], i, born[i]));
                    throw new EnergyException(String.format(" %s\n Born radii %d %8.3f", atoms[i], i, born[i]),
                            true);
                }
            }
        }
    }

    /**
     * <p>
     * computePermanentGKField</p>
     */
    public void computePermanentGKField() {
        try {
            parallelTeam.execute(permanentGKFieldRegion);
        } catch (Exception e) {
            String message = "Fatal exception computing permanent GK field.";
            logger.log(Level.SEVERE, message, e);
        }
    }

    /**
     * <p>
     * computeInducedGKField</p>
     */
    public void computeInducedGKField() {
        try {
            parallelTeam.execute(inducedGKFieldRegion);
        } catch (Exception e) {
            String message = "Fatal exception computing induced GK field.";
            logger.log(Level.SEVERE, message, e);
        }
    }

    /**
     * <p>
     * solvationEnergy</p>
     *
     * @param gradient a boolean.
     * @param print a boolean.
     * @return a double.
     */
    public double solvationEnergy(boolean gradient, boolean print) {

        /**
         * Initialize the gradient accumulation arrays.
         */
        if (gradient) {
            for (int j = 0; j < nAtoms; j++) {
                sharedBornGrad.set(j, 0.0);
            }
        }

        try {
            /**
             * Find the GK energy.
             */
            gkTime = -System.nanoTime();
            gkEnergyRegion.setGradient(gradient);
            parallelTeam.execute(gkEnergyRegion);
            gkTime += System.nanoTime();
            /**
             * Find the nonpolar energy.
             */
            switch (nonPolar) {
            case CAV:
                cavitationTime = -System.nanoTime();
                parallelTeam.execute(cavitationRegion);
                cavitationTime += System.nanoTime();
                break;
            case CAV_DISP:
                dispersionTime = -System.nanoTime();
                dispersionRegion.setGradient(gradient);
                parallelTeam.execute(dispersionRegion);
                dispersionTime += System.nanoTime();
                cavitationTime = -System.nanoTime();
                parallelTeam.execute(cavitationRegion);
                cavitationTime += System.nanoTime();
                break;
            case BORN_CAV_DISP:
                dispersionTime = -System.nanoTime();
                dispersionRegion.setGradient(gradient);
                parallelTeam.execute(dispersionRegion);
                dispersionTime += System.nanoTime();
                break;
            case HYDROPHOBIC_PMF:
            case BORN_SOLV:
            case NONE:
            default:
                break;
            }
        } catch (Exception e) {
            String message = "Fatal exception computing the continuum solvation energy.";
            logger.log(Level.SEVERE, message, e);
        }

        /**
         * Compute the Born radii chain rule term.
         */
        if (gradient) {
            try {
                gkTime -= System.nanoTime();
                parallelTeam.execute(bornGradRegion);
                gkTime += System.nanoTime();
            } catch (Exception e) {
                String message = "Fatal exception computing Born radii chain rule term.";
                logger.log(Level.SEVERE, message, e);
            }
        }

        if (print) {
            logger.info(format(" Generalized Kirkwood%16.8f %10.3f", gkEnergyRegion.getEnergy(), gkTime * 1e-9));
            switch (nonPolar) {
            case CAV:
                cavitationEnergy = cavitationRegion.getEnergy();
                logger.info(format(" Cavitation          %16.8f %10.3f", cavitationEnergy, cavitationTime * 1e-9));
                break;
            case CAV_DISP:
                cavitationEnergy = cavitationRegion.getEnergy();
                dispersionEnergy = dispersionRegion.getEnergy();
                logger.info(format(" Cavitation          %16.8f %10.3f", cavitationEnergy, cavitationTime * 1e-9));
                logger.info(format(" Dispersion          %16.8f %10.3f", dispersionEnergy, dispersionTime * 1e-9));
                break;
            case BORN_CAV_DISP:
                dispersionEnergy = dispersionRegion.getEnergy();
                logger.info(format(" Dispersion          %16.8f %10.3f", dispersionEnergy, dispersionTime * 1e-9));
                break;
            case HYDROPHOBIC_PMF:
            case BORN_SOLV:
            case NONE:
            default:
                break;
            }
        }

        switch (nonPolar) {
        case CAV:
            solvationEnergy = gkEnergyRegion.getEnergy() + cavitationRegion.getEnergy();
            break;
        case CAV_DISP:
            solvationEnergy = gkEnergyRegion.getEnergy() + dispersionRegion.getEnergy()
                    + cavitationRegion.getEnergy();
            break;
        case BORN_CAV_DISP:
            solvationEnergy = gkEnergyRegion.getEnergy() + dispersionRegion.getEnergy();
            break;
        case HYDROPHOBIC_PMF:
        case BORN_SOLV:
        case NONE:
        default:
            solvationEnergy = gkEnergyRegion.getEnergy();
            break;

        }

        if (lambdaTerm) {
            return lambda * solvationEnergy;
        } else {
            return solvationEnergy;
        }
    }

    /**
     * Returns the cavitation component (if applicable) of GK energy. If this GK
     * is operating without a cavitation term, it either returns 0, or throws
     * an error if throwError is true.
     * @param throwError
     * @return Cavitation energy
     */
    public double getCavitationEnergy(boolean throwError) {
        switch (nonPolar) {
        case CAV:
        case CAV_DISP:
            return cavitationEnergy;
        default:
            if (throwError) {
                throw new IllegalArgumentException(" GK is operating without a cavitation term");
            } else {
                return 0.0;
            }
        }
    }

    /**
     * Returns the dispersion component (if applicable) of GK energy. If this GK
     * is operating without a dispersion term, it either returns 0, or throws
     * an error if throwError is true.
     * @param throwError
     * @return Cavitation energy
     */
    public double getDispersionEnergy(boolean throwError) {
        switch (nonPolar) {
        case CAV_DISP:
        case BORN_CAV_DISP:
            return dispersionEnergy;
        default:
            if (throwError) {
                throw new IllegalArgumentException(" GK is operating without a dispersion term");
            } else {
                return 0.0;
            }
        }
    }

    /**
     * <p>
     * getInteractions</p>
     *
     * @return a int.
     */
    public int getInteractions() {
        return gkEnergyRegion.getInteractions();
    }

    /**
     * Updates the value of lambda.
     *
     * @param lambda the current lambda value.
     */
    @Override
    public void setLambda(double lambda) {
        if (lambdaTerm) {
            this.lambda = lambda;
        } else {
            /**
             * If the lambdaTerm flag is false, lambda must be set to one.
             */
            this.lambda = 1.0;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getLambda() {
        return lambda;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public double getdEdL() {
        if (lambdaTerm) {
            return solvationEnergy;
        }
        return 0.0;
    }

    /**
     * The 2nd derivative is 0.0. (U=Lambda*Egk, dU/dL=Egk, d2U/dL2=0.0)
     *
     * @return 0.0 is always returned.
     */
    @Override
    public double getd2EdL2() {
        return 0.0;
    }

    /**
     * These contributions are already aggregated into the arrays used by PME.
     *
     * @param gradient the Gradient array.
     */
    @Override
    public void getdEdXdL(double[] gradient) {
    }

    public static NonPolar getNonPolarModel(String nonpolarModel) {
        try {
            return NonPolar.valueOf(nonpolarModel);
        } catch (IllegalArgumentException ex) {
            logger.warning("Unrecognized nonpolar model requested; defaulting to NONE.");
            return NonPolar.NONE;
        }
    }

    public enum NonPolar {

        CAV, CAV_DISP, HYDROPHOBIC_PMF, BORN_CAV_DISP, BORN_SOLV, NONE
    }

    /**
     * Compute Born radii in parallel via the Grycuk method.
     *
     * @since 1.0
     */
    private class BornRadiiRegion extends ParallelRegion {

        private final BornRadiiLoop bornRadiiLoop[];
        private SharedDoubleArray sharedBorn;
        private SharedDouble ecavTot;

        public BornRadiiRegion(int nt) {
            bornRadiiLoop = new BornRadiiLoop[nt];
            for (int i = 0; i < nt; i++) {
                bornRadiiLoop[i] = new BornRadiiLoop();
            }
            ecavTot = new SharedDouble(0.0);
        }

        @Override
        public void start() {
            if (sharedBorn == null || sharedBorn.length() < nAtoms) {
                sharedBorn = new SharedDoubleArray(nAtoms);
            }
            for (int i = 0; i < nAtoms; i++) {
                sharedBorn.set(i, 0.0);
            }
        }

        @Override
        public void run() {
            try {
                execute(0, nAtoms - 1, bornRadiiLoop[getThreadIndex()]);
            } catch (Exception e) {
                String message = "Fatal exception computing Born radii in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        @Override
        public void finish() {
            for (int i = 0; i < nAtoms; i++) {
                final double baseRi = baseRadiusWithBondi[i];
                if (!use[i]) {
                    born[i] = baseRi;
                } else {
                    double sum = sharedBorn.get(i);
                    if (sum <= 0.0) {
                        sum = 0.001;
                    }
                    born[i] = pow(sum / PI4_3, THIRD);
                    born[i] = 1.0 / born[i];
                }

                /*
                 if (i < 25) {
                 logger.info(String.format(" %d Born radius:  %16.8f", i + 1, born[i]));
                 }
                 born[i] = baseRi;
                 */
            }
        }

        /**
         * Compute Born radii for a range of atoms via the Grycuk method.
         *
         * @since 1.0
         */
        private class BornRadiiLoop extends IntegerForLoop {

            private double localBorn[];
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;
            private double ecav;

            public BornRadiiLoop() {
                ecav = 0.0;
            }

            @Override
            public void start() {
                if (localBorn == null || localBorn.length < nAtoms) {
                    localBorn = new double[nAtoms];
                }
                fill(localBorn, 0.0);
            }

            @Override
            public void finish() {
                sharedBorn.reduce(localBorn, DoubleOp.SUM);
                ecavTot.addAndGet(ecav);
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!bornUseAll && !use[i]) {
                        continue;
                    }
                    final double baseRi = baseRadiusWithBondi[i];
                    assert (baseRi > 0.0);
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    /**
                     * The descreening integral is initialized to the limit of
                     * the atom alone in solvent.
                     *
                     * Lower values of i may have contributed descreening, so
                     * the integral is incremented rather than initialized.
                     */
                    localBorn[i] += PI4_3 / (baseRi * baseRi * baseRi);
                    int list[] = neighborLists[0][i];
                    int npair = list.length;
                    for (int l = 0; l < npair; l++) {
                        int k = list[l];
                        final double baseRk = baseRadiusWithBondi[k];
                        if (i != k && baseRk > 0.0) {
                            if (!bornUseAll && !use[k]) {
                                continue;
                            }
                            final double xr = x[k] - xi;
                            final double yr = y[k] - yi;
                            final double zr = z[k] - zi;
                            final double r2 = crystal.image(xr, yr, zr);
                            if (r2 > cut2) {
                                continue;
                            }
                            final double r = sqrt(r2);

                            // Atom i being descreeened by atom k.
                            final double scaledRk = baseRk * overlapScale[k];
                            final double scaledRk2 = scaledRk * scaledRk;
                            // Atom i is engulfed by atom k.
                            if (baseRi + r < scaledRk) {
                                final double lower = baseRi;
                                final double upper = scaledRk - r;
                                localBorn[i] += (PI4_3
                                        * (1.0 / (upper * upper * upper) - 1.0 / (lower * lower * lower)));
                            }
                            // Upper integration bound is always the same.
                            double upper = r + scaledRk;
                            // Lower integration bound depends on atoms sizes and separation.
                            double lower;
                            if (baseRi + r < scaledRk) {
                                // Atom i is engulfed by atom k.
                                lower = scaledRk - r;
                            } else if (r < baseRi + scaledRk) {
                                // Atoms are overlapped, begin integration from ri.
                                lower = baseRi;
                            } else {
                                // No overlap between atoms.
                                lower = r - scaledRk;
                            }
                            double l2 = lower * lower;
                            double l4 = l2 * l2;
                            double lr = lower * r;
                            double l4r = l4 * r;
                            double u2 = upper * upper;
                            double u4 = u2 * u2;
                            double ur = upper * r;
                            double u4r = u4 * r;
                            double term = (3.0 * (r2 - scaledRk2) + 6.0 * u2 - 8.0 * ur) / u4r
                                    - (3.0 * (r2 - scaledRk2) + 6.0 * l2 - 8.0 * lr) / l4r;
                            localBorn[i] -= PI_12 * term;

                            // Atom k being descreeened by atom i.
                            final double scaledRi = baseRi * overlapScale[i];
                            final double scaledRi2 = scaledRi * scaledRi;
                            // Atom k is engulfed by atom i.
                            if (baseRk + r < scaledRi) {
                                lower = baseRk;
                                upper = scaledRi - r;
                                localBorn[k] += (PI4_3
                                        * (1.0 / (upper * upper * upper) - 1.0 / (lower * lower * lower)));
                            }
                            // Upper integration bound is always the same.
                            upper = r + scaledRi;
                            // Lower integration bound depends on atoms sizes and separation.
                            if (baseRk + r < scaledRi) {
                                // Atom k is engulfed by atom i.
                                lower = scaledRi - r;
                            } else if (r < baseRk + scaledRi) {
                                // Atoms are overlapped, begin integration from rk.
                                lower = baseRk;
                            } else {
                                // No overlap between atoms.
                                lower = r - scaledRi;
                            }
                            l2 = lower * lower;
                            l4 = l2 * l2;
                            lr = lower * r;
                            l4r = l4 * r;
                            u2 = upper * upper;
                            u4 = u2 * u2;
                            ur = upper * r;
                            u4r = u4 * r;
                            term = (3.0 * (r2 - scaledRi2) + 6.0 * u2 - 8.0 * ur) / u4r
                                    - (3.0 * (r2 - scaledRi2) + 6.0 * l2 - 8.0 * lr) / l4r;
                            localBorn[k] -= PI_12 * term;
                        }
                    }
                }
            }
        }
    }

    /**
     * Compute Hydrophobic PMF.
     *
     * @since 1.0
     */
    private class HydrophobicPMFRegion extends ParallelRegion {

        // Radius of a carbon atom.
        private final double rCarbon = 1.7;
        // Radius of a water molecule.
        private final double rWater = 1.4;
        // Constant for calculation of atomic surface area.
        private final double safact = 0.3516;
        // Surface area of a hydrophobic carbon atom.
        private final double acSurf = 120.7628;
        // tanh slope (set very steep).
        private final double tSlope = 100.0;
        // Shift the tanh plot along the x-axis.
        private final double tOffset = 6.0;
        // Cutoff distance for pairwise HPMF interactions.
        private final double hpmfCut = 11.0;
        // Cutoff squared
        private final double hpmfCut2 = hpmfCut * hpmfCut;
        // Hydrophobic PMF well depth parameter.
        private final double h1 = -0.7308004860404441194;
        private final double h2 = 0.2001645051578760659;
        private final double h3 = -0.0905499953418473502;
        // Hydrophobic PMF well center point.
        private final double c1 = 3.8167879266271396155;
        private final double c2 = 5.4669162286016419472;
        private final double c3 = 7.1167694861385353278;
        // Reciprocal of the hydrophobic PMF well width.
        private final double w1 = 1.6858993102248638341;
        private final double w2 = 1.3906405621629980285;
        private final double w3 = 1.5741657341338335385;
        private final double rSurf = rCarbon + 2.0 * rWater;
        private final double piSurf = PI * (rCarbon + rWater);
        // Radius of each atom for use with hydrophobic PMF.
        private final double rPMF[];
        // Number of hydrophobic carbon atoms in the system.
        private final int nCarbon;
        // Number of the atom for each HPMF carbon atom site.
        private final int iCarbon[];
        // SASA value for each hydrophobic PMF carbon atom
        private final double carbonSASA[];
        // SASA value
        private final double tanhSA[];
        // Loop to find the SASA value of each hydrophobic carbon.
        private final CarbonSASALoop carbonSASALoop[];
        // Loop to find the hydrophobic energy.
        private final HydrophobicPMFLoop hydrophobicPMFLoop[];
        // Loop to find the SASA chain rule derivatives.
        private final CarbonSASACRLoop carbonSASACRLoop[];
        // Shared energy variable.
        private final SharedDouble sharedEnergy;
        private boolean gradient;
        private final double dtanhSA[];
        private final double sasa[];
        private final double carbonSASACR[];

        public HydrophobicPMFRegion(int nt) {
            logger.info(String.format(" Hydrophobic PMF cut-off:              %8.2f (A)", hpmfCut));
            /**
             * Count hydrophobic carbons.
             */
            int count = 0;
            for (int i = 0; i < nAtoms; i++) {
                Atom atom = atoms[i];
                int atomicNumber = atom.getAtomicNumber();
                if (atomicNumber == 6) {
                    List<Bond> bonds = atom.getBonds();
                    int bondCount = bonds.size();
                    if (bondCount <= 2) {
                        continue;
                    }
                    boolean keep = true;
                    for (int j = 0; j < bondCount; j++) {
                        Atom atom2 = bonds.get(j).get1_2(atom);
                        int atomicNumber2 = atom2.getAtomicNumber();
                        if (bondCount == 3 && atomicNumber2 == 8) {
                            keep = false;
                            break;
                        }
                    }
                    if (keep) {
                        count++;
                    }
                }
            }
            /**
             * Allocate arrays.
             */
            rPMF = new double[nAtoms];
            nCarbon = count;
            iCarbon = new int[nCarbon];
            carbonSASA = new double[nCarbon];
            carbonSASACR = new double[nCarbon];
            tanhSA = new double[nCarbon];
            dtanhSA = new double[nCarbon];
            sasa = new double[nCarbon];

            carbonSASALoop = new CarbonSASALoop[nt];
            hydrophobicPMFLoop = new HydrophobicPMFLoop[nt];
            carbonSASACRLoop = new CarbonSASACRLoop[nt];
            for (int i = 0; i < nt; i++) {
                carbonSASALoop[i] = new CarbonSASALoop();
                hydrophobicPMFLoop[i] = new HydrophobicPMFLoop();
                carbonSASACRLoop[i] = new CarbonSASACRLoop();
            }
            sharedEnergy = new SharedDouble();

            /**
             * Assign hydrophobic carbon values.
             */
            int index = 0;
            for (int i = 0; i < nAtoms; i++) {
                Atom atom = atoms[i];
                int atomicNumber = atom.getAtomicNumber();
                if (atomicNumber == 6) {
                    int nh = 0;
                    List<Bond> bonds = atom.getBonds();
                    int bondCount = bonds.size();
                    if (bondCount <= 2) {
                        continue;
                    }
                    boolean keep = true;
                    for (int j = 0; j < bondCount; j++) {
                        Atom atom2 = bonds.get(j).get1_2(atom);
                        int atomicNumber2 = atom2.getAtomicNumber();
                        if (atomicNumber2 == 1) {
                            nh++;
                        }
                        if (bondCount == 3 && atomicNumber2 == 8) {
                            keep = false;
                        }
                    }
                    if (keep) {
                        iCarbon[index] = i;
                        carbonSASA[index] = 1.0;
                        if (bondCount == 3 && nh == 0) {
                            carbonSASA[index] = 1.554;
                        } else if (bondCount == 3 && nh == 1) {
                            carbonSASA[index] = 1.073;
                        } else if (bondCount == 4 && nh == 1) {
                            carbonSASA[index] = 1.276;
                        } else if (bondCount == 4 && nh == 2) {
                            carbonSASA[index] = 1.045;
                        } else if (bondCount == 4 && nh == 3) {
                            carbonSASA[index] = 0.880;
                        }
                        carbonSASA[index] = carbonSASA[index] * safact / acSurf;
                        if (logger.isLoggable(Level.FINEST)) {
                            logger.finest(String.format(" %d Base HPMF SASA for atom %d: %10.8f", index + 1, i + 1,
                                    carbonSASA[index]));
                        }
                        index++;
                    }
                }
            }

            /**
             * Assign HPMF atomic radii from traditional Bondi values
             */
            for (int i = 0; i < nAtoms; i++) {
                rPMF[i] = 2.0;
                int atmnum = atoms[i].getAtomicNumber();
                switch (atmnum) {
                case 0:
                    rPMF[i] = 0.0;
                    break;
                case 1:
                    rPMF[i] = 1.20;
                    break;
                case 2:
                    rPMF[i] = 1.40;
                    break;
                case 5:
                    rPMF[i] = 1.80;
                    break;
                case 6:
                    rPMF[i] = 1.70;
                    break;
                case 7:
                    rPMF[i] = 1.55;
                    break;
                case 8:
                    rPMF[i] = 1.50;
                    break;
                case 9:
                    rPMF[i] = 1.47;
                    break;
                case 10:
                    rPMF[i] = 1.54;
                    break;
                case 14:
                    rPMF[i] = 2.10;
                    break;
                case 15:
                    rPMF[i] = 1.80;
                    break;
                case 16:
                    rPMF[i] = 1.80;
                    break;
                case 17:
                    rPMF[i] = 1.75;
                    break;
                case 18:
                    rPMF[i] = 1.88;
                    break;
                case 34:
                    rPMF[i] = 1.90;
                    break;
                case 35:
                    rPMF[i] = 1.85;
                    break;
                case 36:
                    rPMF[i] = 2.02;
                    break;
                case 53:
                    rPMF[i] = 1.98;
                    break;
                case 54:
                    rPMF[i] = 2.16;
                    break;
                }
            }
        }

        public void setGradient(boolean gradient) {
            this.gradient = gradient;
        }

        public double getEnergy() {
            return sharedEnergy.get();
        }

        @Override
        public void start() {
            sharedEnergy.set(0);
        }

        @Override
        public void run() {
            int ti = getThreadIndex();
            try {
                execute(0, nCarbon - 1, carbonSASALoop[ti]);
                execute(0, nCarbon - 2, hydrophobicPMFLoop[ti]);
                if (gradient) {
                    execute(0, nCarbon - 1, carbonSASACRLoop[ti]);
                }
            } catch (Exception e) {
                String message = "Fatal exception computing Born radii in thread " + ti + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute Hydrophobic PMF radii.
         *
         * @since 1.0
         */
        private class CarbonSASALoop extends IntegerForLoop {

            @Override
            public void run(int lb, int ub) {
                /**
                 * Get the surface area for each hydrophobic carbon atom.
                 */
                for (int ii = lb; ii <= ub; ii++) {
                    final int i = iCarbon[ii];
                    if (!use[i]) {
                        continue;
                    }
                    final double carbonSA = carbonSASA[ii];
                    double sa = acSurf;
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    int count = 0;
                    for (int k = 0; k < nAtoms; k++) {
                        if (i != k && use[k]) {
                            final double xr = x[k] - xi;
                            final double yr = y[k] - yi;
                            final double zr = z[k] - zi;
                            final double r2 = xr * xr + yr * yr + zr * zr;
                            double rk = rPMF[k];
                            double rBig = rk + rSurf;
                            if (r2 < rBig * rBig) {
                                final double r = sqrt(r2);
                                final double rSmall = rk - rCarbon;
                                final double part = piSurf * (rBig - r) * (1.0 + rSmall / r);
                                sa *= (1.0 - carbonSA * part);
                                count++;
                            }
                        }
                    }
                    sasa[ii] = sa;
                    //sasa[ii] = carbonSA;
                    double tSA = tanh(tSlope * (sa - tOffset));
                    tanhSA[ii] = 0.5 * (1.0 + tSA);
                    dtanhSA[ii] = 0.5 * tSlope * (1.0 - tSA * tSA);
                }
            }
        }

        /**
         * Compute Born radii for a range of atoms via the Grycuk method.
         *
         * @since 1.0
         */
        private class HydrophobicPMFLoop extends IntegerForLoop {

            private double energy;
            // Omit
            private final int omit[];
            private double gX[];
            private double gY[];
            private double gZ[];

            public HydrophobicPMFLoop() {
                omit = new int[nAtoms];
            }

            @Override
            public void start() {
                energy = 0.0;
                for (int i = 0; i < nAtoms; i++) {
                    omit[i] = -1;
                }
                int threadID = getThreadIndex();
                gX = grad[threadID][0];
                gY = grad[threadID][1];
                gZ = grad[threadID][2];
                if (gradient) {
                    for (int i = 0; i < nCarbon; i++) {
                        carbonSASACR[i] = 0.0;
                    }
                }
            }

            @Override
            public void run(int lb, int ub) {
                /**
                 * Hydrophobic PME energy.
                 */
                for (int ii = lb; ii <= ub; ii++) {
                    final int i = iCarbon[ii];
                    if (!use[i]) {
                        continue;
                    }
                    double tanhSAi = tanhSA[ii];
                    Atom ssAtom = null;
                    Atom atom = atoms[i];
                    List<Bond> bonds = atom.getBonds();
                    for (Bond bond : bonds) {
                        Atom atom2 = bond.get1_2(atom);
                        int k = atom2.xyzIndex - 1;
                        if (!use[k]) {
                            continue;
                        }
                        omit[k] = i;
                        if (atom2.getAtomicNumber() == 16) {
                            ssAtom = atom2;
                        }
                    }
                    List<Angle> angles = atom.getAngles();
                    for (Angle angle : angles) {
                        Atom atom2 = angle.get1_3(atom);
                        if (atom2 != null) {
                            int k = atom2.xyzIndex - 1;
                            if (!use[k]) {
                                continue;
                            }
                            omit[k] = i;
                        }
                    }
                    List<Torsion> torsions = atom.getTorsions();
                    for (Torsion torsion : torsions) {
                        Atom atom2 = torsion.get1_4(atom);
                        if (atom2 != null) {
                            int k = atom2.xyzIndex - 1;
                            if (!use[k]) {
                                continue;
                            }
                            omit[k] = i;
                            if (ssAtom != null) {
                                List<Bond> bonds2 = atom2.getBonds();
                                for (Bond bond : bonds2) {
                                    Atom s = bond.get1_2(atom2);
                                    if (s.getAtomicNumber() == 16) {
                                        List<Bond> sBonds = s.getBonds();
                                        for (Bond sBond : sBonds) {
                                            Atom s2 = sBond.get1_2(s);
                                            if (s2 == ssAtom) {
                                                omit[k] = -1;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    double e = 0.0;
                    for (int kk = ii + 1; kk < nCarbon; kk++) {
                        int k = iCarbon[kk];
                        if (!use[k]) {
                            continue;
                        }
                        if (omit[k] != i) {
                            final double xr = xi - x[k];
                            final double yr = yi - y[k];
                            final double zr = zi - z[k];
                            final double r2 = xr * xr + yr * yr + zr * zr;
                            if (r2 < hpmfCut2) {
                                final double r = sqrt(r2);
                                final double a1 = (r - c1) * w1;
                                final double a2 = (r - c2) * w2;
                                final double a3 = (r - c3) * w3;
                                final double e1 = h1 * exp(-a1 * a1);
                                final double e2 = h2 * exp(-a2 * a2);
                                final double e3 = h3 * exp(-a3 * a3);
                                final double t1t2 = tanhSAi * tanhSA[kk];
                                final double sum = (e1 + e2 + e3);
                                e += sum * t1t2;
                                if (gradient) {
                                    /**
                                     * First part of hydrophobic PMF derivative
                                     * calculation.
                                     */
                                    double de1 = -2.0 * e1 * a1 * w1;
                                    double de2 = -2.0 * e2 * a2 * w2;
                                    double de3 = -2.0 * e3 * a3 * w3;
                                    double dsum = (de1 + de2 + de3) * t1t2 / r;
                                    double dedx = dsum * xr;
                                    double dedy = dsum * yr;
                                    double dedz = dsum * zr;
                                    gX[i] += dedx;
                                    gY[i] += dedy;
                                    gZ[i] += dedz;
                                    gX[k] -= dedx;
                                    gY[k] -= dedy;
                                    gZ[k] -= dedz;
                                    /**
                                     * Chain Rule Term.
                                     */
                                    carbonSASACR[ii] += sum * tanhSA[kk] * dtanhSA[ii];
                                    carbonSASACR[kk] += sum * tanhSA[ii] * dtanhSA[kk];
                                }
                            }
                        }
                    }
                    energy += e;
                }
            }

            @Override
            public void finish() {
                sharedEnergy.addAndGet(energy);
            }
        }

        /**
         * Compute Hydrophobic PMF chain rule term.
         *
         * @since 1.0
         */
        private class CarbonSASACRLoop extends IntegerForLoop {

            private double gX[];
            private double gY[];
            private double gZ[];

            public CarbonSASACRLoop() {
            }

            @Override
            public void start() {
                int threadID = getThreadIndex();
                gX = grad[threadID][0];
                gY = grad[threadID][1];
                gZ = grad[threadID][2];
            }

            @Override
            public void run(int lb, int ub) {
                for (int ii = lb; ii <= ub; ii++) {
                    final int i = iCarbon[ii];
                    if (!use[i]) {
                        continue;
                    }
                    final double carbonSA = carbonSASA[ii];
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    for (int k = 0; k < nAtoms; k++) {
                        if (i != k && use[k]) {
                            final double xr = xi - x[k];
                            final double yr = yi - y[k];
                            final double zr = zi - z[k];
                            final double r2 = xr * xr + yr * yr + zr * zr;
                            double rk = rPMF[k];
                            double rBig = rk + rSurf;
                            if (r2 <= rBig * rBig) {
                                final double r = sqrt(r2);
                                final double rSmall = rk - rCarbon;
                                final double rr = 1.0 / r;
                                final double rr2 = rr * rr;
                                final double part = piSurf * (rBig - r) * (1.0 + rSmall * rr);
                                double t1b = -piSurf * (1.0 + rBig * rSmall * rr2);
                                double t1a = -sasa[ii] / (1.0 / carbonSA - part);
                                double de = t1a * t1b * rr * carbonSASACR[ii];
                                double dedx = de * xr;
                                double dedy = de * yr;
                                double dedz = de * zr;
                                gX[i] += dedx;
                                gY[i] += dedy;
                                gZ[i] += dedz;
                                gX[k] -= dedx;
                                gY[k] -= dedy;
                                gZ[k] -= dedz;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Compute the Generalized Kirkwood permanent reaction field in parallel.
     *
     * @since 1.0
     */
    private class PermanentGKFieldRegion extends ParallelRegion {

        private final PermanentGKFieldLoop permanentGKFieldLoop[];

        public PermanentGKFieldRegion(int nt) {
            permanentGKFieldLoop = new PermanentGKFieldLoop[nt];
            for (int i = 0; i < nt; i++) {
                permanentGKFieldLoop[i] = new PermanentGKFieldLoop();
            }
        }

        @Override
        public void start() {
            for (int i = 0; i < nAtoms; i++) {
                sharedGKField[0].set(i, 0.0);
                sharedGKField[1].set(i, 0.0);
                sharedGKField[2].set(i, 0.0);
            }
        }

        @Override
        public void run() {
            try {
                int threadIndex = getThreadIndex();
                execute(0, nAtoms - 1, permanentGKFieldLoop[threadIndex]);
            } catch (Exception e) {
                String message = "Fatal exception computing GK Energy in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute the Generalized Kirkwood permanent reaction field.
         *
         * @since 1.0
         */
        private class PermanentGKFieldLoop extends IntegerForLoop {

            private final double a[][];
            private final double gc[];
            private final double gux[], guy[], guz[];
            private final double gqxx[], gqyy[], gqzz[];
            private final double gqxy[], gqxz[], gqyz[];
            private double fx_local[];
            private double fy_local[];
            private double fz_local[];
            private final double dx_local[];
            private double multipolei[];
            private double xi, yi, zi;
            private double ci, uxi, uyi, uzi, qxxi, qxyi, qxzi, qyyi, qyzi, qzzi;
            private double rbi;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public PermanentGKFieldLoop() {
                a = new double[4][3];
                gc = new double[11];
                gux = new double[11];
                guy = new double[11];
                guz = new double[11];
                gqxx = new double[11];
                gqyy = new double[11];
                gqzz = new double[11];
                gqxy = new double[11];
                gqxz = new double[11];
                gqyz = new double[11];
                dx_local = new double[3];
            }

            @Override
            public void start() {
                if (fx_local == null || fx_local.length < maxNumAtoms) {
                    fx_local = new double[maxNumAtoms];
                    fy_local = new double[maxNumAtoms];
                    fz_local = new double[maxNumAtoms];
                }
                fill(fx_local, 0.0);
                fill(fy_local, 0.0);
                fill(fz_local, 0.0);
            }

            @Override
            public void finish() {
                /**
                 * Reduce the field contributions computed by the current thread
                 * into the shared arrays.
                 */
                sharedGKField[0].reduce(fx_local, DoubleOp.SUM);
                sharedGKField[1].reduce(fy_local, DoubleOp.SUM);
                sharedGKField[2].reduce(fz_local, DoubleOp.SUM);
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!use[i]) {
                        continue;
                    }
                    xi = x[i];
                    yi = y[i];
                    zi = z[i];
                    multipolei = globalMultipole[i];
                    ci = multipolei[t000];
                    uxi = multipolei[t100];
                    uyi = multipolei[t010];
                    uzi = multipolei[t001];
                    qxxi = multipolei[t200] / 3.0;
                    qxyi = multipolei[t110] / 3.0;
                    qxzi = multipolei[t101] / 3.0;
                    qyyi = multipolei[t020] / 3.0;
                    qyzi = multipolei[t011] / 3.0;
                    qzzi = multipolei[t002] / 3.0;
                    rbi = born[i];
                    int list[] = neighborLists[0][i];
                    int npair = list.length;
                    for (int l = 0; l < npair; l++) {
                        int k = list[l];
                        if (!use[k]) {
                            continue;
                        }
                        permanentGKField(i, k);
                    }
                    /**
                     * Include the self permanent reaction field, which is not
                     * in the neighbor list.
                     */
                    permanentGKField(i, i);
                }
            }

            private void permanentGKField(int i, int k) {
                dx_local[0] = x[k] - xi;
                dx_local[1] = y[k] - yi;
                dx_local[2] = z[k] - zi;
                final double r2 = crystal.image(dx_local);
                if (r2 > cut2) {
                    return;
                }
                double xr = dx_local[0];
                double yr = dx_local[1];
                double zr = dx_local[2];
                double xr2 = xr * xr;
                double yr2 = yr * yr;
                double zr2 = zr * zr;
                final double rbk = born[k];
                final double multipolek[] = globalMultipole[k];
                final double ck = multipolek[t000];
                final double uxk = multipolek[t100];
                final double uyk = multipolek[t010];
                final double uzk = multipolek[t001];
                final double qxxk = multipolek[t200] / 3.0;
                final double qxyk = multipolek[t110] / 3.0;
                final double qxzk = multipolek[t101] / 3.0;
                final double qyyk = multipolek[t020] / 3.0;
                final double qyzk = multipolek[t011] / 3.0;
                final double qzzk = multipolek[t002] / 3.0;
                final double rb2 = rbi * rbk;
                final double expterm = exp(-r2 / (gkc * rb2));
                final double expc = expterm / gkc;
                final double expc1 = 1.0 - expc;
                final double dexpc = -2.0 / (gkc * rb2);
                final double expcdexpc = -expc * dexpc;
                final double gf2 = 1.0 / (r2 + rb2 * expterm);
                final double gf = sqrt(gf2);
                final double gf3 = gf2 * gf;
                final double gf5 = gf3 * gf2;
                final double gf7 = gf5 * gf2;
                /**
                 * Reaction potential auxiliary terms.
                 */
                a[0][0] = gf;
                a[1][0] = -gf3;
                a[2][0] = 3.0 * gf5;
                a[3][0] = -15.0 * gf7;
                /**
                 * Reaction potential gradient auxiliary terms.
                 */
                a[0][1] = expc1 * a[1][0];
                a[1][1] = expc1 * a[2][0];
                a[2][1] = expc1 * a[3][0];
                /**
                 * 2nd reaction potential gradient auxiliary terms.
                 */
                a[1][2] = expc1 * a[2][1] + expcdexpc * a[2][0];
                /**
                 * Multiply the potential auxiliary terms by their dielectric
                 * functions.
                 */
                a[0][1] = fc * a[0][1];
                a[1][0] = fd * a[1][0];
                a[1][1] = fd * a[1][1];
                a[1][2] = fd * a[1][2];
                a[2][0] = fq * a[2][0];
                a[2][1] = fq * a[2][1];
                /**
                 * Unweighted reaction potential tensor.
                 */
                gux[1] = xr * a[1][0];
                guy[1] = yr * a[1][0];
                guz[1] = zr * a[1][0];
                /**
                 * Unweighted reaction potential gradient tensor.
                 */
                gc[2] = xr * a[0][1];
                gc[3] = yr * a[0][1];
                gc[4] = zr * a[0][1];
                gux[2] = a[1][0] + xr2 * a[1][1];
                gux[3] = xr * yr * a[1][1];
                gux[4] = xr * zr * a[1][1];
                guy[2] = gux[3];
                guy[3] = a[1][0] + yr2 * a[1][1];
                guy[4] = yr * zr * a[1][1];
                guz[2] = gux[4];
                guz[3] = guy[4];
                guz[4] = a[1][0] + zr2 * a[1][1];
                gqxx[2] = xr * (2.0 * a[2][0] + xr2 * a[2][1]);
                gqxx[3] = yr * xr2 * a[2][1];
                gqxx[4] = zr * xr2 * a[2][1];
                gqyy[2] = xr * yr2 * a[2][1];
                gqyy[3] = yr * (2.0 * a[2][0] + yr2 * a[2][1]);
                gqyy[4] = zr * yr2 * a[2][1];
                gqzz[2] = xr * zr2 * a[2][1];
                gqzz[3] = yr * zr2 * a[2][1];
                gqzz[4] = zr * (2.0 * a[2][0] + zr2 * a[2][1]);
                gqxy[2] = yr * (a[2][0] + xr2 * a[2][1]);
                gqxy[3] = xr * (a[2][0] + yr2 * a[2][1]);
                gqxy[4] = zr * xr * yr * a[2][1];
                gqxz[2] = zr * (a[2][0] + xr2 * a[2][1]);
                gqxz[3] = gqxy[4];
                gqxz[4] = xr * (a[2][0] + zr2 * a[2][1]);
                gqyz[2] = gqxy[4];
                gqyz[3] = zr * (a[2][0] + yr2 * a[2][1]);
                gqyz[4] = yr * (a[2][0] + zr2 * a[2][1]);
                /**
                 * Unweighted 2nd reaction potential gradient tensor.
                 */
                gux[5] = xr * (3.0 * a[1][1] + xr2 * a[1][2]);
                gux[6] = yr * (a[1][1] + xr2 * a[1][2]);
                gux[7] = zr * (a[1][1] + xr2 * a[1][2]);
                gux[8] = xr * (a[1][1] + yr2 * a[1][2]);
                gux[9] = zr * xr * yr * a[1][2];
                gux[10] = xr * (a[1][1] + zr2 * a[1][2]);
                guy[5] = yr * (a[1][1] + xr2 * a[1][2]);
                guy[6] = xr * (a[1][1] + yr2 * a[1][2]);
                guy[7] = gux[9];
                guy[8] = yr * (3.0 * a[1][1] + yr2 * a[1][2]);
                guy[9] = zr * (a[1][1] + yr2 * a[1][2]);
                guy[10] = yr * (a[1][1] + zr2 * a[1][2]);
                guz[5] = zr * (a[1][1] + xr2 * a[1][2]);
                guz[6] = gux[9];
                guz[7] = xr * (a[1][1] + zr2 * a[1][2]);
                guz[8] = zr * (a[1][1] + yr2 * a[1][2]);
                guz[9] = yr * (a[1][1] + zr2 * a[1][2]);
                guz[10] = zr * (3.0 * a[1][1] + zr2 * a[1][2]);

                /**
                 * Generalized Kirkwood permanent reaction field.
                 */
                double fix = uxk * gux[2] + uyk * gux[3] + uzk * gux[4]
                        + 0.5 * (ck * gux[1] + qxxk * gux[5] + qyyk * gux[8] + qzzk * gux[10]
                                + 2.0 * (qxyk * gux[6] + qxzk * gux[7] + qyzk * gux[9]))
                        + 0.5 * (ck * gc[2] + qxxk * gqxx[2] + qyyk * gqyy[2] + qzzk * gqzz[2]
                                + 2.0 * (qxyk * gqxy[2] + qxzk * gqxz[2] + qyzk * gqyz[2]));
                double fiy = uxk * guy[2] + uyk * guy[3] + uzk * guy[4]
                        + 0.5 * (ck * guy[1] + qxxk * guy[5] + qyyk * guy[8] + qzzk * guy[10]
                                + 2.0 * (qxyk * guy[6] + qxzk * guy[7] + qyzk * guy[9]))
                        + 0.5 * (ck * gc[3] + qxxk * gqxx[3] + qyyk * gqyy[3] + qzzk * gqzz[3]
                                + 2.0 * (qxyk * gqxy[3] + qxzk * gqxz[3] + qyzk * gqyz[3]));
                double fiz = uxk * guz[2] + uyk * guz[3] + uzk * guz[4]
                        + 0.5 * (ck * guz[1] + qxxk * guz[5] + qyyk * guz[8] + qzzk * guz[10]
                                + 2.0 * (qxyk * guz[6] + qxzk * guz[7] + qyzk * guz[9]))
                        + 0.5 * (ck * gc[4] + qxxk * gqxx[4] + qyyk * gqyy[4] + qzzk * gqzz[4]
                                + 2.0 * (qxyk * gqxy[4] + qxzk * gqxz[4] + qyzk * gqyz[4]));
                double fkx = uxi * gux[2] + uyi * gux[3] + uzi * gux[4]
                        - 0.5 * (ci * gux[1] + qxxi * gux[5] + qyyi * gux[8] + qzzi * gux[10]
                                + 2.0 * (qxyi * gux[6] + qxzi * gux[7] + qyzi * gux[9]))
                        - 0.5 * (ci * gc[2] + qxxi * gqxx[2] + qyyi * gqyy[2] + qzzi * gqzz[2]
                                + 2.0 * (qxyi * gqxy[2] + qxzi * gqxz[2] + qyzi * gqyz[2]));
                double fky = uxi * guy[2] + uyi * guy[3] + uzi * guy[4]
                        - 0.5 * (ci * guy[1] + qxxi * guy[5] + qyyi * guy[8] + qzzi * guy[10]
                                + 2.0 * (qxyi * guy[6] + qxzi * guy[7] + qyzi * guy[9]))
                        - 0.5 * (ci * gc[3] + qxxi * gqxx[3] + qyyi * gqyy[3] + qzzi * gqzz[3]
                                + 2.0 * (qxyi * gqxy[3] + qxzi * gqxz[3] + qyzi * gqyz[3]));
                double fkz = uxi * guz[2] + uyi * guz[3] + uzi * guz[4]
                        - 0.5 * (ci * guz[1] + qxxi * guz[5] + qyyi * guz[8] + qzzi * guz[10]
                                + 2.0 * (qxyi * guz[6] + qxzi * guz[7] + qyzi * guz[9]))
                        - 0.5 * (ci * gc[4] + qxxi * gqxx[4] + qyyi * gqyy[4] + qzzi * gqzz[4]
                                + 2.0 * (qxyi * gqxy[4] + qxzi * gqxz[4] + qyzi * gqyz[4]));
                /**
                 * Scale the self-field by half, such that it sums to one below.
                 */
                if (i == k) {
                    fix *= 0.5;
                    fiy *= 0.5;
                    fiz *= 0.5;
                    fkx *= 0.5;
                    fky *= 0.5;
                    fkz *= 0.5;
                }
                fx_local[i] += fix;
                fy_local[i] += fiy;
                fz_local[i] += fiz;
                fx_local[k] += fkx;
                fy_local[k] += fky;
                fz_local[k] += fkz;
            }
        }
    }

    /**
     * Compute the Generalized Kirkwood induced reaction field in parallel.
     *
     * @since 1.0
     */
    private class InducedGKFieldRegion extends ParallelRegion {

        private final InducedGKFieldLoop inducedGKFieldLoop[];

        public InducedGKFieldRegion(int nt) {
            inducedGKFieldLoop = new InducedGKFieldLoop[nt];
            for (int i = 0; i < nt; i++) {
                inducedGKFieldLoop[i] = new InducedGKFieldLoop();
            }
        }

        @Override
        public void start() {
            for (int i = 0; i < nAtoms; i++) {
                sharedGKField[0].set(i, 0.0);
                sharedGKField[1].set(i, 0.0);
                sharedGKField[2].set(i, 0.0);
                sharedGKFieldCR[0].set(i, 0.0);
                sharedGKFieldCR[1].set(i, 0.0);
                sharedGKFieldCR[2].set(i, 0.0);
            }
        }

        @Override
        public void run() {
            try {
                int threadIndex = getThreadIndex();
                execute(0, nAtoms - 1, inducedGKFieldLoop[threadIndex]);
            } catch (Exception e) {
                String message = "Fatal exception computing GK field in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute the Generalized Kirkwood induced reaction field.
         *
         * @since 1.0
         */
        private class InducedGKFieldLoop extends IntegerForLoop {

            private final double a[][];
            private final double gux[], guy[], guz[];
            private double fx_local[];
            private double fy_local[];
            private double fz_local[];
            private double fxCR_local[];
            private double fyCR_local[];
            private double fzCR_local[];
            private final double dx_local[];
            private double xi, yi, zi;
            private double uix, uiy, uiz;
            private double uixCR, uiyCR, uizCR;
            private double rbi;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public InducedGKFieldLoop() {
                a = new double[3][2];
                gux = new double[5];
                guy = new double[5];
                guz = new double[5];
                dx_local = new double[3];
            }

            @Override
            public void start() {
                if (fx_local == null || fx_local.length < maxNumAtoms) {
                    fx_local = new double[maxNumAtoms];
                    fy_local = new double[maxNumAtoms];
                    fz_local = new double[maxNumAtoms];
                    fxCR_local = new double[maxNumAtoms];
                    fyCR_local = new double[maxNumAtoms];
                    fzCR_local = new double[maxNumAtoms];
                }
                fill(fx_local, 0.0);
                fill(fy_local, 0.0);
                fill(fz_local, 0.0);
                fill(fxCR_local, 0.0);
                fill(fyCR_local, 0.0);
                fill(fzCR_local, 0.0);
            }

            @Override
            public void finish() {
                /**
                 * Reduce the field contributions computed by the current thread
                 * into the shared arrays.
                 */
                sharedGKField[0].reduce(fx_local, DoubleOp.SUM);
                sharedGKField[1].reduce(fy_local, DoubleOp.SUM);
                sharedGKField[2].reduce(fz_local, DoubleOp.SUM);
                sharedGKFieldCR[0].reduce(fxCR_local, DoubleOp.SUM);
                sharedGKFieldCR[1].reduce(fyCR_local, DoubleOp.SUM);
                sharedGKFieldCR[2].reduce(fzCR_local, DoubleOp.SUM);
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!use[i]) {
                        continue;
                    }
                    xi = x[i];
                    yi = y[i];
                    zi = z[i];
                    uix = inducedDipole[i][0];
                    uiy = inducedDipole[i][1];
                    uiz = inducedDipole[i][2];
                    uixCR = inducedDipoleCR[i][0];
                    uiyCR = inducedDipoleCR[i][1];
                    uizCR = inducedDipoleCR[i][2];
                    rbi = born[i];
                    int list[] = neighborLists[0][i];
                    int nPair = list.length;
                    for (int l = 0; l < nPair; l++) {
                        int k = list[l];
                        if (!use[k]) {
                            continue;
                        }
                        inducedGKField(i, k);
                    }
                    /**
                     * Include the self induced reaction field, which is not in
                     * the neighbor list.
                     */
                    inducedGKField(i, i);
                }
            }

            private void inducedGKField(int i, int k) {
                dx_local[0] = x[k] - xi;
                dx_local[1] = y[k] - yi;
                dx_local[2] = z[k] - zi;
                final double r2 = crystal.image(dx_local);
                if (r2 > cut2) {
                    return;
                }
                double xr = dx_local[0];
                double yr = dx_local[1];
                double zr = dx_local[2];
                double xr2 = xr * xr;
                double yr2 = yr * yr;
                double zr2 = zr * zr;
                final double ukx = inducedDipole[k][0];
                final double uky = inducedDipole[k][1];
                final double ukz = inducedDipole[k][2];
                final double ukxCR = inducedDipoleCR[k][0];
                final double ukyCR = inducedDipoleCR[k][1];
                final double ukzCR = inducedDipoleCR[k][2];
                final double rbk = born[k];
                final double rb2 = rbi * rbk;
                final double expterm = exp(-r2 / (gkc * rb2));
                final double expc = expterm / gkc;
                final double expc1 = 1.0 - expc;
                final double gf2 = 1.0 / (r2 + rb2 * expterm);
                final double gf = sqrt(gf2);
                final double gf3 = gf2 * gf;
                final double gf5 = gf3 * gf2;
                /**
                 * Reaction potential auxiliary terms.
                 */
                a[1][0] = -gf3;
                a[2][0] = 3.0 * gf5;
                /**
                 * Reaction potential gradient auxiliary term.
                 */
                a[1][1] = expc1 * a[2][0];
                /**
                 * Multiply the potential auxiliary terms by their dielectric
                 * functions.
                 */
                a[1][0] = fd * a[1][0];
                a[1][1] = fd * a[1][1];
                a[2][0] = fq * a[2][0];
                /**
                 * Unweighted reaction potential gradient tensor.
                 */
                gux[2] = a[1][0] + xr2 * a[1][1];
                gux[3] = xr * yr * a[1][1];
                gux[4] = xr * zr * a[1][1];
                guy[2] = gux[3];
                guy[3] = a[1][0] + yr2 * a[1][1];
                guy[4] = yr * zr * a[1][1];
                guz[2] = gux[4];
                guz[3] = guy[4];
                guz[4] = a[1][0] + zr2 * a[1][1];
                /**
                 * Compute the reaction field due to induced dipoles.
                 */
                double fix = ukx * gux[2] + uky * guy[2] + ukz * guz[2];
                double fiy = ukx * gux[3] + uky * guy[3] + ukz * guz[3];
                double fiz = ukx * gux[4] + uky * guy[4] + ukz * guz[4];
                double fkx = uix * gux[2] + uiy * guy[2] + uiz * guz[2];
                double fky = uix * gux[3] + uiy * guy[3] + uiz * guz[3];
                double fkz = uix * gux[4] + uiy * guy[4] + uiz * guz[4];
                double fixCR = ukxCR * gux[2] + ukyCR * guy[2] + ukzCR * guz[2];
                double fiyCR = ukxCR * gux[3] + ukyCR * guy[3] + ukzCR * guz[3];
                double fizCR = ukxCR * gux[4] + ukyCR * guy[4] + ukzCR * guz[4];
                double fkxCR = uixCR * gux[2] + uiyCR * guy[2] + uizCR * guz[2];
                double fkyCR = uixCR * gux[3] + uiyCR * guy[3] + uizCR * guz[3];
                double fkzCR = uixCR * gux[4] + uiyCR * guy[4] + uizCR * guz[4];
                /**
                 * Scale the self-field by half, such that it sums to one below.
                 */
                if (i == k) {
                    fix *= 0.5;
                    fiy *= 0.5;
                    fiz *= 0.5;
                    fkx *= 0.5;
                    fky *= 0.5;
                    fkz *= 0.5;
                    fixCR *= 0.5;
                    fiyCR *= 0.5;
                    fizCR *= 0.5;
                    fkxCR *= 0.5;
                    fkyCR *= 0.5;
                    fkzCR *= 0.5;
                }
                fx_local[i] += fix;
                fy_local[i] += fiy;
                fz_local[i] += fiz;
                fx_local[k] += fkx;
                fy_local[k] += fky;
                fz_local[k] += fkz;
                fxCR_local[i] += fixCR;
                fyCR_local[i] += fiyCR;
                fzCR_local[i] += fizCR;
                fxCR_local[k] += fkxCR;
                fyCR_local[k] += fkyCR;
                fzCR_local[k] += fkzCR;
            }
        }
    }

    /**
     * Compute the Generalized Kirkwood reaction field energy.
     *
     * @since 1.0
     */
    private class GKEnergyRegion extends ParallelRegion {

        private boolean gradient = false;
        private final SharedDouble sharedGKEnergy;
        private final SharedInteger sharedInteractions;
        private final GKEnergyLoop gkEnergyLoop[];

        public GKEnergyRegion(int nt) {
            gkEnergyLoop = new GKEnergyLoop[nt];
            for (int i = 0; i < nt; i++) {
                gkEnergyLoop[i] = new GKEnergyLoop();
            }
            sharedGKEnergy = new SharedDouble();
            sharedInteractions = new SharedInteger();
        }

        public void setGradient(boolean gradient) {
            this.gradient = gradient;
        }

        public double getEnergy() {
            return sharedGKEnergy.get();
        }

        public int getInteractions() {
            return sharedInteractions.get();
        }

        @Override
        public void start() {
            sharedGKEnergy.set(0.0);
            sharedInteractions.set(0);
        }

        @Override
        public void run() {
            try {
                int threadIndex = getThreadIndex();
                gkEnergyLoop[threadIndex].setGradient(gradient);
                execute(0, nAtoms - 1, gkEnergyLoop[threadIndex]);
            } catch (Exception e) {
                String message = "Fatal exception computing GK Energy in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute Born radii for a range of atoms via the Grycuk method.
         *
         * @since 1.0
         */
        private class GKEnergyLoop extends IntegerForLoop {

            private final double a[][];
            private final double b[][];
            private final double gc[];
            private final double gux[], guy[], guz[];
            private final double gqxx[], gqyy[], gqzz[];
            private final double gqxy[], gqxz[], gqyz[];
            private double gb_local[];
            private double gbi_local[];
            private final double dx_local[];
            private double gX[];
            private double gY[];
            private double gZ[];
            private double tX[];
            private double tY[];
            private double tZ[];
            private double lgX[];
            private double lgY[];
            private double lgZ[];
            private double ltX[];
            private double ltY[];
            private double ltZ[];
            private double ci, uxi, uyi, uzi, qxxi, qxyi, qxzi, qyyi, qyzi, qzzi;
            private double ck, uxk, uyk, uzk, qxxk, qxyk, qxzk, qyyk, qyzk, qzzk;
            private double dxi, dyi, dzi, pxi, pyi, pzi, sxi, syi, szi;
            private double dxk, dyk, dzk, pxk, pyk, pzk, sxk, syk, szk;
            private double xr, yr, zr, xr2, yr2, zr2, r2, rbi, rbk;
            private double xi, yi, zi;
            private boolean gradient = false;
            private int count;
            private double gkEnergy;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public GKEnergyLoop() {
                a = new double[6][4];
                b = new double[5][3];
                gc = new double[31];
                gux = new double[31];
                guy = new double[31];
                guz = new double[31];
                gqxx = new double[31];
                gqyy = new double[31];
                gqzz = new double[31];
                gqxy = new double[31];
                gqxz = new double[31];
                gqyz = new double[31];
                dx_local = new double[3];
            }

            public void setGradient(boolean gradient) {
                this.gradient = gradient;
            }

            @Override
            public void start() {

                if (gb_local == null || gb_local.length < nAtoms) {
                    gb_local = new double[nAtoms];
                    gbi_local = new double[nAtoms];
                }

                gkEnergy = 0.0;
                count = 0;
                int threadID = getThreadIndex();
                gX = grad[threadID][0];
                gY = grad[threadID][1];
                gZ = grad[threadID][2];
                tX = torque[threadID][0];
                tY = torque[threadID][1];
                tZ = torque[threadID][2];
                if (gradient) {
                    fill(gb_local, 0.0);
                    fill(gbi_local, 0.0);
                }
                if (lambdaTerm) {
                    lgX = lambdaGrad[threadID][0];
                    lgY = lambdaGrad[threadID][1];
                    lgZ = lambdaGrad[threadID][2];
                    ltX = lambdaTorque[threadID][0];
                    ltY = lambdaTorque[threadID][1];
                    ltZ = lambdaTorque[threadID][2];
                }
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!use[i]) {
                        continue;
                    }
                    xi = x[i];
                    yi = y[i];
                    zi = z[i];
                    final double multipolei[] = globalMultipole[i];
                    ci = multipolei[t000];
                    uxi = multipolei[t100];
                    uyi = multipolei[t010];
                    uzi = multipolei[t001];
                    qxxi = multipolei[t200] / 3.0;
                    qxyi = multipolei[t110] / 3.0;
                    qxzi = multipolei[t101] / 3.0;
                    qyyi = multipolei[t020] / 3.0;
                    qyzi = multipolei[t011] / 3.0;
                    qzzi = multipolei[t002] / 3.0;
                    dxi = inducedDipole[i][0];
                    dyi = inducedDipole[i][1];
                    dzi = inducedDipole[i][2];
                    pxi = inducedDipoleCR[i][0];
                    pyi = inducedDipoleCR[i][1];
                    pzi = inducedDipoleCR[i][2];
                    sxi = dxi + pxi;
                    syi = dyi + pyi;
                    szi = dzi + pzi;
                    rbi = born[i];
                    int list[] = neighborLists[0][i];
                    int nPair = list.length;
                    for (int l = 0; l < nPair; l++) {
                        int k = list[l];
                        if (!use[k]) {
                            continue;
                        }
                        interaction(i, k);
                    }
                    // Include the self-interaction.
                    interaction(i, i);

                    /**
                     * Formula for Born energy approximation is: e = ai * 4.0*pi
                     * * (ri + probe)^2 * (ri/rb)^6. ri is baseRadius, rb is
                     * Born radius of given atom. ai is an empirical constant
                     * for the atom. If ai is too low, everything wants to pack
                     * into a solid ball, and if ai is too high, everything
                     * wants to unfold and be as solvent-exposed as possible.
                     *
                     * The bornaiTerm is a precalculated 4 * pi * ai value.
                     *
                     * Fix this.
                     */
                    switch (nonPolar) {
                    case BORN_SOLV:
                    case BORN_CAV_DISP:
                        double e = baseRadiusWithBondi[i] + probe; // e = ri + probe
                        e *= (e * bornaiTerm);// e = (ri + probe) * ((ri + probe) * 4 * pi * ai)
                        double rirb = baseRadiusWithBondi[i] / born[i]; // ri/rb^6
                        rirb *= rirb;
                        rirb *= (rirb * rirb);
                        e *= rirb; // e = consts * (ri/rb)^6
                        gkEnergy += e;
                        // Now calculate derivatives
                        e *= (-6.0 / born[i]); // e = consts * -6*ri^6 / rb^-7
                        // To get each derivative, would multiple
                        gb_local[i] += e;
                        break;
                    default:
                        break;
                    }
                }
            }

            @Override
            public void finish() {
                sharedInteractions.addAndGet(count);
                sharedGKEnergy.addAndGet(gkEnergy);
                if (gradient) {
                    /**
                     * Reduce the torque contributions computed by the current
                     * thread into the shared array.
                     */
                    sharedBornGrad.reduce(gb_local, DoubleOp.SUM);
                }
            }

            private void interaction(int i, int k) {
                dx_local[0] = x[k] - xi;
                dx_local[1] = y[k] - yi;
                dx_local[2] = z[k] - zi;
                r2 = crystal.image(dx_local);
                if (r2 > cut2) {
                    return;
                }
                xr = dx_local[0];
                yr = dx_local[1];
                zr = dx_local[2];
                xr2 = xr * xr;
                yr2 = yr * yr;
                zr2 = zr * zr;
                rbk = born[k];
                final double multipolek[] = globalMultipole[k];
                ck = multipolek[t000];
                uxk = multipolek[t100];
                uyk = multipolek[t010];
                uzk = multipolek[t001];
                qxxk = multipolek[t200] / 3.0;
                qxyk = multipolek[t110] / 3.0;
                qxzk = multipolek[t101] / 3.0;
                qyyk = multipolek[t020] / 3.0;
                qyzk = multipolek[t011] / 3.0;
                qzzk = multipolek[t002] / 3.0;
                dxk = inducedDipole[k][0];
                dyk = inducedDipole[k][1];
                dzk = inducedDipole[k][2];
                pxk = inducedDipoleCR[k][0];
                pyk = inducedDipoleCR[k][1];
                pzk = inducedDipoleCR[k][2];
                sxk = dxk + pxk;
                syk = dyk + pyk;
                szk = dzk + pzk;
                final double rb2 = rbi * rbk;
                final double expterm = exp(-r2 / (gkc * rb2));
                final double expc = expterm / gkc;
                final double expc1 = 1.0 - expc;
                final double expcr = r2 * expterm / (gkc * gkc * rb2 * rb2);
                final double dexpc = -2.0 / (gkc * rb2);
                double expcdexpc = -expc * dexpc;
                final double dexpcr = 2.0 / (gkc * rb2 * rb2);
                final double dgfdr = 0.5 * expterm * (1.0 + r2 / (rb2 * gkc));
                final double gf2 = 1.0 / (r2 + rb2 * expterm);
                final double gf = sqrt(gf2);
                final double gf3 = gf2 * gf;
                final double gf5 = gf3 * gf2;
                final double gf7 = gf5 * gf2;
                final double gf9 = gf7 * gf2;
                final double gf11 = gf9 * gf2;
                /**
                 * Reaction potential auxiliary terms.
                 */
                a[0][0] = gf;
                a[1][0] = -gf3;
                a[2][0] = 3.0 * gf5;
                a[3][0] = -15.0 * gf7;
                a[4][0] = 105.0 * gf9;
                a[5][0] = -945.0 * gf11;
                /**
                 * Reaction potential gradient auxiliary terms.
                 */
                a[0][1] = expc1 * a[1][0];
                a[1][1] = expc1 * a[2][0];
                a[2][1] = expc1 * a[3][0];
                a[3][1] = expc1 * a[4][0];
                a[4][1] = expc1 * a[5][0];
                /**
                 * 2nd reaction potential gradient auxiliary terms.
                 */
                a[0][2] = expc1 * a[1][1] + expcdexpc * a[1][0];
                a[1][2] = expc1 * a[2][1] + expcdexpc * a[2][0];
                a[2][2] = expc1 * a[3][1] + expcdexpc * a[3][0];
                a[3][2] = expc1 * a[4][1] + expcdexpc * a[4][0];

                if (gradient) {
                    /**
                     * 3rd reaction potential gradient auxiliary terms.
                     */
                    expcdexpc = 2.0 * expcdexpc;
                    a[0][3] = expc1 * a[1][2] + expcdexpc * a[1][1];
                    a[1][3] = expc1 * a[2][2] + expcdexpc * a[2][1];
                    a[2][3] = expc1 * a[3][2] + expcdexpc * a[3][1];
                    expcdexpc = -expc * dexpc * dexpc;
                    a[0][3] = a[0][3] + expcdexpc * a[1][0];
                    a[1][3] = a[1][3] + expcdexpc * a[2][0];
                    a[2][3] = a[2][3] + expcdexpc * a[3][0];
                    /**
                     * Born radii derivatives of reaction potential auxiliary
                     * terms.
                     */
                    b[0][0] = dgfdr * a[1][0];
                    b[1][0] = dgfdr * a[2][0];
                    b[2][0] = dgfdr * a[3][0];
                    b[3][0] = dgfdr * a[4][0];
                    b[4][0] = dgfdr * a[5][0];
                    /**
                     * Born radii gradients of reaction potential gradient
                     * auxiliary terms.
                     */
                    b[0][1] = b[1][0] - expcr * a[1][0] - expc * b[1][0];
                    b[1][1] = b[2][0] - expcr * a[2][0] - expc * b[2][0];
                    b[2][1] = b[3][0] - expcr * a[3][0] - expc * b[3][0];
                    b[3][1] = b[4][0] - expcr * a[4][0] - expc * b[4][0];
                    /**
                     * Born radii derivatives of the 2nd reaction potential
                     * gradient auxiliary terms.
                     */
                    b[0][2] = b[1][1] - (expcr * (a[1][1] + dexpc * a[1][0])
                            + expc * (b[1][1] + dexpcr * a[1][0] + dexpc * b[1][0]));
                    b[1][2] = b[2][1] - (expcr * (a[2][1] + dexpc * a[2][0])
                            + expc * (b[2][1] + dexpcr * a[2][0] + dexpc * b[2][0]));
                    b[2][2] = b[3][1] - (expcr * (a[3][1] + dexpc * a[3][0])
                            + expc * (b[3][1] + dexpcr * a[3][0] + dexpc * b[3][0]));
                    /**
                     * Multiply the Born radii auxiliary terms by their
                     * dielectric functions.
                     */
                    b[0][0] = ELECTRIC * fc * b[0][0];
                    b[0][1] = ELECTRIC * fc * b[0][1];
                    b[0][2] = ELECTRIC * fc * b[0][2];
                    b[1][0] = ELECTRIC * fd * b[1][0];
                    b[1][1] = ELECTRIC * fd * b[1][1];
                    b[1][2] = ELECTRIC * fd * b[1][2];
                    b[2][0] = ELECTRIC * fq * b[2][0];
                    b[2][1] = ELECTRIC * fq * b[2][1];
                    b[2][2] = ELECTRIC * fq * b[2][2];
                }

                /**
                 * Multiply the potential auxiliary terms by their dielectric
                 * functions.
                 */
                a[0][0] = ELECTRIC * fc * a[0][0];
                a[0][1] = ELECTRIC * fc * a[0][1];
                a[0][2] = ELECTRIC * fc * a[0][2];
                a[0][3] = ELECTRIC * fc * a[0][3];
                a[1][0] = ELECTRIC * fd * a[1][0];
                a[1][1] = ELECTRIC * fd * a[1][1];
                a[1][2] = ELECTRIC * fd * a[1][2];
                a[1][3] = ELECTRIC * fd * a[1][3];
                a[2][0] = ELECTRIC * fq * a[2][0];
                a[2][1] = ELECTRIC * fq * a[2][1];
                a[2][2] = ELECTRIC * fq * a[2][2];
                a[2][3] = ELECTRIC * fq * a[2][3];
                /**
                 * Compute the GK tensors required to compute the energy.
                 */
                energyTensors();
                /**
                 * Compute the GK interac tion energy.
                 */
                gkEnergy += energy(i, k);
                count++;
                if (gradient || lambdaTerm) {
                    /**
                     * Compute the additional GK tensors required to compute the
                     * energy gradient.
                     */
                    gradientTensors();
                    /**
                     * Compute the permanent GK energy gradient.
                     */
                    permanentEnergyGradient(i, k);
                    if (polarization != Polarization.NONE) {
                        /**
                         * Compute the induced GK energy gradient.
                         */
                        polarizationEnergyGradient(i, k);
                    }
                }
            }

            private void energyTensors() {
                /**
                 * Unweighted reaction potential tensor.
                 */
                gc[1] = a[0][0];
                gux[1] = xr * a[1][0];
                guy[1] = yr * a[1][0];
                guz[1] = zr * a[1][0];
                gqxx[1] = xr2 * a[2][0];
                gqyy[1] = yr2 * a[2][0];
                gqzz[1] = zr2 * a[2][0];
                gqxy[1] = xr * yr * a[2][0];
                gqxz[1] = xr * zr * a[2][0];
                gqyz[1] = yr * zr * a[2][0];
                /**
                 * Unweighted reaction potential gradient tensor.
                 */
                gc[2] = xr * a[0][1];
                gc[3] = yr * a[0][1];
                gc[4] = zr * a[0][1];
                gux[2] = a[1][0] + xr2 * a[1][1];
                gux[3] = xr * yr * a[1][1];
                gux[4] = xr * zr * a[1][1];
                guy[2] = gux[3];
                guy[3] = a[1][0] + yr2 * a[1][1];
                guy[4] = yr * zr * a[1][1];
                guz[2] = gux[4];
                guz[3] = guy[4];
                guz[4] = a[1][0] + zr2 * a[1][1];
                gqxx[2] = xr * (2.0 * a[2][0] + xr2 * a[2][1]);
                gqxx[3] = yr * xr2 * a[2][1];
                gqxx[4] = zr * xr2 * a[2][1];
                gqyy[2] = xr * yr2 * a[2][1];
                gqyy[3] = yr * (2.0 * a[2][0] + yr2 * a[2][1]);
                gqyy[4] = zr * yr2 * a[2][1];
                gqzz[2] = xr * zr2 * a[2][1];
                gqzz[3] = yr * zr2 * a[2][1];
                gqzz[4] = zr * (2.0 * a[2][0] + zr2 * a[2][1]);
                gqxy[2] = yr * (a[2][0] + xr2 * a[2][1]);
                gqxy[3] = xr * (a[2][0] + yr2 * a[2][1]);
                gqxy[4] = zr * xr * yr * a[2][1];
                gqxz[2] = zr * (a[2][0] + xr2 * a[2][1]);
                gqxz[3] = gqxy[4];
                gqxz[4] = xr * (a[2][0] + zr2 * a[2][1]);
                gqyz[2] = gqxy[4];
                gqyz[3] = zr * (a[2][0] + yr2 * a[2][1]);
                gqyz[4] = yr * (a[2][0] + zr2 * a[2][1]);
                /**
                 * Unweighted 2nd reaction potential gradient tensor.
                 */
                gc[5] = a[0][1] + xr2 * a[0][2];
                gc[6] = xr * yr * a[0][2];
                gc[7] = xr * zr * a[0][2];
                gc[8] = a[0][1] + yr2 * a[0][2];
                gc[9] = yr * zr * a[0][2];
                gc[10] = a[0][1] + zr2 * a[0][2];
                gux[5] = xr * (3.0 * a[1][1] + xr2 * a[1][2]);
                gux[6] = yr * (a[1][1] + xr2 * a[1][2]);
                gux[7] = zr * (a[1][1] + xr2 * a[1][2]);
                gux[8] = xr * (a[1][1] + yr2 * a[1][2]);
                gux[9] = zr * xr * yr * a[1][2];
                gux[10] = xr * (a[1][1] + zr2 * a[1][2]);
                guy[5] = yr * (a[1][1] + xr2 * a[1][2]);
                guy[6] = xr * (a[1][1] + yr2 * a[1][2]);
                guy[7] = gux[9];
                guy[8] = yr * (3.0 * a[1][1] + yr2 * a[1][2]);
                guy[9] = zr * (a[1][1] + yr2 * a[1][2]);
                guy[10] = yr * (a[1][1] + zr2 * a[1][2]);
                guz[5] = zr * (a[1][1] + xr2 * a[1][2]);
                guz[6] = gux[9];
                guz[7] = xr * (a[1][1] + zr2 * a[1][2]);
                guz[8] = zr * (a[1][1] + yr2 * a[1][2]);
                guz[9] = yr * (a[1][1] + zr2 * a[1][2]);
                guz[10] = zr * (3.0 * a[1][1] + zr2 * a[1][2]);
                gqxx[5] = 2.0 * a[2][0] + xr2 * (5.0 * a[2][1] + xr2 * a[2][2]);
                gqxx[6] = yr * xr * (2.0 * a[2][1] + xr2 * a[2][2]);
                gqxx[7] = zr * xr * (2.0 * a[2][1] + xr2 * a[2][2]);
                gqxx[8] = xr2 * (a[2][1] + yr2 * a[2][2]);
                gqxx[9] = zr * yr * xr2 * a[2][2];
                gqxx[10] = xr2 * (a[2][1] + zr2 * a[2][2]);
                gqyy[5] = yr2 * (a[2][1] + xr2 * a[2][2]);
                gqyy[6] = xr * yr * (2.0 * a[2][1] + yr2 * a[2][2]);
                gqyy[7] = xr * zr * yr2 * a[2][2];
                gqyy[8] = 2.0 * a[2][0] + yr2 * (5.0 * a[2][1] + yr2 * a[2][2]);
                gqyy[9] = yr * zr * (2.0 * a[2][1] + yr2 * a[2][2]);
                gqyy[10] = yr2 * (a[2][1] + zr2 * a[2][2]);
                gqzz[5] = zr2 * (a[2][1] + xr2 * a[2][2]);
                gqzz[6] = xr * yr * zr2 * a[2][2];
                gqzz[7] = xr * zr * (2.0 * a[2][1] + zr2 * a[2][2]);
                gqzz[8] = zr2 * (a[2][1] + yr2 * a[2][2]);
                gqzz[9] = yr * zr * (2.0 * a[2][1] + zr2 * a[2][2]);
                gqzz[10] = 2.0 * a[2][0] + zr2 * (5.0 * a[2][1] + zr2 * a[2][2]);
                gqxy[5] = xr * yr * (3.0 * a[2][1] + xr2 * a[2][2]);
                gqxy[6] = a[2][0] + (xr2 + yr2) * a[2][1] + xr2 * yr2 * a[2][2];
                gqxy[7] = zr * yr * (a[2][1] + xr2 * a[2][2]);
                gqxy[8] = xr * yr * (3.0 * a[2][1] + yr2 * a[2][2]);
                gqxy[9] = zr * xr * (a[2][1] + yr2 * a[2][2]);
                gqxy[10] = xr * yr * (a[2][1] + zr2 * a[2][2]);
                gqxz[5] = xr * zr * (3.0 * a[2][1] + xr2 * a[2][2]);
                gqxz[6] = yr * zr * (a[2][1] + xr2 * a[2][2]);
                gqxz[7] = a[2][0] + (xr2 + zr2) * a[2][1] + xr2 * zr2 * a[2][2];
                gqxz[8] = xr * zr * (a[2][1] + yr2 * a[2][2]);
                gqxz[9] = xr * yr * (a[2][1] + zr2 * a[2][2]);
                gqxz[10] = xr * zr * (3.0 * a[2][1] + zr2 * a[2][2]);
                gqyz[5] = zr * yr * (a[2][1] + xr2 * a[2][2]);
                gqyz[6] = xr * zr * (a[2][1] + yr2 * a[2][2]);
                gqyz[7] = xr * yr * (a[2][1] + zr2 * a[2][2]);
                gqyz[8] = yr * zr * (3.0 * a[2][1] + yr2 * a[2][2]);
                gqyz[9] = a[2][0] + (yr2 + zr2) * a[2][1] + yr2 * zr2 * a[2][2];
                gqyz[10] = yr * zr * (3.0 * a[2][1] + zr2 * a[2][2]);
            }

            private double energy(int i, int k) {
                /**
                 * Electrostatic solvation energy of the permanent multipoles in
                 * their own GK reaction potential.
                 */
                double esym = ci * ck * gc[1] - (uxi * (uxk * gux[2] + uyk * guy[2] + uzk * guz[2])
                        + uyi * (uxk * gux[3] + uyk * guy[3] + uzk * guz[3])
                        + uzi * (uxk * gux[4] + uyk * guy[4] + uzk * guz[4]));
                double ewi = ci * (uxk * gc[2] + uyk * gc[3] + uzk * gc[4])
                        - ck * (uxi * gux[1] + uyi * guy[1] + uzi * guz[1])
                        + ci * (qxxk * gc[5] + qyyk * gc[8] + qzzk * gc[10]
                                + 2.0 * (qxyk * gc[6] + qxzk * gc[7] + qyzk * gc[9]))
                        + ck * (qxxi * gqxx[1] + qyyi * gqyy[1] + qzzi * gqzz[1]
                                + 2.0 * (qxyi * gqxy[1] + qxzi * gqxz[1] + qyzi * gqyz[1]))
                        - uxi * (qxxk * gux[5] + qyyk * gux[8] + qzzk * gux[10]
                                + 2.0 * (qxyk * gux[6] + qxzk * gux[7] + qyzk * gux[9]))
                        - uyi * (qxxk * guy[5] + qyyk * guy[8] + qzzk * guy[10]
                                + 2.0 * (qxyk * guy[6] + qxzk * guy[7] + qyzk * guy[9]))
                        - uzi * (qxxk * guz[5] + qyyk * guz[8] + qzzk * guz[10]
                                + 2.0 * (qxyk * guz[6] + qxzk * guz[7] + qyzk * guz[9]))
                        + uxk * (qxxi * gqxx[2] + qyyi * gqyy[2] + qzzi * gqzz[2]
                                + 2.0 * (qxyi * gqxy[2] + qxzi * gqxz[2] + qyzi * gqyz[2]))
                        + uyk * (qxxi * gqxx[3] + qyyi * gqyy[3] + qzzi * gqzz[3]
                                + 2.0 * (qxyi * gqxy[3] + qxzi * gqxz[3] + qyzi * gqyz[3]))
                        + uzk * (qxxi * gqxx[4] + qyyi * gqyy[4] + qzzi * gqzz[4]
                                + 2.0 * (qxyi * gqxy[4] + qxzi * gqxz[4] + qyzi * gqyz[4]))
                        + qxxi * (qxxk * gqxx[5] + qyyk * gqxx[8] + qzzk * gqxx[10]
                                + 2.0 * (qxyk * gqxx[6] + qxzk * gqxx[7] + qyzk * gqxx[9]))
                        + qyyi * (qxxk * gqyy[5] + qyyk * gqyy[8] + qzzk * gqyy[10]
                                + 2.0 * (qxyk * gqyy[6] + qxzk * gqyy[7] + qyzk * gqyy[9]))
                        + qzzi * (qxxk * gqzz[5] + qyyk * gqzz[8] + qzzk * gqzz[10]
                                + 2.0 * (qxyk * gqzz[6] + qxzk * gqzz[7] + qyzk * gqzz[9]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxy[5] + qyyk * gqxy[8] + qzzk * gqxy[10]
                                        + 2.0 * (qxyk * gqxy[6] + qxzk * gqxy[7] + qyzk * gqxy[9]))
                                + qxzi * (qxxk * gqxz[5] + qyyk * gqxz[8] + qzzk * gqxz[10]
                                        + 2.0 * (qxyk * gqxz[6] + qxzk * gqxz[7] + qyzk * gqxz[9]))
                                + qyzi * (qxxk * gqyz[5] + qyyk * gqyz[8] + qzzk * gqyz[10]
                                        + 2.0 * (qxyk * gqyz[6] + qxzk * gqyz[7] + qyzk * gqyz[9])));
                double ewk = ci * (uxk * gux[1] + uyk * guy[1] + uzk * guz[1])
                        - ck * (uxi * gc[2] + uyi * gc[3] + uzi * gc[4])
                        + ci * (qxxk * gqxx[1] + qyyk * gqyy[1] + qzzk * gqzz[1]
                                + 2.0 * (qxyk * gqxy[1] + qxzk * gqxz[1] + qyzk * gqyz[1]))
                        + ck * (qxxi * gc[5] + qyyi * gc[8] + qzzi * gc[10]
                                + 2.0 * (qxyi * gc[6] + qxzi * gc[7] + qyzi * gc[9]))
                        - uxi * (qxxk * gqxx[2] + qyyk * gqyy[2] + qzzk * gqzz[2]
                                + 2.0 * (qxyk * gqxy[2] + qxzk * gqxz[2] + qyzk * gqyz[2]))
                        - uyi * (qxxk * gqxx[3] + qyyk * gqyy[3] + qzzk * gqzz[3]
                                + 2.0 * (qxyk * gqxy[3] + qxzk * gqxz[3] + qyzk * gqyz[3]))
                        - uzi * (qxxk * gqxx[4] + qyyk * gqyy[4] + qzzk * gqzz[4]
                                + 2.0 * (qxyk * gqxy[4] + qxzk * gqxz[4] + qyzk * gqyz[4]))
                        + uxk * (qxxi * gux[5] + qyyi * gux[8] + qzzi * gux[10]
                                + 2.0 * (qxyi * gux[6] + qxzi * gux[7] + qyzi * gux[9]))
                        + uyk * (qxxi * guy[5] + qyyi * guy[8] + qzzi * guy[10]
                                + 2.0 * (qxyi * guy[6] + qxzi * guy[7] + qyzi * guy[9]))
                        + uzk * (qxxi * guz[5] + qyyi * guz[8] + qzzi * guz[10]
                                + 2.0 * (qxyi * guz[6] + qxzi * guz[7] + qyzi * guz[9]))
                        + qxxi * (qxxk * gqxx[5] + qyyk * gqyy[5] + qzzk * gqzz[5]
                                + 2.0 * (qxyk * gqxy[5] + qxzk * gqxz[5] + qyzk * gqyz[5]))
                        + qyyi * (qxxk * gqxx[8] + qyyk * gqyy[8] + qzzk * gqzz[8]
                                + 2.0 * (qxyk * gqxy[8] + qxzk * gqxz[8] + qyzk * gqyz[8]))
                        + qzzi * (qxxk * gqxx[10] + qyyk * gqyy[10] + qzzk * gqzz[10]
                                + 2.0 * (qxyk * gqxy[10] + qxzk * gqxz[10] + qyzk * gqyz[10]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                                        + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]))
                                + qxzi * (qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                                        + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]))
                                + qyzi * (qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                                        + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9])));
                double e = esym + 0.5 * (ewi + ewk);
                double ei = 0.0;
                if (polarization != Polarization.NONE) {
                    /**
                     * Electrostatic solvation energy of the permanent
                     * multipoles in the GK reaction potential of the induced
                     * dipoles.
                     */
                    double esymi = -uxi * (dxk * gux[2] + dyk * guy[2] + dzk * guz[2])
                            - uyi * (dxk * gux[3] + dyk * guy[3] + dzk * guz[3])
                            - uzi * (dxk * gux[4] + dyk * guy[4] + dzk * guz[4])
                            - uxk * (dxi * gux[2] + dyi * guy[2] + dzi * guz[2])
                            - uyk * (dxi * gux[3] + dyi * guy[3] + dzi * guz[3])
                            - uzk * (dxi * gux[4] + dyi * guy[4] + dzi * guz[4]);
                    double ewii = ci * (dxk * gc[2] + dyk * gc[3] + dzk * gc[4])
                            - ck * (dxi * gux[1] + dyi * guy[1] + dzi * guz[1])
                            - dxi * (qxxk * gux[5] + qyyk * gux[8] + qzzk * gux[10]
                                    + 2.0 * (qxyk * gux[6] + qxzk * gux[7] + qyzk * gux[9]))
                            - dyi * (qxxk * guy[5] + qyyk * guy[8] + qzzk * guy[10]
                                    + 2.0 * (qxyk * guy[6] + qxzk * guy[7] + qyzk * guy[9]))
                            - dzi * (qxxk * guz[5] + qyyk * guz[8] + qzzk * guz[10]
                                    + 2.0 * (qxyk * guz[6] + qxzk * guz[7] + qyzk * guz[9]))
                            + dxk * (qxxi * gqxx[2] + qyyi * gqyy[2] + qzzi * gqzz[2]
                                    + 2.0 * (qxyi * gqxy[2] + qxzi * gqxz[2] + qyzi * gqyz[2]))
                            + dyk * (qxxi * gqxx[3] + qyyi * gqyy[3] + qzzi * gqzz[3]
                                    + 2.0 * (qxyi * gqxy[3] + qxzi * gqxz[3] + qyzi * gqyz[3]))
                            + dzk * (qxxi * gqxx[4] + qyyi * gqyy[4] + qzzi * gqzz[4]
                                    + 2.0 * (qxyi * gqxy[4] + qxzi * gqxz[4] + qyzi * gqyz[4]));
                    double ewki = ci * (dxk * gux[1] + dyk * guy[1] + dzk * guz[1])
                            - ck * (dxi * gc[2] + dyi * gc[3] + dzi * gc[4])
                            - dxi * (qxxk * gqxx[2] + qyyk * gqyy[2] + qzzk * gqzz[2]
                                    + 2.0 * (qxyk * gqxy[2] + qxzk * gqxz[2] + qyzk * gqyz[2]))
                            - dyi * (qxxk * gqxx[3] + qyyk * gqyy[3] + qzzk * gqzz[3]
                                    + 2.0 * (qxyk * gqxy[3] + qxzk * gqxz[3] + qyzk * gqyz[3]))
                            - dzi * (qxxk * gqxx[4] + qyyk * gqyy[4] + qzzk * gqzz[4]
                                    + 2.0 * (qxyk * gqxy[4] + qxzk * gqxz[4] + qyzk * gqyz[4]))
                            + dxk * (qxxi * gux[5] + qyyi * gux[8] + qzzi * gux[10]
                                    + 2.0 * (qxyi * gux[6] + qxzi * gux[7] + qyzi * gux[9]))
                            + dyk * (qxxi * guy[5] + qyyi * guy[8] + qzzi * guy[10]
                                    + 2.0 * (qxyi * guy[6] + qxzi * guy[7] + qyzi * guy[9]))
                            + dzk * (qxxi * guz[5] + qyyi * guz[8] + qzzi * guz[10]
                                    + 2.0 * (qxyi * guz[6] + qxzi * guz[7] + qyzi * guz[9]));
                    ei = 0.5 * (esymi + 0.5 * (ewii + ewki));
                }
                if (i == k) {
                    e *= 0.5;
                    ei *= 0.5;
                }
                return e + ei;
            }

            private void gradientTensors() {
                /**
                 * Born radii gradients of unweighted reaction potential tensor.
                 */
                gc[21] = b[0][0];
                gux[21] = xr * b[1][0];
                guy[21] = yr * b[1][0];
                guz[21] = zr * b[1][0];
                gqxx[21] = xr2 * b[2][0];
                gqyy[21] = yr2 * b[2][0];
                gqzz[21] = zr2 * b[2][0];
                gqxy[21] = xr * yr * b[2][0];
                gqxz[21] = xr * zr * b[2][0];
                gqyz[21] = yr * zr * b[2][0];
                /**
                 * Born gradients of the unweighted reaction potential gradient
                 * tensor
                 */
                gc[22] = xr * b[0][1];
                gc[23] = yr * b[0][1];
                gc[24] = zr * b[0][1];
                gux[22] = b[1][0] + xr2 * b[1][1];
                gux[23] = xr * yr * b[1][1];
                gux[24] = xr * zr * b[1][1];
                guy[22] = gux[23];
                guy[23] = b[1][0] + yr2 * b[1][1];
                guy[24] = yr * zr * b[1][1];
                guz[22] = gux[24];
                guz[23] = guy[24];
                guz[24] = b[1][0] + zr2 * b[1][1];
                gqxx[22] = xr * (2.0 * b[2][0] + xr2 * b[2][1]);
                gqxx[23] = yr * xr2 * b[2][1];
                gqxx[24] = zr * xr2 * b[2][1];
                gqyy[22] = xr * yr2 * b[2][1];
                gqyy[23] = yr * (2.0 * b[2][0] + yr2 * b[2][1]);
                gqyy[24] = zr * yr2 * b[2][1];
                gqzz[22] = xr * zr2 * b[2][1];
                gqzz[23] = yr * zr2 * b[2][1];
                gqzz[24] = zr * (2.0 * b[2][0] + zr2 * b[2][1]);
                gqxy[22] = yr * (b[2][0] + xr2 * b[2][1]);
                gqxy[23] = xr * (b[2][0] + yr2 * b[2][1]);
                gqxy[24] = zr * xr * yr * b[2][1];
                gqxz[22] = zr * (b[2][0] + xr2 * b[2][1]);
                gqxz[23] = gqxy[24];
                gqxz[24] = xr * (b[2][0] + zr2 * b[2][1]);
                gqyz[22] = gqxy[24];
                gqyz[23] = zr * (b[2][0] + yr2 * b[2][1]);
                gqyz[24] = yr * (b[2][0] + zr2 * b[2][1]);
                /**
                 * Born radii derivatives of the unweighted 2nd reaction
                 * potential gradient tensor.
                 */
                gc[25] = b[0][1] + xr2 * b[0][2];
                gc[26] = xr * yr * b[0][2];
                gc[27] = xr * zr * b[0][2];
                gc[28] = b[0][1] + yr2 * b[0][2];
                gc[29] = yr * zr * b[0][2];
                gc[30] = b[0][1] + zr2 * b[0][2];
                gux[25] = xr * (3.0 * b[1][1] + xr2 * b[1][2]);
                gux[26] = yr * (b[1][1] + xr2 * b[1][2]);
                gux[27] = zr * (b[1][1] + xr2 * b[1][2]);
                gux[28] = xr * (b[1][1] + yr2 * b[1][2]);
                gux[29] = zr * xr * yr * b[1][2];
                gux[30] = xr * (b[1][1] + zr2 * b[1][2]);
                guy[25] = yr * (b[1][1] + xr2 * b[1][2]);
                guy[26] = xr * (b[1][1] + yr2 * b[1][2]);
                guy[27] = gux[29];
                guy[28] = yr * (3.0 * b[1][1] + yr2 * b[1][2]);
                guy[29] = zr * (b[1][1] + yr2 * b[1][2]);
                guy[30] = yr * (b[1][1] + zr2 * b[1][2]);
                guz[25] = zr * (b[1][1] + xr2 * b[1][2]);
                guz[26] = gux[29];
                guz[27] = xr * (b[1][1] + zr2 * b[1][2]);
                guz[28] = zr * (b[1][1] + yr2 * b[1][2]);
                guz[29] = yr * (b[1][1] + zr2 * b[1][2]);
                guz[30] = zr * (3.0 * b[1][1] + zr2 * b[1][2]);
                gqxx[25] = 2.0 * b[2][0] + xr2 * (5.0 * b[2][1] + xr2 * b[2][2]);
                gqxx[26] = yr * xr * (2.0 * b[2][1] + xr2 * b[2][2]);
                gqxx[27] = zr * xr * (2.0 * b[2][1] + xr2 * b[2][2]);
                gqxx[28] = xr2 * (b[2][1] + yr2 * b[2][2]);
                gqxx[29] = zr * yr * xr2 * b[2][2];
                gqxx[30] = xr2 * (b[2][1] + zr2 * b[2][2]);
                gqyy[25] = yr2 * (b[2][1] + xr2 * b[2][2]);
                gqyy[26] = xr * yr * (2.0 * b[2][1] + yr2 * b[2][2]);
                gqyy[27] = xr * zr * yr2 * b[2][2];
                gqyy[28] = 2.0 * b[2][0] + yr2 * (5.0 * b[2][1] + yr2 * b[2][2]);
                gqyy[29] = yr * zr * (2.0 * b[2][1] + yr2 * b[2][2]);
                gqyy[30] = yr2 * (b[2][1] + zr2 * b[2][2]);
                gqzz[25] = zr2 * (b[2][1] + xr2 * b[2][2]);
                gqzz[26] = xr * yr * zr2 * b[2][2];
                gqzz[27] = xr * zr * (2.0 * b[2][1] + zr2 * b[2][2]);
                gqzz[28] = zr2 * (b[2][1] + yr2 * b[2][2]);
                gqzz[29] = yr * zr * (2.0 * b[2][1] + zr2 * b[2][2]);
                gqzz[30] = 2.0 * b[2][0] + zr2 * (5.0 * b[2][1] + zr2 * b[2][2]);
                gqxy[25] = xr * yr * (3.0 * b[2][1] + xr2 * b[2][2]);
                gqxy[26] = b[2][0] + (xr2 + yr2) * b[2][1] + xr2 * yr2 * b[2][2];
                gqxy[27] = zr * yr * (b[2][1] + xr2 * b[2][2]);
                gqxy[28] = xr * yr * (3.0 * b[2][1] + yr2 * b[2][2]);
                gqxy[29] = zr * xr * (b[2][1] + yr2 * b[2][2]);
                gqxy[30] = xr * yr * (b[2][1] + zr2 * b[2][2]);
                gqxz[25] = xr * zr * (3.0 * b[2][1] + xr2 * b[2][2]);
                gqxz[26] = yr * zr * (b[2][1] + xr2 * b[2][2]);
                gqxz[27] = b[2][0] + (xr2 + zr2) * b[2][1] + xr2 * zr2 * b[2][2];
                gqxz[28] = xr * zr * (b[2][1] + yr2 * b[2][2]);
                gqxz[29] = xr * yr * (b[2][1] + zr2 * b[2][2]);
                gqxz[30] = xr * zr * (3.0 * b[2][1] + zr2 * b[2][2]);
                gqyz[25] = zr * yr * (b[2][1] + xr2 * b[2][2]);
                gqyz[26] = xr * zr * (b[2][1] + yr2 * b[2][2]);
                gqyz[27] = xr * yr * (b[2][1] + zr2 * b[2][2]);
                gqyz[28] = yr * zr * (3.0 * b[2][1] + yr2 * b[2][2]);
                gqyz[29] = b[2][0] + (yr2 + zr2) * b[2][1] + yr2 * zr2 * b[2][2];
                gqyz[30] = yr * zr * (3.0 * b[2][1] + zr2 * b[2][2]);
                /**
                 * Unweighted 3rd reaction potential gradient tensor.
                 */
                gc[11] = xr * (3.0 * a[0][2] + xr2 * a[0][3]);
                gc[12] = yr * (a[0][2] + xr2 * a[0][3]);
                gc[13] = zr * (a[0][2] + xr2 * a[0][3]);
                gc[14] = xr * (a[0][2] + yr2 * a[0][3]);
                gc[15] = xr * yr * zr * a[0][3];
                gc[16] = xr * (a[0][2] + zr2 * a[0][3]);
                gc[17] = yr * (3.0 * a[0][2] + yr2 * a[0][3]);
                gc[18] = zr * (a[0][2] + yr2 * a[0][3]);
                gc[19] = yr * (a[0][2] + zr2 * a[0][3]);
                gc[20] = zr * (3.0 * a[0][2] + zr2 * a[0][3]);
                gux[11] = 3.0 * a[1][1] + xr2 * (6.0 * a[1][2] + xr2 * a[1][3]);
                gux[12] = xr * yr * (3.0 * a[1][2] + xr2 * a[1][3]);
                gux[13] = xr * zr * (3.0 * a[1][2] + xr2 * a[1][3]);
                gux[14] = a[1][1] + (xr2 + yr2) * a[1][2] + xr2 * yr2 * a[1][3];
                gux[15] = yr * zr * (a[1][2] + xr2 * a[1][3]);
                gux[16] = a[1][1] + (xr2 + zr2) * a[1][2] + xr2 * zr2 * a[1][3];
                gux[17] = xr * yr * (3.0 * a[1][2] + yr2 * a[1][3]);
                gux[18] = xr * zr * (a[1][2] + yr2 * a[1][3]);
                gux[19] = xr * yr * (a[1][2] + zr2 * a[1][3]);
                gux[20] = xr * zr * (3.0 * a[1][2] + zr2 * a[1][3]);
                guy[11] = gux[12];
                guy[12] = gux[14];
                guy[13] = gux[15];
                guy[14] = gux[17];
                guy[15] = gux[18];
                guy[16] = gux[19];
                guy[17] = 3.0 * a[1][1] + yr2 * (6.0 * a[1][2] + yr2 * a[1][3]);
                gux[18] = xr * zr * (a[1][2] + yr2 * a[1][3]);
                gux[19] = xr * yr * (a[1][2] + zr2 * a[1][3]);
                gux[20] = xr * zr * (3.0 * a[1][2] + zr2 * a[1][3]);
                guy[11] = gux[12];
                guy[12] = gux[14];
                guy[13] = gux[15];
                guy[14] = gux[17];
                guy[15] = gux[18];
                guy[16] = gux[19];
                guy[17] = 3.0 * a[1][1] + yr2 * (6.0 * a[1][2] + yr2 * a[1][3]);
                guy[18] = yr * zr * (3.0 * a[1][2] + yr2 * a[1][3]);
                guy[19] = a[1][1] + (yr2 + zr2) * a[1][2] + yr2 * zr2 * a[1][3];
                guy[20] = yr * zr * (3.0 * a[1][2] + zr2 * a[1][3]);
                guz[11] = gux[13];
                guz[12] = gux[15];
                guz[13] = gux[16];
                guz[14] = gux[18];
                guz[15] = gux[19];
                guz[16] = gux[20];
                guz[17] = guy[18];
                guz[18] = guy[19];
                guz[19] = guy[20];
                guz[20] = 3.0 * a[1][1] + zr2 * (6.0 * a[1][2] + zr2 * a[1][3]);
                gqxx[11] = xr * (12.0 * a[2][1] + xr2 * (9.0 * a[2][2] + xr2 * a[2][3]));
                gqxx[12] = yr * (2.0 * a[2][1] + xr2 * (5.0 * a[2][2] + xr2 * a[2][3]));
                gqxx[13] = zr * (2.0 * a[2][1] + xr2 * (5.0 * a[2][2] + xr2 * a[2][3]));
                gqxx[14] = xr * (2.0 * a[2][1] + yr2 * 2.0 * a[2][2] + xr2 * (a[2][2] + yr2 * a[2][3]));
                gqxx[15] = xr * yr * zr * (2.0 * a[2][2] + xr2 * a[2][3]);
                gqxx[16] = xr * (2.0 * a[2][1] + zr2 * 2.0 * a[2][2] + xr2 * (a[2][2] + zr2 * a[2][3]));
                gqxx[17] = yr * xr2 * (3.0 * a[2][2] + yr2 * a[2][3]);
                gqxx[18] = zr * xr2 * (a[2][2] + yr2 * a[2][3]);
                gqxx[19] = yr * xr2 * (a[2][2] + zr2 * a[2][3]);
                gqxx[20] = zr * xr2 * (3.0 * a[2][2] + zr2 * a[2][3]);
                gqxy[11] = yr * (3.0 * a[2][1] + xr2 * (6.0 * a[2][2] + xr2 * a[2][3]));
                gqxy[12] = xr * (3.0 * (a[2][1] + yr2 * a[2][2]) + xr2 * (a[2][2] + yr2 * a[2][3]));
                gqxy[13] = xr * yr * zr * (3.0 * a[2][2] + xr2 * a[2][3]);
                gqxy[14] = yr * (3.0 * (a[2][1] + xr2 * a[2][2]) + yr2 * (a[2][2] + xr2 * a[2][3]));
                gqxy[15] = zr * (a[2][1] + (yr2 + xr2) * a[2][2] + yr2 * xr2 * a[2][3]);
                gqxy[16] = yr * (a[2][1] + (xr2 + zr2) * a[2][2] + xr2 * zr2 * a[2][3]);
                gqxy[17] = xr * (3.0 * (a[2][1] + yr2 * a[2][2]) + yr2 * (3.0 * a[2][2] + yr2 * a[2][3]));
                gqxy[18] = xr * yr * zr * (3.0 * a[2][2] + yr2 * a[2][3]);
                gqxy[19] = xr * (a[2][1] + (yr2 + zr2) * a[2][2] + yr2 * zr2 * a[2][3]);
                gqxy[20] = xr * yr * zr * (3.0 * a[2][2] + zr2 * a[2][3]);
                gqxz[11] = zr * (3.0 * a[2][1] + xr2 * (6.0 * a[2][2] + xr2 * a[2][3]));
                gqxz[12] = xr * yr * zr * (3.0 * a[2][2] + xr2 * a[2][3]);
                gqxz[13] = xr * (3.0 * (a[2][1] + zr2 * a[2][2]) + xr2 * (a[2][2] + zr2 * a[2][3]));
                gqxz[14] = zr * (a[2][1] + (xr2 + yr2) * a[2][2] + xr2 * yr2 * a[2][3]);
                gqxz[15] = yr * (a[2][1] + (xr2 + zr2) * a[2][2] + zr2 * xr2 * a[2][3]);
                gqxz[16] = zr * (3.0 * (a[2][1] + xr2 * a[2][2]) + zr2 * (a[2][2] + xr2 * a[2][3]));
                gqxz[17] = xr * yr * zr * (3.0 * a[2][2] + yr2 * a[2][3]);
                gqxz[18] = xr * (a[2][1] + (zr2 + yr2) * a[2][2] + zr2 * yr2 * a[2][3]);
                gqxz[19] = xr * yr * zr * (3.0 * a[2][2] + zr2 * a[2][3]);
                gqxz[20] = xr * (3.0 * a[2][1] + zr2 * (6.0 * a[2][2] + zr2 * a[2][3]));
                gqyy[11] = xr * yr2 * (3.0 * a[2][2] + xr2 * a[2][3]);
                gqyy[12] = yr * (2.0 * a[2][1] + xr2 * 2.0 * a[2][2] + yr2 * (a[2][2] + xr2 * a[2][3]));
                gqyy[13] = zr * yr2 * (a[2][2] + xr2 * a[2][3]);
                gqyy[14] = xr * (2.0 * a[2][1] + yr2 * (5.0 * a[2][2] + yr2 * a[2][3]));
                gqyy[15] = xr * yr * zr * (2.0 * a[2][2] + yr2 * a[2][3]);
                gqyy[16] = xr * yr2 * (a[2][2] + zr2 * a[2][3]);
                gqyy[17] = yr * (12.0 * a[2][1] + yr2 * (9.0 * a[2][2] + yr2 * a[2][3]));
                gqyy[18] = zr * (2.0 * a[2][1] + yr2 * (5.0 * a[2][2] + yr2 * a[2][3]));
                gqyy[19] = yr * (2.0 * a[2][1] + zr2 * 2.0 * a[2][2] + yr2 * (a[2][2] + zr2 * a[2][3]));
                gqyy[20] = zr * yr2 * (3.0 * a[2][2] + zr2 * a[2][3]);
                gqyz[11] = xr * yr * zr * (3.0 * a[2][2] + xr2 * a[2][3]);
                gqyz[12] = zr * (a[2][1] + (xr2 + yr2) * a[2][2] + xr2 * yr2 * a[2][3]);
                gqyz[13] = yr * (a[2][1] + (xr2 + zr2) * a[2][2] + xr2 * zr2 * a[2][3]);
                gqyz[14] = xr * yr * zr * (3.0 * a[2][2] + yr2 * a[2][3]);
                gqyz[15] = xr * (a[2][1] + (yr2 + zr2) * a[2][2] + yr2 * zr2 * a[2][3]);
                gqyz[16] = xr * yr * zr * (3.0 * a[2][2] + zr2 * a[2][3]);
                gqyz[17] = zr * (3.0 * a[2][1] + yr2 * (6.0 * a[2][2] + yr2 * a[2][3]));
                gqyz[18] = yr * (3.0 * (a[2][1] + zr2 * a[2][2]) + yr2 * (a[2][2] + zr2 * a[2][3]));
                gqyz[19] = zr * (3.0 * (a[2][1] + yr2 * a[2][2]) + zr2 * (a[2][2] + yr2 * a[2][3]));
                gqyz[20] = yr * (3.0 * a[2][1] + zr2 * (6.0 * a[2][2] + zr2 * a[2][3]));
                gqzz[11] = xr * zr2 * (3.0 * a[2][2] + xr2 * a[2][3]);
                gqzz[12] = yr * (zr2 * a[2][2] + xr2 * (zr2 * a[2][3]));
                gqzz[13] = zr * (2.0 * a[2][1] + xr2 * 2.0 * a[2][2] + zr2 * (a[2][2] + xr2 * a[2][3]));
                gqzz[14] = xr * zr2 * (a[2][2] + yr2 * a[2][3]);
                gqzz[15] = xr * yr * zr * (2.0 * a[2][2] + zr2 * a[2][3]);
                gqzz[16] = xr * (2.0 * a[2][1] + zr2 * (5.0 * a[2][2] + zr2 * a[2][3]));
                gqzz[17] = yr * zr2 * (3.0 * a[2][2] + yr2 * a[2][3]);
                gqzz[18] = zr * (2.0 * a[2][1] + yr2 * 2.0 * a[2][2] + zr2 * (a[2][2] + yr2 * a[2][3]));
                gqzz[19] = yr * (2.0 * a[2][1] + zr2 * (5.0 * a[2][2] + zr2 * a[2][3]));
                gqzz[20] = zr * (12.0 * a[2][1] + zr2 * (9.0 * a[2][2] + zr2 * a[2][3]));
            }

            private void permanentEnergyGradient(int i, int k) {
                final double desymdr = ci * ck * gc[21] - (uxi * (uxk * gux[22] + uyk * guy[22] + uzk * guz[22])
                        + uyi * (uxk * gux[23] + uyk * guy[23] + uzk * guz[23])
                        + uzi * (uxk * gux[24] + uyk * guy[24] + uzk * guz[24]));
                final double dewidr = ci * (uxk * gc[22] + uyk * gc[23] + uzk * gc[24])
                        - ck * (uxi * gux[21] + uyi * guy[21] + uzi * guz[21])
                        + ci * (qxxk * gc[25] + qyyk * gc[28] + qzzk * gc[30]
                                + 2.0 * (qxyk * gc[26] + qxzk * gc[27] + qyzk * gc[29]))
                        + ck * (qxxi * gqxx[21] + qyyi * gqyy[21] + qzzi * gqzz[21]
                                + 2.0 * (qxyi * gqxy[21] + qxzi * gqxz[21] + qyzi * gqyz[21]))
                        - uxi * (qxxk * gux[25] + qyyk * gux[28] + qzzk * gux[30]
                                + 2.0 * (qxyk * gux[26] + qxzk * gux[27] + qyzk * gux[29]))
                        - uyi * (qxxk * guy[25] + qyyk * guy[28] + qzzk * guy[30]
                                + 2.0 * (qxyk * guy[26] + qxzk * guy[27] + qyzk * guy[29]))
                        - uzi * (qxxk * guz[25] + qyyk * guz[28] + qzzk * guz[30]
                                + 2.0 * (qxyk * guz[26] + qxzk * guz[27] + qyzk * guz[29]))
                        + uxk * (qxxi * gqxx[22] + qyyi * gqyy[22] + qzzi * gqzz[22]
                                + 2.0 * (qxyi * gqxy[22] + qxzi * gqxz[22] + qyzi * gqyz[22]))
                        + uyk * (qxxi * gqxx[23] + qyyi * gqyy[23] + qzzi * gqzz[23]
                                + 2.0 * (qxyi * gqxy[23] + qxzi * gqxz[23] + qyzi * gqyz[23]))
                        + uzk * (qxxi * gqxx[24] + qyyi * gqyy[24] + qzzi * gqzz[24]
                                + 2.0 * (qxyi * gqxy[24] + qxzi * gqxz[24] + qyzi * gqyz[24]))
                        + qxxi * (qxxk * gqxx[25] + qyyk * gqxx[28] + qzzk * gqxx[30]
                                + 2.0 * (qxyk * gqxx[26] + qxzk * gqxx[27] + qyzk * gqxx[29]))
                        + qyyi * (qxxk * gqyy[25] + qyyk * gqyy[28] + qzzk * gqyy[30]
                                + 2.0 * (qxyk * gqyy[26] + qxzk * gqyy[27] + qyzk * gqyy[29]))
                        + qzzi * (qxxk * gqzz[25] + qyyk * gqzz[28] + qzzk * gqzz[30]
                                + 2.0 * (qxyk * gqzz[26] + qxzk * gqzz[27] + qyzk * gqzz[29]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxy[25] + qyyk * gqxy[28] + qzzk * gqxy[30]
                                        + 2.0 * (qxyk * gqxy[26] + qxzk * gqxy[27] + qyzk * gqxy[29]))
                                + qxzi * (qxxk * gqxz[25] + qyyk * gqxz[28] + qzzk * gqxz[30]
                                        + 2.0 * (qxyk * gqxz[26] + qxzk * gqxz[27] + qyzk * gqxz[29]))
                                + qyzi * (qxxk * gqyz[25] + qyyk * gqyz[28] + qzzk * gqyz[30]
                                        + 2.0 * (qxyk * gqyz[26] + qxzk * gqyz[27] + qyzk * gqyz[29])));
                final double dewkdr = ci * (uxk * gux[21] + uyk * guy[21] + uzk * guz[21])
                        - ck * (uxi * gc[22] + uyi * gc[23] + uzi * gc[24])
                        + ci * (qxxk * gqxx[21] + qyyk * gqyy[21] + qzzk * gqzz[21]
                                + 2.0 * (qxyk * gqxy[21] + qxzk * gqxz[21] + qyzk * gqyz[21]))
                        + ck * (qxxi * gc[25] + qyyi * gc[28] + qzzi * gc[30]
                                + 2.0 * (qxyi * gc[26] + qxzi * gc[27] + qyzi * gc[29]))
                        - uxi * (qxxk * gqxx[22] + qyyk * gqyy[22] + qzzk * gqzz[22]
                                + 2.0 * (qxyk * gqxy[22] + qxzk * gqxz[22] + qyzk * gqyz[22]))
                        - uyi * (qxxk * gqxx[23] + qyyk * gqyy[23] + qzzk * gqzz[23]
                                + 2.0 * (qxyk * gqxy[23] + qxzk * gqxz[23] + qyzk * gqyz[23]))
                        - uzi * (qxxk * gqxx[24] + qyyk * gqyy[24] + qzzk * gqzz[24]
                                + 2.0 * (qxyk * gqxy[24] + qxzk * gqxz[24] + qyzk * gqyz[24]))
                        + uxk * (qxxi * gux[25] + qyyi * gux[28] + qzzi * gux[30]
                                + 2.0 * (qxyi * gux[26] + qxzi * gux[27] + qyzi * gux[29]))
                        + uyk * (qxxi * guy[25] + qyyi * guy[28] + qzzi * guy[30]
                                + 2.0 * (qxyi * guy[26] + qxzi * guy[27] + qyzi * guy[29]))
                        + uzk * (qxxi * guz[25] + qyyi * guz[28] + qzzi * guz[30]
                                + 2.0 * (qxyi * guz[26] + qxzi * guz[27] + qyzi * guz[29]))
                        + qxxi * (qxxk * gqxx[25] + qyyk * gqyy[25] + qzzk * gqzz[25]
                                + 2.0 * (qxyk * gqxy[25] + qxzk * gqxz[25] + qyzk * gqyz[25]))
                        + qyyi * (qxxk * gqxx[28] + qyyk * gqyy[28] + qzzk * gqzz[28]
                                + 2.0 * (qxyk * gqxy[28] + qxzk * gqxz[28] + qyzk * gqyz[28]))
                        + qzzi * (qxxk * gqxx[30] + qyyk * gqyy[30] + qzzk * gqzz[30]
                                + 2.0 * (qxyk * gqxy[30] + qxzk * gqxz[30] + qyzk * gqyz[30]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxx[26] + qyyk * gqyy[26] + qzzk * gqzz[26]
                                        + 2.0 * (qxyk * gqxy[26] + qxzk * gqxz[26] + qyzk * gqyz[26]))
                                + qxzi * (qxxk * gqxx[27] + qyyk * gqyy[27] + qzzk * gqzz[27]
                                        + 2.0 * (qxyk * gqxy[27] + qxzk * gqxz[27] + qyzk * gqyz[27]))
                                + qyzi * (qxxk * gqxx[29] + qyyk * gqyy[29] + qzzk * gqzz[29]
                                        + 2.0 * (qxyk * gqxy[29] + qxzk * gqxz[29] + qyzk * gqyz[29])));
                final double dsumdr = desymdr + 0.5 * (dewidr + dewkdr);
                final double drbi = rbk * dsumdr;
                final double drbk = rbi * dsumdr;
                if (i == k) {
                    gb_local[i] += drbi;
                    return;
                }
                /**
                 * Increment the gradients and Born chain rule term.
                 */
                final double dedx = dEdX();
                final double dedy = dEdY();
                final double dedz = dEdZ();
                gX[i] -= lambda * dedx;
                gY[i] -= lambda * dedy;
                gZ[i] -= lambda * dedz;
                gb_local[i] += drbi;
                gX[k] += lambda * dedx;
                gY[k] += lambda * dedy;
                gZ[k] += lambda * dedz;
                gb_local[k] += drbk;
                if (lambdaTerm) {
                    lgX[i] -= dedx;
                    lgY[i] -= dedy;
                    lgZ[i] -= dedz;
                    lgX[k] += dedx;
                    lgY[k] += dedy;
                    lgZ[k] += dedz;
                }
                permanentEnergyTorque(i, k);
            }

            private double dEdZ() {
                final double desymdz = ci * ck * gc[4] - (uxi * (uxk * gux[7] + uyk * guy[7] + uzk * guz[7])
                        + uyi * (uxk * gux[9] + uyk * guy[9] + uzk * guz[9])
                        + uzi * (uxk * gux[10] + uyk * guy[10] + uzk * guz[10]));
                final double dewidz = ci * (uxk * gc[7] + uyk * gc[9] + uzk * gc[10])
                        - ck * (uxi * gux[4] + uyi * guy[4] + uzi * guz[4])
                        + ci * (qxxk * gc[13] + qyyk * gc[18] + qzzk * gc[20]
                                + 2.0 * (qxyk * gc[15] + qxzk * gc[16] + qyzk * gc[19]))
                        + ck * (qxxi * gqxx[4] + qyyi * gqyy[4] + qzzi * gqzz[4]
                                + 2.0 * (qxyi * gqxy[4] + qxzi * gqxz[4] + qyzi * gqyz[4]))
                        - uxi * (qxxk * gux[13] + qyyk * gux[18] + qzzk * gux[20]
                                + 2.0 * (qxyk * gux[15] + qxzk * gux[16] + qyzk * gux[19]))
                        - uyi * (qxxk * guy[13] + qyyk * guy[18] + qzzk * guy[20]
                                + 2.0 * (qxyk * guy[15] + qxzk * guy[16] + qyzk * guy[19]))
                        - uzi * (qxxk * guz[13] + qyyk * guz[18] + qzzk * guz[20]
                                + 2.0 * (qxyk * guz[15] + qxzk * guz[16] + qyzk * guz[19]))
                        + uxk * (qxxi * gqxx[7] + qyyi * gqyy[7] + qzzi * gqzz[7]
                                + 2.0 * (qxyi * gqxy[7] + qxzi * gqxz[7] + qyzi * gqyz[7]))
                        + uyk * (qxxi * gqxx[9] + qyyi * gqyy[9] + qzzi * gqzz[9]
                                + 2.0 * (qxyi * gqxy[9] + qxzi * gqxz[9] + qyzi * gqyz[9]))
                        + uzk * (qxxi * gqxx[10] + qyyi * gqyy[10] + qzzi * gqzz[10]
                                + 2.0 * (qxyi * gqxy[10] + qxzi * gqxz[10] + qyzi * gqyz[10]))
                        + qxxi * (qxxk * gqxx[13] + qyyk * gqxx[18] + qzzk * gqxx[20]
                                + 2.0 * (qxyk * gqxx[15] + qxzk * gqxx[16] + qyzk * gqxx[19]))
                        + qyyi * (qxxk * gqyy[13] + qyyk * gqyy[18] + qzzk * gqyy[20]
                                + 2.0 * (qxyk * gqyy[15] + qxzk * gqyy[16] + qyzk * gqyy[19]))
                        + qzzi * (qxxk * gqzz[13] + qyyk * gqzz[18] + qzzk * gqzz[20]
                                + 2.0 * (qxyk * gqzz[15] + qxzk * gqzz[16] + qyzk * gqzz[19]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxy[13] + qyyk * gqxy[18] + qzzk * gqxy[20]
                                        + 2.0 * (qxyk * gqxy[15] + qxzk * gqxy[16] + qyzk * gqxy[19]))
                                + qxzi * (qxxk * gqxz[13] + qyyk * gqxz[18] + qzzk * gqxz[20]
                                        + 2.0 * (qxyk * gqxz[15] + qxzk * gqxz[16] + qyzk * gqxz[19]))
                                + qyzi * (qxxk * gqyz[13] + qyyk * gqyz[18] + qzzk * gqyz[20]
                                        + 2.0 * (qxyk * gqyz[15] + qxzk * gqyz[16] + qyzk * gqyz[19])));
                final double dewkdz = ci * (uxk * gux[4] + uyk * guy[4] + uzk * guz[4])
                        - ck * (uxi * gc[7] + uyi * gc[9] + uzi * gc[10])
                        + ci * (qxxk * gqxx[4] + qyyk * gqyy[4] + qzzk * gqzz[4]
                                + 2.0 * (qxyk * gqxy[4] + qxzk * gqxz[4] + qyzk * gqyz[4]))
                        + ck * (qxxi * gc[13] + qyyi * gc[18] + qzzi * gc[20]
                                + 2.0 * (qxyi * gc[15] + qxzi * gc[16] + qyzi * gc[19]))
                        - uxi * (qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                                + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]))
                        - uyi * (qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                                + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9]))
                        - uzi * (qxxk * gqxx[10] + qyyk * gqyy[10] + qzzk * gqzz[10]
                                + 2.0 * (qxyk * gqxy[10] + qxzk * gqxz[10] + qyzk * gqyz[10]))
                        + uxk * (qxxi * gux[13] + qyyi * gux[18] + qzzi * gux[20]
                                + 2.0 * (qxyi * gux[15] + qxzi * gux[16] + qyzi * gux[19]))
                        + uyk * (qxxi * guy[13] + qyyi * guy[18] + qzzi * guy[20]
                                + 2.0 * (qxyi * guy[15] + qxzi * guy[16] + qyzi * guy[19]))
                        + uzk * (qxxi * guz[13] + qyyi * guz[18] + qzzi * guz[20]
                                + 2.0 * (qxyi * guz[15] + qxzi * guz[16] + qyzi * guz[19]))
                        + qxxi * (qxxk * gqxx[13] + qyyk * gqyy[13] + qzzk * gqzz[13]
                                + 2.0 * (qxyk * gqxy[13] + qxzk * gqxz[13] + qyzk * gqyz[13]))
                        + qyyi * (qxxk * gqxx[18] + qyyk * gqyy[18] + qzzk * gqzz[18]
                                + 2.0 * (qxyk * gqxy[18] + qxzk * gqxz[18] + qyzk * gqyz[18]))
                        + qzzi * (qxxk * gqxx[20] + qyyk * gqyy[20] + qzzk * gqzz[20]
                                + 2.0 * (qxyk * gqxy[20] + qxzk * gqxz[20] + qyzk * gqyz[20]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxx[15] + qyyk * gqyy[15] + qzzk * gqzz[15]
                                        + 2.0 * (qxyk * gqxy[15] + qxzk * gqxz[15] + qyzk * gqyz[15]))
                                + qxzi * (qxxk * gqxx[16] + qyyk * gqyy[16] + qzzk * gqzz[16]
                                        + 2.0 * (qxyk * gqxy[16] + qxzk * gqxz[16] + qyzk * gqyz[16]))
                                + qyzi * (qxxk * gqxx[19] + qyyk * gqyy[19] + qzzk * gqzz[19]
                                        + 2.0 * (qxyk * gqxy[19] + qxzk * gqxz[19] + qyzk * gqyz[19])));
                return desymdz + 0.5 * (dewidz + dewkdz);
            }

            private double dEdY() {
                final double desymdy = ci * ck * gc[3] - (uxi * (uxk * gux[6] + uyk * guy[6] + uzk * guz[6])
                        + uyi * (uxk * gux[8] + uyk * guy[8] + uzk * guz[8])
                        + uzi * (uxk * gux[9] + uyk * guy[9] + uzk * guz[9]));
                final double dewidy = ci * (uxk * gc[6] + uyk * gc[8] + uzk * gc[9])
                        - ck * (uxi * gux[3] + uyi * guy[3] + uzi * guz[3])
                        + ci * (qxxk * gc[12] + qyyk * gc[17] + qzzk * gc[19]
                                + 2.0 * (qxyk * gc[14] + qxzk * gc[15] + qyzk * gc[18]))
                        + ck * (qxxi * gqxx[3] + qyyi * gqyy[3] + qzzi * gqzz[3]
                                + 2.0 * (qxyi * gqxy[3] + qxzi * gqxz[3] + qyzi * gqyz[3]))
                        - uxi * (qxxk * gux[12] + qyyk * gux[17] + qzzk * gux[19]
                                + 2.0 * (qxyk * gux[14] + qxzk * gux[15] + qyzk * gux[18]))
                        - uyi * (qxxk * guy[12] + qyyk * guy[17] + qzzk * guy[19]
                                + 2.0 * (qxyk * guy[14] + qxzk * guy[15] + qyzk * guy[18]))
                        - uzi * (qxxk * guz[12] + qyyk * guz[17] + qzzk * guz[19]
                                + 2.0 * (qxyk * guz[14] + qxzk * guz[15] + qyzk * guz[18]))
                        + uxk * (qxxi * gqxx[6] + qyyi * gqyy[6] + qzzi * gqzz[6]
                                + 2.0 * (qxyi * gqxy[6] + qxzi * gqxz[6] + qyzi * gqyz[6]))
                        + uyk * (qxxi * gqxx[8] + qyyi * gqyy[8] + qzzi * gqzz[8]
                                + 2.0 * (qxyi * gqxy[8] + qxzi * gqxz[8] + qyzi * gqyz[8]))
                        + uzk * (qxxi * gqxx[9] + qyyi * gqyy[9] + qzzi * gqzz[9]
                                + 2.0 * (qxyi * gqxy[9] + qxzi * gqxz[9] + qyzi * gqyz[9]))
                        + qxxi * (qxxk * gqxx[12] + qyyk * gqxx[17] + qzzk * gqxx[19]
                                + 2.0 * (qxyk * gqxx[14] + qxzk * gqxx[15] + qyzk * gqxx[18]))
                        + qyyi * (qxxk * gqyy[12] + qyyk * gqyy[17] + qzzk * gqyy[19]
                                + 2.0 * (qxyk * gqyy[14] + qxzk * gqyy[15] + qyzk * gqyy[18]))
                        + qzzi * (qxxk * gqzz[12] + qyyk * gqzz[17] + qzzk * gqzz[19]
                                + 2.0 * (qxyk * gqzz[14] + qxzk * gqzz[15] + qyzk * gqzz[18]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxy[12] + qyyk * gqxy[17] + qzzk * gqxy[19]
                                        + 2.0 * (qxyk * gqxy[14] + qxzk * gqxy[15] + qyzk * gqxy[18]))
                                + qxzi * (qxxk * gqxz[12] + qyyk * gqxz[17] + qzzk * gqxz[19]
                                        + 2.0 * (qxyk * gqxz[14] + qxzk * gqxz[15] + qyzk * gqxz[18]))
                                + qyzi * (qxxk * gqyz[12] + qyyk * gqyz[17] + qzzk * gqyz[19]
                                        + 2.0 * (qxyk * gqyz[14] + qxzk * gqyz[15] + qyzk * gqyz[18])));
                final double dewkdy = ci * (uxk * gux[3] + uyk * guy[3] + uzk * guz[3])
                        - ck * (uxi * gc[6] + uyi * gc[8] + uzi * gc[9])
                        + ci * (qxxk * gqxx[3] + qyyk * gqyy[3] + qzzk * gqzz[3]
                                + 2.0 * (qxyk * gqxy[3] + qxzk * gqxz[3] + qyzk * gqyz[3]))
                        + ck * (qxxi * gc[12] + qyyi * gc[17] + qzzi * gc[19]
                                + 2.0 * (qxyi * gc[14] + qxzi * gc[15] + qyzi * gc[18]))
                        - uxi * (qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                                + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]))
                        - uyi * (qxxk * gqxx[8] + qyyk * gqyy[8] + qzzk * gqzz[8]
                                + 2.0 * (qxyk * gqxy[8] + qxzk * gqxz[8] + qyzk * gqyz[8]))
                        - uzi * (qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                                + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9]))
                        + uxk * (qxxi * gux[12] + qyyi * gux[17] + qzzi * gux[19]
                                + 2.0 * (qxyi * gux[14] + qxzi * gux[15] + qyzi * gux[18]))
                        + uyk * (qxxi * guy[12] + qyyi * guy[17] + qzzi * guy[19]
                                + 2.0 * (qxyi * guy[14] + qxzi * guy[15] + qyzi * guy[18]))
                        + uzk * (qxxi * guz[12] + qyyi * guz[17] + qzzi * guz[19]
                                + 2.0 * (qxyi * guz[14] + qxzi * guz[15] + qyzi * guz[18]))
                        + qxxi * (qxxk * gqxx[12] + qyyk * gqyy[12] + qzzk * gqzz[12]
                                + 2.0 * (qxyk * gqxy[12] + qxzk * gqxz[12] + qyzk * gqyz[12]))
                        + qyyi * (qxxk * gqxx[17] + qyyk * gqyy[17] + qzzk * gqzz[17]
                                + 2.0 * (qxyk * gqxy[17] + qxzk * gqxz[17] + qyzk * gqyz[17]))
                        + qzzi * (qxxk * gqxx[19] + qyyk * gqyy[19] + qzzk * gqzz[19]
                                + 2.0 * (qxyk * gqxy[19] + qxzk * gqxz[19] + qyzk * gqyz[19]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxx[14] + qyyk * gqyy[14] + qzzk * gqzz[14]
                                        + 2.0 * (qxyk * gqxy[14] + qxzk * gqxz[14] + qyzk * gqyz[14]))
                                + qxzi * (qxxk * gqxx[15] + qyyk * gqyy[15] + qzzk * gqzz[15]
                                        + 2.0 * (qxyk * gqxy[15] + qxzk * gqxz[15] + qyzk * gqyz[15]))
                                + qyzi * (qxxk * gqxx[18] + qyyk * gqyy[18] + qzzk * gqzz[18]
                                        + 2.0 * (qxyk * gqxy[18] + qxzk * gqxz[18] + qyzk * gqyz[18])));

                return desymdy + 0.5 * (dewidy + dewkdy);
            }

            private double dEdX() {
                final double desymdx = ci * ck * gc[2] - (uxi * (uxk * gux[5] + uyk * guy[5] + uzk * guz[5])
                        + uyi * (uxk * gux[6] + uyk * guy[6] + uzk * guz[6])
                        + uzi * (uxk * gux[7] + uyk * guy[7] + uzk * guz[7]));
                final double dewidx = ci * (uxk * gc[5] + uyk * gc[6] + uzk * gc[7])
                        - ck * (uxi * gux[2] + uyi * guy[2] + uzi * guz[2])
                        + ci * (qxxk * gc[11] + qyyk * gc[14] + qzzk * gc[16]
                                + 2.0 * (qxyk * gc[12] + qxzk * gc[13] + qyzk * gc[15]))
                        + ck * (qxxi * gqxx[2] + qyyi * gqyy[2] + qzzi * gqzz[2]
                                + 2.0 * (qxyi * gqxy[2] + qxzi * gqxz[2] + qyzi * gqyz[2]))
                        - uxi * (qxxk * gux[11] + qyyk * gux[14] + qzzk * gux[16]
                                + 2.0 * (qxyk * gux[12] + qxzk * gux[13] + qyzk * gux[15]))
                        - uyi * (qxxk * guy[11] + qyyk * guy[14] + qzzk * guy[16]
                                + 2.0 * (qxyk * guy[12] + qxzk * guy[13] + qyzk * guy[15]))
                        - uzi * (qxxk * guz[11] + qyyk * guz[14] + qzzk * guz[16]
                                + 2.0 * (qxyk * guz[12] + qxzk * guz[13] + qyzk * guz[15]))
                        + uxk * (qxxi * gqxx[5] + qyyi * gqyy[5] + qzzi * gqzz[5]
                                + 2.0 * (qxyi * gqxy[5] + qxzi * gqxz[5] + qyzi * gqyz[5]))
                        + uyk * (qxxi * gqxx[6] + qyyi * gqyy[6] + qzzi * gqzz[6]
                                + 2.0 * (qxyi * gqxy[6] + qxzi * gqxz[6] + qyzi * gqyz[6]))
                        + uzk * (qxxi * gqxx[7] + qyyi * gqyy[7] + qzzi * gqzz[7]
                                + 2.0 * (qxyi * gqxy[7] + qxzi * gqxz[7] + qyzi * gqyz[7]))
                        + qxxi * (qxxk * gqxx[11] + qyyk * gqxx[14] + qzzk * gqxx[16]
                                + 2.0 * (qxyk * gqxx[12] + qxzk * gqxx[13] + qyzk * gqxx[15]))
                        + qyyi * (qxxk * gqyy[11] + qyyk * gqyy[14] + qzzk * gqyy[16]
                                + 2.0 * (qxyk * gqyy[12] + qxzk * gqyy[13] + qyzk * gqyy[15]))
                        + qzzi * (qxxk * gqzz[11] + qyyk * gqzz[14] + qzzk * gqzz[16]
                                + 2.0 * (qxyk * gqzz[12] + qxzk * gqzz[13] + qyzk * gqzz[15]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxy[11] + qyyk * gqxy[14] + qzzk * gqxy[16]
                                        + 2.0 * (qxyk * gqxy[12] + qxzk * gqxy[13] + qyzk * gqxy[15]))
                                + qxzi * (qxxk * gqxz[11] + qyyk * gqxz[14] + qzzk * gqxz[16]
                                        + 2.0 * (qxyk * gqxz[12] + qxzk * gqxz[13] + qyzk * gqxz[15]))
                                + qyzi * (qxxk * gqyz[11] + qyyk * gqyz[14] + qzzk * gqyz[16]
                                        + 2.0 * (qxyk * gqyz[12] + qxzk * gqyz[13] + qyzk * gqyz[15])));
                final double dewkdx = ci * (uxk * gux[2] + uyk * guy[2] + uzk * guz[2])
                        - ck * (uxi * gc[5] + uyi * gc[6] + uzi * gc[7])
                        + ci * (qxxk * gqxx[2] + qyyk * gqyy[2] + qzzk * gqzz[2]
                                + 2.0 * (qxyk * gqxy[2] + qxzk * gqxz[2] + qyzk * gqyz[2]))
                        + ck * (qxxi * gc[11] + qyyi * gc[14] + qzzi * gc[16]
                                + 2.0 * (qxyi * gc[12] + qxzi * gc[13] + qyzi * gc[15]))
                        - uxi * (qxxk * gqxx[5] + qyyk * gqyy[5] + qzzk * gqzz[5]
                                + 2.0 * (qxyk * gqxy[5] + qxzk * gqxz[5] + qyzk * gqyz[5]))
                        - uyi * (qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                                + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]))
                        - uzi * (qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                                + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]))
                        + uxk * (qxxi * gux[11] + qyyi * gux[14] + qzzi * gux[16]
                                + 2.0 * (qxyi * gux[12] + qxzi * gux[13] + qyzi * gux[15]))
                        + uyk * (qxxi * guy[11] + qyyi * guy[14] + qzzi * guy[16]
                                + 2.0 * (qxyi * guy[12] + qxzi * guy[13] + qyzi * guy[15]))
                        + uzk * (qxxi * guz[11] + qyyi * guz[14] + qzzi * guz[16]
                                + 2.0 * (qxyi * guz[12] + qxzi * guz[13] + qyzi * guz[15]))
                        + qxxi * (qxxk * gqxx[11] + qyyk * gqyy[11] + qzzk * gqzz[11]
                                + 2.0 * (qxyk * gqxy[11] + qxzk * gqxz[11] + qyzk * gqyz[11]))
                        + qyyi * (qxxk * gqxx[14] + qyyk * gqyy[14] + qzzk * gqzz[14]
                                + 2.0 * (qxyk * gqxy[14] + qxzk * gqxz[14] + qyzk * gqyz[14]))
                        + qzzi * (qxxk * gqxx[16] + qyyk * gqyy[16] + qzzk * gqzz[16]
                                + 2.0 * (qxyk * gqxy[16] + qxzk * gqxz[16] + qyzk * gqyz[16]))
                        + 2.0 * (qxyi
                                * (qxxk * gqxx[12] + qyyk * gqyy[12] + qzzk * gqzz[12]
                                        + 2.0 * (qxyk * gqxy[12] + qxzk * gqxz[12] + qyzk * gqyz[12]))
                                + qxzi * (qxxk * gqxx[13] + qyyk * gqyy[13] + qzzk * gqzz[13]
                                        + 2.0 * (qxyk * gqxy[13] + qxzk * gqxz[13] + qyzk * gqyz[13]))
                                + qyzi * (qxxk * gqxx[15] + qyyk * gqyy[15] + qzzk * gqzz[15]
                                        + 2.0 * (qxyk * gqxy[15] + qxzk * gqxz[15] + qyzk * gqyz[15])));

                return desymdx + 0.5 * (dewidx + dewkdx);
            }

            private void permanentEnergyTorque(int i, int k) {
                /**
                 * Torque on permanent dipoles due to permanent reaction field.
                 */
                final double ix = uxk * gux[2] + uyk * gux[3] + uzk * gux[4]
                        + 0.5 * (ck * gux[1] + qxxk * gux[5] + qyyk * gux[8] + qzzk * gux[10]
                                + 2.0 * (qxyk * gux[6] + qxzk * gux[7] + qyzk * gux[9]) + ck * gc[2]
                                + qxxk * gqxx[2] + qyyk * gqyy[2] + qzzk * gqzz[2]
                                + 2.0 * (qxyk * gqxy[2] + qxzk * gqxz[2] + qyzk * gqyz[2]));
                final double iy = uxk * guy[2] + uyk * guy[3] + uzk * guy[4]
                        + 0.5 * (ck * guy[1] + qxxk * guy[5] + qyyk * guy[8] + qzzk * guy[10]
                                + 2.0 * (qxyk * guy[6] + qxzk * guy[7] + qyzk * guy[9]) + ck * gc[3]
                                + qxxk * gqxx[3] + qyyk * gqyy[3] + qzzk * gqzz[3]
                                + 2.0 * (qxyk * gqxy[3] + qxzk * gqxz[3] + qyzk * gqyz[3]));
                final double iz = uxk * guz[2] + uyk * guz[3] + uzk * guz[4]
                        + 0.5 * (ck * guz[1] + qxxk * guz[5] + qyyk * guz[8] + qzzk * guz[10]
                                + 2.0 * (qxyk * guz[6] + qxzk * guz[7] + qyzk * guz[9]) + ck * gc[4]
                                + qxxk * gqxx[4] + qyyk * gqyy[4] + qzzk * gqzz[4]
                                + 2.0 * (qxyk * gqxy[4] + qxzk * gqxz[4] + qyzk * gqyz[4]));
                final double kx = uxi * gux[2] + uyi * gux[3] + uzi * gux[4]
                        - 0.5 * (ci * gux[1] + qxxi * gux[5] + qyyi * gux[8] + qzzi * gux[10]
                                + 2.0 * (qxyi * gux[6] + qxzi * gux[7] + qyzi * gux[9]) + ci * gc[2]
                                + qxxi * gqxx[2] + qyyi * gqyy[2] + qzzi * gqzz[2]
                                + 2.0 * (qxyi * gqxy[2] + qxzi * gqxz[2] + qyzi * gqyz[2]));
                final double ky = uxi * guy[2] + uyi * guy[3] + uzi * guy[4]
                        - 0.5 * (ci * guy[1] + qxxi * guy[5] + qyyi * guy[8] + qzzi * guy[10]
                                + 2.0 * (qxyi * guy[6] + qxzi * guy[7] + qyzi * guy[9]) + ci * gc[3]
                                + qxxi * gqxx[3] + qyyi * gqyy[3] + qzzi * gqzz[3]
                                + 2.0 * (qxyi * gqxy[3] + qxzi * gqxz[3] + qyzi * gqyz[3]));
                final double kz = uxi * guz[2] + uyi * guz[3] + uzi * guz[4]
                        - 0.5 * (ci * guz[1] + qxxi * guz[5] + qyyi * guz[8] + qzzi * guz[10]
                                + 2.0 * (qxyi * guz[6] + qxzi * guz[7] + qyzi * guz[9]) + ci * gc[4]
                                + qxxi * gqxx[4] + qyyi * gqyy[4] + qzzi * gqzz[4]
                                + 2.0 * (qxyi * gqxy[4] + qxzi * gqxz[4] + qyzi * gqyz[4]));
                double tix = uyi * iz - uzi * iy;
                double tiy = uzi * ix - uxi * iz;
                double tiz = uxi * iy - uyi * ix;
                double tkx = uyk * kz - uzk * ky;
                double tky = uzk * kx - uxk * kz;
                double tkz = uxk * ky - uyk * kx;
                /**
                 * Torque on quadrupoles due to permanent reaction field
                 * gradient.
                 */
                final double ixx = -0.5 * (ck * gqxx[1] + uxk * gqxx[2] + uyk * gqxx[3] + uzk * gqxx[4]
                        + qxxk * gqxx[5] + qyyk * gqxx[8] + qzzk * gqxx[10]
                        + 2.0 * (qxyk * gqxx[6] + qxzk * gqxx[7] + qyzk * gqxx[9]) + ck * gc[5] + uxk * gux[5]
                        + uyk * guy[5] + uzk * guz[5] + qxxk * gqxx[5] + qyyk * gqyy[5] + qzzk * gqzz[5]
                        + 2.0 * (qxyk * gqxy[5] + qxzk * gqxz[5] + qyzk * gqyz[5]));
                final double ixy = -0.5 * (ck * gqxy[1] + uxk * gqxy[2] + uyk * gqxy[3] + uzk * gqxy[4]
                        + qxxk * gqxy[5] + qyyk * gqxy[8] + qzzk * gqxy[10]
                        + 2.0 * (qxyk * gqxy[6] + qxzk * gqxy[7] + qyzk * gqxy[9]) + ck * gc[6] + uxk * gux[6]
                        + uyk * guy[6] + uzk * guz[6] + qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                        + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]));
                final double ixz = -0.5 * (ck * gqxz[1] + uxk * gqxz[2] + uyk * gqxz[3] + uzk * gqxz[4]
                        + qxxk * gqxz[5] + qyyk * gqxz[8] + qzzk * gqxz[10]
                        + 2.0 * (qxyk * gqxz[6] + qxzk * gqxz[7] + qyzk * gqxz[9]) + ck * gc[7] + uxk * gux[7]
                        + uyk * guy[7] + uzk * guz[7] + qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                        + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]));
                final double iyy = -0.5 * (ck * gqyy[1] + uxk * gqyy[2] + uyk * gqyy[3] + uzk * gqyy[4]
                        + qxxk * gqyy[5] + qyyk * gqyy[8] + qzzk * gqyy[10]
                        + 2.0 * (qxyk * gqyy[6] + qxzk * gqyy[7] + qyzk * gqyy[9]) + ck * gc[8] + uxk * gux[8]
                        + uyk * guy[8] + uzk * guz[8] + qxxk * gqxx[8] + qyyk * gqyy[8] + qzzk * gqzz[8]
                        + 2.0 * (qxyk * gqxy[8] + qxzk * gqxz[8] + qyzk * gqyz[8]));
                final double iyz = -0.5 * (ck * gqyz[1] + uxk * gqyz[2] + uyk * gqyz[3] + uzk * gqyz[4]
                        + qxxk * gqyz[5] + qyyk * gqyz[8] + qzzk * gqyz[10]
                        + 2.0 * (qxyk * gqyz[6] + qxzk * gqyz[7] + qyzk * gqyz[9]) + ck * gc[9] + uxk * gux[9]
                        + uyk * guy[9] + uzk * guz[9] + qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                        + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9]));
                final double izz = -0.5 * (ck * gqzz[1] + uxk * gqzz[2] + uyk * gqzz[3] + uzk * gqzz[4]
                        + qxxk * gqzz[5] + qyyk * gqzz[8] + qzzk * gqzz[10]
                        + 2.0 * (qxyk * gqzz[6] + qxzk * gqzz[7] + qyzk * gqzz[9]) + ck * gc[10] + uxk * gux[10]
                        + uyk * guy[10] + uzk * guz[10] + qxxk * gqxx[10] + qyyk * gqyy[10] + qzzk * gqzz[10]
                        + 2.0 * (qxyk * gqxy[10] + qxzk * gqxz[10] + qyzk * gqyz[10]));
                final double iyx = ixy;
                final double izx = ixz;
                final double izy = iyz;
                final double kxx = -0.5 * (ci * gqxx[1] - uxi * gqxx[2] - uyi * gqxx[3] - uzi * gqxx[4]
                        + qxxi * gqxx[5] + qyyi * gqxx[8] + qzzi * gqxx[10]
                        + 2.0 * (qxyi * gqxx[6] + qxzi * gqxx[7] + qyzi * gqxx[9]) + ci * gc[5] - uxi * gux[5]
                        - uyi * guy[5] - uzi * guz[5] + qxxi * gqxx[5] + qyyi * gqyy[5] + qzzi * gqzz[5]
                        + 2.0 * (qxyi * gqxy[5] + qxzi * gqxz[5] + qyzi * gqyz[5]));
                double kxy = -0.5 * (ci * gqxy[1] - uxi * gqxy[2] - uyi * gqxy[3] - uzi * gqxy[4] + qxxi * gqxy[5]
                        + qyyi * gqxy[8] + qzzi * gqxy[10]
                        + 2.0 * (qxyi * gqxy[6] + qxzi * gqxy[7] + qyzi * gqxy[9]) + ci * gc[6] - uxi * gux[6]
                        - uyi * guy[6] - uzi * guz[6] + qxxi * gqxx[6] + qyyi * gqyy[6] + qzzi * gqzz[6]
                        + 2.0 * (qxyi * gqxy[6] + qxzi * gqxz[6] + qyzi * gqyz[6]));
                double kxz = -0.5 * (ci * gqxz[1] - uxi * gqxz[2] - uyi * gqxz[3] - uzi * gqxz[4] + qxxi * gqxz[5]
                        + qyyi * gqxz[8] + qzzi * gqxz[10]
                        + 2.0 * (qxyi * gqxz[6] + qxzi * gqxz[7] + qyzi * gqxz[9]) + ci * gc[7] - uxi * gux[7]
                        - uyi * guy[7] - uzi * guz[7] + qxxi * gqxx[7] + qyyi * gqyy[7] + qzzi * gqzz[7]
                        + 2.0 * (qxyi * gqxy[7] + qxzi * gqxz[7] + qyzi * gqyz[7]));
                double kyy = -0.5 * (ci * gqyy[1] - uxi * gqyy[2] - uyi * gqyy[3] - uzi * gqyy[4] + qxxi * gqyy[5]
                        + qyyi * gqyy[8] + qzzi * gqyy[10]
                        + 2.0 * (qxyi * gqyy[6] + qxzi * gqyy[7] + qyzi * gqyy[9]) + ci * gc[8] - uxi * gux[8]
                        - uyi * guy[8] - uzi * guz[8] + qxxi * gqxx[8] + qyyi * gqyy[8] + qzzi * gqzz[8]
                        + 2.0 * (qxyi * gqxy[8] + qxzi * gqxz[8] + qyzi * gqyz[8]));
                double kyz = -0.5 * (ci * gqyz[1] - uxi * gqyz[2] - uyi * gqyz[3] - uzi * gqyz[4] + qxxi * gqyz[5]
                        + qyyi * gqyz[8] + qzzi * gqyz[10]
                        + 2.0 * (qxyi * gqyz[6] + qxzi * gqyz[7] + qyzi * gqyz[9]) + ci * gc[9] - uxi * gux[9]
                        - uyi * guy[9] - uzi * guz[9] + qxxi * gqxx[9] + qyyi * gqyy[9] + qzzi * gqzz[9]
                        + 2.0 * (qxyi * gqxy[9] + qxzi * gqxz[9] + qyzi * gqyz[9]));
                double kzz = -0.5 * (ci * gqzz[1] - uxi * gqzz[2] - uyi * gqzz[3] - uzi * gqzz[4] + qxxi * gqzz[5]
                        + qyyi * gqzz[8] + qzzi * gqzz[10]
                        + 2.0 * (qxyi * gqzz[6] + qxzi * gqzz[7] + qyzi * gqzz[9]) + ci * gc[10] - uxi * gux[10]
                        - uyi * guy[10] - uzi * guz[10] + qxxi * gqxx[10] + qyyi * gqyy[10] + qzzi * gqzz[10]
                        + 2.0 * (qxyi * gqxy[10] + qxzi * gqxz[10] + qyzi * gqyz[10]));
                double kyx = kxy;
                double kzx = kxz;
                double kzy = kyz;
                tix += 2.0 * (qxyi * ixz + qyyi * iyz + qyzi * izz - qxzi * ixy - qyzi * iyy - qzzi * izy);
                tiy += 2.0 * (qxzi * ixx + qyzi * iyx + qzzi * izx - qxxi * ixz - qxyi * iyz - qxzi * izz);
                tiz += 2.0 * (qxxi * ixy + qxyi * iyy + qxzi * izy - qxyi * ixx - qyyi * iyx - qyzi * izx);
                tkx += 2.0 * (qxyk * kxz + qyyk * kyz + qyzk * kzz - qxzk * kxy - qyzk * kyy - qzzk * kzy);
                tky += 2.0 * (qxzk * kxx + qyzk * kyx + qzzk * kzx - qxxk * kxz - qxyk * kyz - qxzk * kzz);
                tkz += 2.0 * (qxxk * kxy + qxyk * kyy + qxzk * kzy - qxyk * kxx - qyyk * kyx - qyzk * kzx);
                tX[i] += lambda * tix;
                tY[i] += lambda * tiy;
                tZ[i] += lambda * tiz;
                tX[k] += lambda * tkx;
                tY[k] += lambda * tky;
                tZ[k] += lambda * tkz;
                if (lambdaTerm) {
                    ltX[i] += tix;
                    ltY[i] += tiy;
                    ltZ[i] += tiz;
                    ltX[k] += tkx;
                    ltY[k] += tky;
                    ltZ[k] += tkz;
                }
            }

            private void polarizationEnergyGradient(int i, int k) {
                /**
                 * Electrostatic solvation free energy gradient of the permanent
                 * multipoles in the reaction potential of the induced dipoles.
                 */
                final double dpsymdx = -uxi * (sxk * gux[5] + syk * guy[5] + szk * guz[5])
                        - uyi * (sxk * gux[6] + syk * guy[6] + szk * guz[6])
                        - uzi * (sxk * gux[7] + syk * guy[7] + szk * guz[7])
                        - uxk * (sxi * gux[5] + syi * guy[5] + szi * guz[5])
                        - uyk * (sxi * gux[6] + syi * guy[6] + szi * guz[6])
                        - uzk * (sxi * gux[7] + syi * guy[7] + szi * guz[7]);
                final double dpwidx = ci * (sxk * gc[5] + syk * gc[6] + szk * gc[7])
                        - ck * (sxi * gux[2] + syi * guy[2] + szi * guz[2])
                        - sxi * (qxxk * gux[11] + qyyk * gux[14] + qzzk * gux[16]
                                + 2.0 * (qxyk * gux[12] + qxzk * gux[13] + qyzk * gux[15]))
                        - syi * (qxxk * guy[11] + qyyk * guy[14] + qzzk * guy[16]
                                + 2.0 * (qxyk * guy[12] + qxzk * guy[13] + qyzk * guy[15]))
                        - szi * (qxxk * guz[11] + qyyk * guz[14] + qzzk * guz[16]
                                + 2.0 * (qxyk * guz[12] + qxzk * guz[13] + qyzk * guz[15]))
                        + sxk * (qxxi * gqxx[5] + qyyi * gqyy[5] + qzzi * gqzz[5]
                                + 2.0 * (qxyi * gqxy[5] + qxzi * gqxz[5] + qyzi * gqyz[5]))
                        + syk * (qxxi * gqxx[6] + qyyi * gqyy[6] + qzzi * gqzz[6]
                                + 2.0 * (qxyi * gqxy[6] + qxzi * gqxz[6] + qyzi * gqyz[6]))
                        + szk * (qxxi * gqxx[7] + qyyi * gqyy[7] + qzzi * gqzz[7]
                                + 2.0 * (qxyi * gqxy[7] + qxzi * gqxz[7] + qyzi * gqyz[7]));
                final double dpwkdx = ci * (sxk * gux[2] + syk * guy[2] + szk * guz[2])
                        - ck * (sxi * gc[5] + syi * gc[6] + szi * gc[7])
                        - sxi * (qxxk * gqxx[5] + qyyk * gqyy[5] + qzzk * gqzz[5]
                                + 2.0 * (qxyk * gqxy[5] + qxzk * gqxz[5] + qyzk * gqyz[5]))
                        - syi * (qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                                + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]))
                        - szi * (qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                                + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]))
                        + sxk * (qxxi * gux[11] + qyyi * gux[14] + qzzi * gux[16]
                                + 2.0 * (qxyi * gux[12] + qxzi * gux[13] + qyzi * gux[15]))
                        + syk * (qxxi * guy[11] + qyyi * guy[14] + qzzi * guy[16]
                                + 2.0 * (qxyi * guy[12] + qxzi * guy[13] + qyzi * guy[15]))
                        + szk * (qxxi * guz[11] + qyyi * guz[14] + qzzi * guz[16]
                                + 2.0 * (qxyi * guz[12] + qxzi * guz[13] + qyzi * guz[15]));
                final double dpsymdy = -uxi * (sxk * gux[6] + syk * guy[6] + szk * guz[6])
                        - uyi * (sxk * gux[8] + syk * guy[8] + szk * guz[8])
                        - uzi * (sxk * gux[9] + syk * guy[9] + szk * guz[9])
                        - uxk * (sxi * gux[6] + syi * guy[6] + szi * guz[6])
                        - uyk * (sxi * gux[8] + syi * guy[8] + szi * guz[8])
                        - uzk * (sxi * gux[9] + syi * guy[9] + szi * guz[9]);
                final double dpwidy = ci * (sxk * gc[6] + syk * gc[8] + szk * gc[9])
                        - ck * (sxi * gux[3] + syi * guy[3] + szi * guz[3])
                        - sxi * (qxxk * gux[12] + qyyk * gux[17] + qzzk * gux[19]
                                + 2.0 * (qxyk * gux[14] + qxzk * gux[15] + qyzk * gux[18]))
                        - syi * (qxxk * guy[12] + qyyk * guy[17] + qzzk * guy[19]
                                + 2.0 * (qxyk * guy[14] + qxzk * guy[15] + qyzk * guy[18]))
                        - szi * (qxxk * guz[12] + qyyk * guz[17] + qzzk * guz[19]
                                + 2.0 * (qxyk * guz[14] + qxzk * guz[15] + qyzk * guz[18]))
                        + sxk * (qxxi * gqxx[6] + qyyi * gqyy[6] + qzzi * gqzz[6]
                                + 2.0 * (qxyi * gqxy[6] + qxzi * gqxz[6] + qyzi * gqyz[6]))
                        + syk * (qxxi * gqxx[8] + qyyi * gqyy[8] + qzzi * gqzz[8]
                                + 2.0 * (qxyi * gqxy[8] + qxzi * gqxz[8] + qyzi * gqyz[8]))
                        + szk * (qxxi * gqxx[9] + qyyi * gqyy[9] + qzzi * gqzz[9]
                                + 2.0 * (qxyi * gqxy[9] + qxzi * gqxz[9] + qyzi * gqyz[9]));
                final double dpwkdy = ci * (sxk * gux[3] + syk * guy[3] + szk * guz[3])
                        - ck * (sxi * gc[6] + syi * gc[8] + szi * gc[9])
                        - sxi * (qxxk * gqxx[6] + qyyk * gqyy[6] + qzzk * gqzz[6]
                                + 2.0 * (qxyk * gqxy[6] + qxzk * gqxz[6] + qyzk * gqyz[6]))
                        - syi * (qxxk * gqxx[8] + qyyk * gqyy[8] + qzzk * gqzz[8]
                                + 2.0 * (qxyk * gqxy[8] + qxzk * gqxz[8] + qyzk * gqyz[8]))
                        - szi * (qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                                + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9]))
                        + sxk * (qxxi * gux[12] + qyyi * gux[17] + qzzi * gux[19]
                                + 2.0 * (qxyi * gux[14] + qxzi * gux[15] + qyzi * gux[18]))
                        + syk * (qxxi * guy[12] + qyyi * guy[17] + qzzi * guy[19]
                                + 2.0 * (qxyi * guy[14] + qxzi * guy[15] + qyzi * guy[18]))
                        + szk * (qxxi * guz[12] + qyyi * guz[17] + qzzi * guz[19]
                                + 2.0 * (qxyi * guz[14] + qxzi * guz[15] + qyzi * guz[18]));
                final double dpsymdz = -uxi * (sxk * gux[7] + syk * guy[7] + szk * guz[7])
                        - uyi * (sxk * gux[9] + syk * guy[9] + szk * guz[9])
                        - uzi * (sxk * gux[10] + syk * guy[10] + szk * guz[10])
                        - uxk * (sxi * gux[7] + syi * guy[7] + szi * guz[7])
                        - uyk * (sxi * gux[9] + syi * guy[9] + szi * guz[9])
                        - uzk * (sxi * gux[10] + syi * guy[10] + szi * guz[10]);
                final double dpwidz = ci * (sxk * gc[7] + syk * gc[9] + szk * gc[10])
                        - ck * (sxi * gux[4] + syi * guy[4] + szi * guz[4])
                        - sxi * (qxxk * gux[13] + qyyk * gux[18] + qzzk * gux[20]
                                + 2.0 * (qxyk * gux[15] + qxzk * gux[16] + qyzk * gux[19]))
                        - syi * (qxxk * guy[13] + qyyk * guy[18] + qzzk * guy[20]
                                + 2.0 * (qxyk * guy[15] + qxzk * guy[16] + qyzk * guy[19]))
                        - szi * (qxxk * guz[13] + qyyk * guz[18] + qzzk * guz[20]
                                + 2.0 * (qxyk * guz[15] + qxzk * guz[16] + qyzk * guz[19]))
                        + sxk * (qxxi * gqxx[7] + qyyi * gqyy[7] + qzzi * gqzz[7]
                                + 2.0 * (qxyi * gqxy[7] + qxzi * gqxz[7] + qyzi * gqyz[7]))
                        + syk * (qxxi * gqxx[9] + qyyi * gqyy[9] + qzzi * gqzz[9]
                                + 2.0 * (qxyi * gqxy[9] + qxzi * gqxz[9] + qyzi * gqyz[9]))
                        + szk * (qxxi * gqxx[10] + qyyi * gqyy[10] + qzzi * gqzz[10]
                                + 2.0 * (qxyi * gqxy[10] + qxzi * gqxz[10] + qyzi * gqyz[10]));
                final double dpwkdz = ci * (sxk * gux[4] + syk * guy[4] + szk * guz[4])
                        - ck * (sxi * gc[7] + syi * gc[9] + szi * gc[10])
                        - sxi * (qxxk * gqxx[7] + qyyk * gqyy[7] + qzzk * gqzz[7]
                                + 2.0 * (qxyk * gqxy[7] + qxzk * gqxz[7] + qyzk * gqyz[7]))
                        - syi * (qxxk * gqxx[9] + qyyk * gqyy[9] + qzzk * gqzz[9]
                                + 2.0 * (qxyk * gqxy[9] + qxzk * gqxz[9] + qyzk * gqyz[9]))
                        - szi * (qxxk * gqxx[10] + qyyk * gqyy[10] + qzzk * gqzz[10]
                                + 2.0 * (qxyk * gqxy[10] + qxzk * gqxz[10] + qyzk * gqyz[10]))
                        + sxk * (qxxi * gux[13] + qyyi * gux[18] + qzzi * gux[20]
                                + 2.0 * (qxyi * gux[15] + qxzi * gux[16] + qyzi * gux[19]))
                        + syk * (qxxi * guy[13] + qyyi * guy[18] + qzzi * guy[20]
                                + 2.0 * (qxyi * guy[15] + qxzi * guy[16] + qyzi * guy[19]))
                        + szk * (qxxi * guz[13] + qyyi * guz[18] + qzzi * guz[20]
                                + 2.0 * (qxyi * guz[15] + qxzi * guz[16] + qyzi * guz[19]));
                /**
                 * Effective radii chain rule terms for the electrostatic
                 * solvation free energy gradient of the permanent multipoles in
                 * the reaction potential of the induced dipoles.
                 */
                final double dsymdr = -uxi * (sxk * gux[22] + syk * guy[22] + szk * guz[22])
                        - uyi * (sxk * gux[23] + syk * guy[23] + szk * guz[23])
                        - uzi * (sxk * gux[24] + syk * guy[24] + szk * guz[24])
                        - uxk * (sxi * gux[22] + syi * guy[22] + szi * guz[22])
                        - uyk * (sxi * gux[23] + syi * guy[23] + szi * guz[23])
                        - uzk * (sxi * gux[24] + syi * guy[24] + szi * guz[24]);
                final double dwipdr = ci * (sxk * gc[22] + syk * gc[23] + szk * gc[24])
                        - ck * (sxi * gux[21] + syi * guy[21] + szi * guz[21])
                        - sxi * (qxxk * gux[25] + qyyk * gux[28] + qzzk * gux[30]
                                + 2.0 * (qxyk * gux[26] + qxzk * gux[27] + qyzk * gux[29]))
                        - syi * (qxxk * guy[25] + qyyk * guy[28] + qzzk * guy[30]
                                + 2.0 * (qxyk * guy[26] + qxzk * guy[27] + qyzk * guy[29]))
                        - szi * (qxxk * guz[25] + qyyk * guz[28] + qzzk * guz[30]
                                + 2.0 * (qxyk * guz[26] + qxzk * guz[27] + qyzk * guz[29]))
                        + sxk * (qxxi * gqxx[22] + qyyi * gqyy[22] + qzzi * gqzz[22]
                                + 2.0 * (qxyi * gqxy[22] + qxzi * gqxz[22] + qyzi * gqyz[22]))
                        + syk * (qxxi * gqxx[23] + qyyi * gqyy[23] + qzzi * gqzz[23]
                                + 2.0 * (qxyi * gqxy[23] + qxzi * gqxz[23] + qyzi * gqyz[23]))
                        + szk * (qxxi * gqxx[24] + qyyi * gqyy[24] + qzzi * gqzz[24]
                                + 2.0 * (qxyi * gqxy[24] + qxzi * gqxz[24] + qyzi * gqyz[24]));
                final double dwkpdr = ci * (sxk * gux[21] + syk * guy[21] + szk * guz[21])
                        - ck * (sxi * gc[22] + syi * gc[23] + szi * gc[24])
                        - sxi * (qxxk * gqxx[22] + qyyk * gqyy[22] + qzzk * gqzz[22]
                                + 2.0 * (qxyk * gqxy[22] + qxzk * gqxz[22] + qyzk * gqyz[22]))
                        - syi * (qxxk * gqxx[23] + qyyk * gqyy[23] + qzzk * gqzz[23]
                                + 2.0 * (qxyk * gqxy[23] + qxzk * gqxz[23] + qyzk * gqyz[23]))
                        - szi * (qxxk * gqxx[24] + qyyk * gqyy[24] + qzzk * gqzz[24]
                                + 2.0 * (qxyk * gqxy[24] + qxzk * gqxz[24] + qyzk * gqyz[24]))
                        + sxk * (qxxi * gux[25] + qyyi * gux[28] + qzzi * gux[30]
                                + 2.0 * (qxyi * gux[26] + qxzi * gux[27] + qyzi * gux[29]))
                        + syk * (qxxi * guy[25] + qyyi * guy[28] + qzzi * guy[30]
                                + 2.0 * (qxyi * guy[26] + qxzi * guy[27] + qyzi * guy[29]))
                        + szk * (qxxi * guz[25] + qyyi * guz[28] + qzzi * guz[30]
                                + 2.0 * (qxyi * guz[26] + qxzi * guz[27] + qyzi * guz[29]));
                double dpdx = 0.5 * (dpsymdx + 0.5 * (dpwidx + dpwkdx));
                double dpdy = 0.5 * (dpsymdy + 0.5 * (dpwidy + dpwkdy));
                double dpdz = 0.5 * (dpsymdz + 0.5 * (dpwidz + dpwkdz));
                double dsumdri = dsymdr + 0.5 * (dwipdr + dwkpdr);
                double dbi = 0.5 * rbk * dsumdri;
                double dbk = 0.5 * rbi * dsumdri;
                if (polarization == Polarization.MUTUAL) {
                    dpdx -= 0.5 * (dxi * (pxk * gux[5] + pyk * gux[6] + pzk * gux[7])
                            + dyi * (pxk * guy[5] + pyk * guy[6] + pzk * guy[7])
                            + dzi * (pxk * guz[5] + pyk * guz[6] + pzk * guz[7])
                            + dxk * (pxi * gux[5] + pyi * gux[6] + pzi * gux[7])
                            + dyk * (pxi * guy[5] + pyi * guy[6] + pzi * guy[7])
                            + dzk * (pxi * guz[5] + pyi * guz[6] + pzi * guz[7]));
                    dpdy -= 0.5 * (dxi * (pxk * gux[6] + pyk * gux[8] + pzk * gux[9])
                            + dyi * (pxk * guy[6] + pyk * guy[8] + pzk * guy[9])
                            + dzi * (pxk * guz[6] + pyk * guz[8] + pzk * guz[9])
                            + dxk * (pxi * gux[6] + pyi * gux[8] + pzi * gux[9])
                            + dyk * (pxi * guy[6] + pyi * guy[8] + pzi * guy[9])
                            + dzk * (pxi * guz[6] + pyi * guz[8] + pzi * guz[9]));
                    dpdz -= 0.5 * (dxi * (pxk * gux[7] + pyk * gux[9] + pzk * gux[10])
                            + dyi * (pxk * guy[7] + pyk * guy[9] + pzk * guy[10])
                            + dzi * (pxk * guz[7] + pyk * guz[9] + pzk * guz[10])
                            + dxk * (pxi * gux[7] + pyi * gux[9] + pzi * gux[10])
                            + dyk * (pxi * guy[7] + pyi * guy[9] + pzi * guy[10])
                            + dzk * (pxi * guz[7] + pyi * guz[9] + pzi * guz[10]));
                    final double duvdr = dxi * (pxk * gux[22] + pyk * gux[23] + pzk * gux[24])
                            + dyi * (pxk * guy[22] + pyk * guy[23] + pzk * guy[24])
                            + dzi * (pxk * guz[22] + pyk * guz[23] + pzk * guz[24])
                            + dxk * (pxi * gux[22] + pyi * gux[23] + pzi * gux[24])
                            + dyk * (pxi * guy[22] + pyi * guy[23] + pzi * guy[24])
                            + dzk * (pxi * guz[22] + pyi * guz[23] + pzi * guz[24]);
                    dbi -= 0.5 * rbk * duvdr;
                    dbk -= 0.5 * rbi * duvdr;
                }
                /**
                 * Increment the gradients and Born chain rule term.
                 */
                if (i == k) {
                    gb_local[i] += dbi;
                } else {
                    gX[i] -= lambda * dpdx;
                    gY[i] -= lambda * dpdy;
                    gZ[i] -= lambda * dpdz;
                    gb_local[i] += dbi;
                    gX[k] += lambda * dpdx;
                    gY[k] += lambda * dpdy;
                    gZ[k] += lambda * dpdz;
                    gb_local[k] += dbk;
                    if (lambdaTerm) {
                        lgX[i] -= dpdx;
                        lgY[i] -= dpdy;
                        lgZ[i] -= dpdz;
                        lgX[k] += dpdx;
                        lgY[k] += dpdy;
                        lgZ[k] += dpdz;
                    }
                }
                polarizationEnergyTorque(i, k);
            }

            private void polarizationEnergyTorque(int i, int k) {
                double fix = 0.5 * (sxk * gux[2] + syk * guy[2] + szk * guz[2]);
                double fiy = 0.5 * (sxk * gux[3] + syk * guy[3] + szk * guz[3]);
                double fiz = 0.5 * (sxk * gux[4] + syk * guy[4] + szk * guz[4]);
                double fkx = 0.5 * (sxi * gux[2] + syi * guy[2] + szi * guz[2]);
                double fky = 0.5 * (sxi * gux[3] + syi * guy[3] + szi * guz[3]);
                double fkz = 0.5 * (sxi * gux[4] + syi * guy[4] + szi * guz[4]);
                double fixx = -0.25 * ((sxk * gqxx[2] + syk * gqxx[3] + szk * gqxx[4])
                        + (sxk * gux[5] + syk * guy[5] + szk * guz[5]));
                double fixy = -0.25 * ((sxk * gqxy[2] + syk * gqxy[3] + szk * gqxy[4])
                        + (sxk * gux[6] + syk * guy[6] + szk * guz[6]));
                double fixz = -0.25 * ((sxk * gqxz[2] + syk * gqxz[3] + szk * gqxz[4])
                        + (sxk * gux[7] + syk * guy[7] + szk * guz[7]));
                double fiyy = -0.25 * ((sxk * gqyy[2] + syk * gqyy[3] + szk * gqyy[4])
                        + (sxk * gux[8] + syk * guy[8] + szk * guz[8]));
                double fiyz = -0.25 * ((sxk * gqyz[2] + syk * gqyz[3] + szk * gqyz[4])
                        + (sxk * gux[9] + syk * guy[9] + szk * guz[9]));
                double fizz = -0.25 * ((sxk * gqzz[2] + syk * gqzz[3] + szk * gqzz[4])
                        + (sxk * gux[10] + syk * guy[10] + szk * guz[10]));
                double fiyx = fixy;
                double fizx = fixz;
                double fizy = fiyz;
                double fkxx = 0.25 * ((sxi * gqxx[2] + syi * gqxx[3] + szi * gqxx[4])
                        + (sxi * gux[5] + syi * guy[5] + szi * guz[5]));
                double fkxy = 0.25 * ((sxi * gqxy[2] + syi * gqxy[3] + szi * gqxy[4])
                        + (sxi * gux[6] + syi * guy[6] + szi * guz[6]));
                double fkxz = 0.25 * ((sxi * gqxz[2] + syi * gqxz[3] + szi * gqxz[4])
                        + (sxi * gux[7] + syi * guy[7] + szi * guz[7]));
                double fkyy = 0.25 * ((sxi * gqyy[2] + syi * gqyy[3] + szi * gqyy[4])
                        + (sxi * gux[8] + syi * guy[8] + szi * guz[8]));
                double fkyz = 0.25 * ((sxi * gqyz[2] + syi * gqyz[3] + szi * gqyz[4])
                        + (sxi * gux[9] + syi * guy[9] + szi * guz[9]));
                double fkzz = 0.25 * ((sxi * gqzz[2] + syi * gqzz[3] + szi * gqzz[4])
                        + (sxi * gux[10] + syi * guy[10] + szi * guz[10]));
                double fkyx = fkxy;
                double fkzx = fkxz;
                double fkzy = fkyz;
                if (i == k) {
                    fix *= 0.5;
                    fiy *= 0.5;
                    fiz *= 0.5;
                    fkx *= 0.5;
                    fky *= 0.5;
                    fkz *= 0.5;
                    fixx *= 0.5;
                    fixy *= 0.5;
                    fixz *= 0.5;
                    fiyx *= 0.5;
                    fiyy *= 0.5;
                    fiyz *= 0.5;
                    fizx *= 0.5;
                    fizy *= 0.5;
                    fizz *= 0.5;
                    fkxx *= 0.5;
                    fkxy *= 0.5;
                    fkxz *= 0.5;
                    fkyx *= 0.5;
                    fkyy *= 0.5;
                    fkyz *= 0.5;
                    fkzx *= 0.5;
                    fkzy *= 0.5;
                    fkzz *= 0.5;
                }
                /**
                 * Torque due to induced reaction field on permanent dipoles.
                 */
                double tix = uyi * fiz - uzi * fiy;
                double tiy = uzi * fix - uxi * fiz;
                double tiz = uxi * fiy - uyi * fix;
                double tkx = uyk * fkz - uzk * fky;
                double tky = uzk * fkx - uxk * fkz;
                double tkz = uxk * fky - uyk * fkx;
                /**
                 * Torque due to induced reaction field gradient on quadrupoles.
                 */
                tix += 2.0 * (qxyi * fixz + qyyi * fiyz + qyzi * fizz - qxzi * fixy - qyzi * fiyy - qzzi * fizy);
                tiy += 2.0 * (qxzi * fixx + qyzi * fiyx + qzzi * fizx - qxxi * fixz - qxyi * fiyz - qxzi * fizz);
                tiz += 2.0 * (qxxi * fixy + qxyi * fiyy + qxzi * fizy - qxyi * fixx - qyyi * fiyx - qyzi * fizx);
                tkx += 2.0 * (qxyk * fkxz + qyyk * fkyz + qyzk * fkzz - qxzk * fkxy - qyzk * fkyy - qzzk * fkzy);
                tky += 2.0 * (qxzk * fkxx + qyzk * fkyx + qzzk * fkzx - qxxk * fkxz - qxyk * fkyz - qxzk * fkzz);
                tkz += 2.0 * (qxxk * fkxy + qxyk * fkyy + qxzk * fkzy - qxyk * fkxx - qyyk * fkyx - qyzk * fkzx);
                tX[i] += lambda * tix;
                tY[i] += lambda * tiy;
                tZ[i] += lambda * tiz;
                tX[k] += lambda * tkx;
                tY[k] += lambda * tky;
                tZ[k] += lambda * tkz;
                if (lambdaTerm) {
                    ltX[i] += tix;
                    ltY[i] += tiy;
                    ltZ[i] += tiz;
                    ltX[k] += tkx;
                    ltY[k] += tky;
                    ltZ[k] += tkz;
                }
            }
        }
    }

    /**
     * Compute Born radii chain rule terms in parallel via the Grycuk method.
     *
     * @since 1.0
     */
    private class BornCRRegion extends ParallelRegion {

        private final BornCRLoop bornCRLoop[];
        private final SharedDouble ecavTot;

        public BornCRRegion(int nt) {
            bornCRLoop = new BornCRLoop[nt];
            for (int i = 0; i < nt; i++) {
                bornCRLoop[i] = new BornCRLoop();
            }
            ecavTot = new SharedDouble(0.0);
        }

        @Override
        public void run() {
            try {
                execute(0, nAtoms - 1, bornCRLoop[getThreadIndex()]);
            } catch (Exception e) {
                String message = "Fatal exception computing Born radii chain rule term in thread "
                        + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute Born radii chain rule terms for a range of atoms via the
         * Grycuk method.
         *
         * @since 1.0
         */
        private class BornCRLoop extends IntegerForLoop {

            private final double factor = -pow(PI, THIRD) * pow(6.0, (2.0 * THIRD)) / 9.0;
            private final double dx_local[];
            private double gX[];
            private double gY[];
            private double gZ[];
            private double lgX[];
            private double lgY[];
            private double lgZ[];
            private double ecav;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public BornCRLoop() {
                dx_local = new double[3];
                ecav = 0.0;
            }

            @Override
            public void start() {
                int threadID = getThreadIndex();
                gX = grad[threadID][0];
                gY = grad[threadID][1];
                gZ = grad[threadID][2];
                if (lambdaTerm) {
                    lgX = lambdaGrad[threadID][0];
                    lgY = lambdaGrad[threadID][1];
                    lgZ = lambdaGrad[threadID][2];
                }
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!use[i]) {
                        continue;
                    }
                    final double ri = baseRadiusWithBondi[i];
                    assert (ri > 0.0);
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    final double rbi = born[i];
                    double termi = PI4_3 / (rbi * rbi * rbi);
                    termi = factor / pow(termi, (4.0 * THIRD));
                    int list[] = neighborLists[0][i];
                    int nPair = list.length;
                    for (int l = 0; l < nPair; l++) {
                        int k = list[l];
                        if (!use[k]) {
                            continue;
                        }
                        final double rk = baseRadiusWithBondi[k];
                        if (k != i && rk > 0.0) {
                            dx_local[0] = x[k] - xi;
                            dx_local[1] = y[k] - yi;
                            dx_local[2] = z[k] - zi;
                            double r2 = crystal.image(dx_local);
                            if (r2 > cut2) {
                                continue;
                            }
                            final double xr = dx_local[0];
                            final double yr = dx_local[1];
                            final double zr = dx_local[2];

                            // Atom i being descreeened by atom k.
                            final double sk = rk * overlapScale[k];
                            final double sk2 = sk * sk;
                            final double r = sqrt(r2);
                            double de = 0.0;
                            // Atom i is engulfed by atom k.
                            if (ri + r < sk) {
                                final double uik = sk - r;
                                de = -4.0 * PI / pow(uik, 4);
                            }
                            // Lower integration bound depends on atoms sizes and separation.
                            if (ri + r < sk) {
                                // Atom i is engulfed by atom k.
                                final double lik = sk - r;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (sk2 - 4.0 * sk * r + 17.0 * r2) / (r2 * lik4);
                            } else if (r < ri + sk) {
                                // Atoms are overlapped, begin integration from ri.
                                double lik = ri;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (2.0 * ri * ri - sk2 - r2) / (r2 * lik4);
                            } else {
                                // No overlap between atoms.
                                double lik = r - sk;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (sk2 - 4.0 * sk * r + r2) / (r2 * lik4);
                            }
                            // Upper integration bound is always the same.
                            double uik = r + sk;
                            double uik4 = pow(uik, 4);
                            de = de - 0.25 * PI * (sk2 + 4.0 * sk * r + r2) / (r2 * uik4);

                            double dbr = termi * de / r;
                            de = dbr * sharedBornGrad.get(i);
                            /**
                             * Increment the overall derivatives.
                             */
                            double dedx = de * xr;
                            double dedy = de * yr;
                            double dedz = de * zr;
                            gX[i] += lambda * dedx;
                            gY[i] += lambda * dedy;
                            gZ[i] += lambda * dedz;
                            gX[k] -= lambda * dedx;
                            gY[k] -= lambda * dedy;
                            gZ[k] -= lambda * dedz;
                            if (lambdaTerm) {
                                lgX[i] += dedx;
                                lgY[i] += dedy;
                                lgZ[i] += dedz;
                                lgX[k] -= dedx;
                                lgY[k] -= dedy;
                                lgZ[k] -= dedz;
                            }

                            // Atom k being descreeened by atom i.
                            double rbk = born[k];
                            double termk = PI4_3 / (rbk * rbk * rbk);
                            termk = factor / pow(termk, (4.0 * THIRD));
                            final double si = ri * overlapScale[i];
                            final double si2 = si * si;
                            de = 0.0;
                            // Atom i is engulfed by atom k.
                            if (rk + r < si) {
                                uik = si - r;
                                de = -4.0 * PI / pow(uik, 4);
                            }
                            // Lower integration bound depends on atoms sizes and separation.
                            if (rk + r < si) {
                                // Atom i is engulfed by atom k.
                                final double lik = si - r;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (si2 - 4.0 * si * r + 17.0 * r2) / (r2 * lik4);
                            } else if (r < rk + si) {
                                // Atoms are overlapped, begin integration from ri.
                                double lik = rk;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (2.0 * rk * rk - si2 - r2) / (r2 * lik4);
                            } else {
                                // No overlap between atoms.
                                double lik = r - si;
                                double lik4 = pow(lik, 4);
                                de = de + 0.25 * PI * (si2 - 4.0 * si * r + r2) / (r2 * lik4);
                            }
                            // Upper integration bound is always the same.
                            uik = r + si;
                            uik4 = pow(uik, 4);
                            de = de - 0.25 * PI * (si2 + 4.0 * si * r + r2) / (r2 * uik4);
                            dbr = termk * de / r;
                            de = dbr * sharedBornGrad.get(k);
                            /**
                             * Increment the overall derivatives.
                             */
                            dedx = de * xr;
                            dedy = de * yr;
                            dedz = de * zr;
                            gX[i] += lambda * dedx;
                            gY[i] += lambda * dedy;
                            gZ[i] += lambda * dedz;
                            gX[k] -= lambda * dedx;
                            gY[k] -= lambda * dedy;
                            gZ[k] -= lambda * dedz;
                            if (lambdaTerm) {
                                lgX[i] += dedx;
                                lgY[i] += dedy;
                                lgZ[i] += dedz;
                                lgX[k] -= dedx;
                                lgY[k] -= dedy;
                                lgZ[k] -= dedz;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Compute Dispersion energy in parallel via pairwise descreening.
     *
     * @since 1.0
     */
    private class DispersionRegion extends ParallelRegion {

        private final DispersionLoop dispersionLoop[];
        private final SharedDouble sharedDispersion;
        private boolean gradient = false;
        private double[] cdisp = null;
        private static final double DISP_OVERLAP_SCALE_FACTOER = 0.81;
        private static final double SLEVY = 1.0;
        private static final double AWATER = 0.033428;
        private static final double EPSO = 0.1100;
        private static final double EPSH = 0.0135;
        private static final double RMINO = 1.7025;
        private static final double RMINH = 1.3275;

        public DispersionRegion(int nt) {
            dispersionLoop = new DispersionLoop[nt];
            for (int i = 0; i < nt; i++) {
                dispersionLoop[i] = new DispersionLoop();
            }
            sharedDispersion = new SharedDouble();
            cdisp = new double[nAtoms];

            for (int i = 0; i < nAtoms; i++) {
                VDWType type = atoms[i].getVDWType();
                double rmini = type.radius;
                rDisp[i] = rmini / 2.0;
            }
            maxDispersionEnergy();
        }

        public void init() {
            cdisp = new double[nAtoms];

            for (int i = 0; i < nAtoms; i++) {
                VDWType type = atoms[i].getVDWType();
                double rmini = type.radius;
                rDisp[i] = rmini / 2.0;
            }
            maxDispersionEnergy();
        }

        public double getEnergy() {
            return sharedDispersion.get();
        }

        /**
         * Compute the maximum Dispersion energy for each atom in isolation. The
         * loss of dispersion energy due to descreening of other atoms is then
         * calculated in the DispersionLoop.
         */
        private void maxDispersionEnergy() {
            for (int i = 0; i < nAtoms; i++) {
                VDWType type = atoms[i].getVDWType();
                double epsi = type.wellDepth;
                double rmini = type.radius / 2.0;
                if (rDisp[i] > 0.0 && epsi > 0.0) {
                    double sqEpsoEpsi = sqrt(EPSO) + sqrt(epsi);
                    double sqEpshEpsi = sqrt(EPSH) + sqrt(epsi);
                    double emixo = 4.0 * EPSO * epsi / (pow(sqEpsoEpsi, 2));
                    double rmixo = 2.0 * (pow(RMINO, 3) + pow(rmini, 3)) / (pow(RMINO, 2) + pow(rmini, 2));
                    double rmixo3 = pow(rmixo, 3);
                    double rmixo7 = pow(rmixo, 7);
                    double ao = emixo * rmixo7;
                    double emixh = 4.0 * EPSH * epsi / (pow(sqEpshEpsi, 2));
                    double rmixh = 2.0 * (pow(RMINH, 3) + pow(rmini, 3)) / (pow(RMINH, 2) + pow(rmini, 2));
                    double rmixh3 = pow(rmixh, 3);
                    double rmixh7 = pow(rmixh, 7);
                    double ah = emixh * rmixh7;
                    double ri = rDisp[i] + 0.26;
                    double ri3 = pow(ri, 3);
                    double ri7 = pow(ri, 7);
                    double ri11 = pow(ri, 11);
                    if (ri < rmixh) {
                        cdisp[i] = -4.0 * PI * emixh * (rmixh3 - ri3) / 3.0;
                        cdisp[i] = cdisp[i] - emixh * 18.0 / 11.0 * rmixh3 * PI;
                    } else {
                        cdisp[i] = 2.0 * PI * (2.0 * rmixh7 - 11.0 * ri7) * ah;
                        cdisp[i] = cdisp[i] / (11.0 * ri11);
                    }
                    cdisp[i] = 2.0 * cdisp[i];
                    if (ri < rmixo) {
                        cdisp[i] = cdisp[i] - 4.0 * PI * emixo * (rmixo3 - ri3) / 3.0;
                        cdisp[i] = cdisp[i] - emixo * 18.0 / 11.0 * rmixo3 * PI;
                    } else {
                        cdisp[i] = cdisp[i] + 2.0 * PI * (2.0 * rmixo7 - 11.0 * ri7) * ao / (11.0 * ri11);
                    }
                }
                cdisp[i] = SLEVY * AWATER * cdisp[i];
            }
        }

        public void setGradient(boolean gradient) {
            this.gradient = gradient;
        }

        @Override
        public void start() {
            sharedDispersion.set(0.0);
        }

        @Override
        public void run() {
            try {
                execute(0, nAtoms - 1, dispersionLoop[getThreadIndex()]);
            } catch (Exception e) {
                String message = "Fatal exception computing Dispersion energy in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        /**
         * Compute Dispersion energy for a range of atoms via pairwise
         * descreening.
         *
         * @since 1.0
         */
        private class DispersionLoop extends IntegerForLoop {

            private double gX[];
            private double gY[];
            private double gZ[];
            private double lgX[];
            private double lgY[];
            private double lgZ[];
            private double edisp;
            private final double dx_local[];
            private double r, r2, r3;
            private double xr, yr, zr;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public DispersionLoop() {
                dx_local = new double[3];
            }

            @Override
            public void start() {
                int threadID = getThreadIndex();
                gX = grad[threadID][0];
                gY = grad[threadID][1];
                gZ = grad[threadID][2];
                if (lambdaTerm) {
                    lgX = lambdaGrad[threadID][0];
                    lgY = lambdaGrad[threadID][1];
                    lgZ = lambdaGrad[threadID][2];
                }
                edisp = 0;
            }

            @Override
            public void finish() {
                sharedDispersion.addAndGet(edisp);
            }

            @Override
            public void run(int lb, int ub) {
                for (int i = lb; i <= ub; i++) {
                    if (!use[i]) {
                        continue;
                    }
                    /**
                     * Begin with the limit of atom alone in solvent.
                     */
                    edisp += cdisp[i];

                    /**
                     * Now descreen over neighbors.
                     */
                    double sum = 0.0;
                    final double xi = x[i];
                    final double yi = y[i];
                    final double zi = z[i];
                    int list[] = neighborLists[0][i];
                    int npair = list.length;
                    for (int l = 0; l < npair; l++) {
                        int k = list[l];
                        final double rk = rDisp[k];
                        if (i != k && rk > 0.0 && use[k]) {
                            dx_local[0] = xi - x[k];
                            dx_local[1] = yi - y[k];
                            dx_local[2] = zi - z[k];
                            r2 = crystal.image(dx_local);
                            if (r2 > cut2) {
                                continue;
                            }
                            xr = dx_local[0];
                            yr = dx_local[1];
                            zr = dx_local[2];
                            r = sqrt(r2);
                            r3 = r * r2;
                            /**
                             * Atom i descreened by atom k.
                             */
                            sum += descreen(i, k);
                            /**
                             * Flip the sign on {xr, yr, zr};
                             */
                            xr = -xr;
                            yr = -yr;
                            zr = -zr;
                            /**
                             * Atom k descreened by atom i.
                             */
                            sum += descreen(k, i);
                        }
                    }
                    /**
                     * Subtract descreening.
                     */
                    edisp -= SLEVY * AWATER * sum;
                }
            }

            private double descreen(int i, int k) {
                double sum = 0.0;
                VDWType type = atoms[i].getVDWType();
                double epsi = type.wellDepth;
                double rmini = type.radius / 2.0;
                double emixo = (4.0 * EPSO * epsi) / (pow(sqrt(EPSO) + sqrt(epsi), 2));
                double rmixo = 2.0 * (pow(RMINO, 3) + pow(rmini, 3)) / (pow(RMINO, 2) + pow(rmini, 2));
                double rmixo7 = pow(rmixo, 7);
                double ao = emixo * rmixo7;
                double emixh = 4.0 * EPSH * epsi / (pow(sqrt(EPSH) + sqrt(epsi), 2));
                double rmixh = 2.0 * (pow(RMINH, 3) + pow(rmini, 3)) / (pow(RMINH, 2) + pow(rmini, 2));
                double rmixh7 = pow(rmixh, 7);
                double ah = emixh * rmixh7;
                double ri = rDisp[i];
                double rk = rDisp[k];
                double sk = rk * DISP_OVERLAP_SCALE_FACTOER;
                double sk2 = sk * sk;
                if (ri < r + sk) {
                    double de = 0.0;
                    double rmax = max(ri, r - sk);
                    double lik = rmax;
                    double lik2 = lik * lik;
                    double lik3 = lik2 * lik;
                    double lik4 = lik3 * lik;
                    if (lik < rmixo) {
                        double uik = min(r + sk, rmixo);
                        double uik2 = uik * uik;
                        double uik3 = uik2 * uik;
                        double uik4 = uik3 * uik;
                        double term = 4.0 * PI / (48.0 * r) * (3.0 * (lik4 - uik4) - 8.0 * r * (lik3 - uik3)
                                + 6.0 * (r2 - sk2) * (lik2 - uik2));
                        double iwca = -emixo * term;
                        sum = sum + iwca;
                        if (gradient) {
                            double dl;
                            if (ri > r - sk) {
                                dl = -lik2 + 2.0 * r2 + 2.0 * sk2;
                                dl = dl * lik2;
                            } else {
                                dl = -lik3 + 4.0 * lik2 * r - 6.0 * lik * r2 + 2.0 * lik * sk2 + 4.0 * r3
                                        - 4.0 * r * sk2;
                                dl = dl * lik;
                            }
                            double du;
                            if (r + sk > rmixo) {
                                du = -uik2 + 2.0 * r2 + 2.0 * sk2;
                                du = -du * uik2;
                            } else {
                                du = -uik3 + 4.0 * uik2 * r - 6.0 * uik * r2 + 2.0 * uik * sk2 + 4.0 * r3
                                        - 4.0 * r * sk2;
                                du = -du * uik;
                            }
                            de = de - emixo * PI * (dl + du) / (4.0 * r2);
                        }
                    }
                    if (lik < rmixh) {
                        double uik = min(r + sk, rmixh);
                        double uik2 = uik * uik;
                        double uik3 = uik2 * uik;
                        double uik4 = uik3 * uik;
                        double term = 4.0 * PI / (48.0 * r) * (3.0 * (lik4 - uik4) - 8.0 * r * (lik3 - uik3)
                                + 6.0 * (r2 - sk2) * (lik2 - uik2));
                        double iwca = -2.0 * emixh * term;
                        sum = sum + iwca;
                        if (gradient) {
                            double dl;
                            if (ri > r - sk) {
                                dl = -lik2 + 2.0 * r2 + 2.0 * sk2;
                                dl = dl * lik2;
                            } else {
                                dl = -lik3 + 4.0 * lik2 * r - 6.0 * lik * r2 + 2.0 * lik * sk2 + 4.0 * r3
                                        - 4.0 * r * sk2;
                                dl = dl * lik;
                            }
                            double du;
                            if (r + sk > rmixh) {
                                du = -uik2 + 2.0 * r2 + 2.0 * sk2;
                                du = -du * uik2;
                            } else {
                                du = -uik3 + 4.0 * uik2 * r - 6.0 * uik * r2 + 2.0 * uik * sk2 + 4.0 * r3
                                        - 4.0 * r * sk2;
                                du = -du * uik;
                            }
                            de = de - 2.0 * emixh * PI * (dl + du) / (4.0 * r2);
                        }
                    }
                    double uik = r + sk;
                    double uik2 = uik * uik;
                    double uik3 = uik2 * uik;
                    double uik4 = uik3 * uik;
                    double uik5 = uik4 * uik;
                    double uik6 = uik5 * uik;
                    double uik10 = uik5 * uik5;
                    double uik11 = uik10 * uik;
                    double uik12 = uik11 * uik;
                    double uik13 = uik12 * uik;
                    if (uik > rmixo) {
                        lik = max(rmax, rmixo);
                        lik2 = lik * lik;
                        lik3 = lik2 * lik;
                        lik4 = lik3 * lik;
                        double lik5 = lik4 * lik;
                        double lik6 = lik5 * lik;
                        double lik10 = lik5 * lik5;
                        double lik11 = lik10 * lik;
                        double lik12 = lik11 * lik;
                        double lik13 = lik12 * lik;
                        double term = 4.0 * PI / (120.0 * r * lik5 * uik5) * (15.0 * uik * lik * r * (uik4 - lik4)
                                - 10.0 * uik2 * lik2 * (uik3 - lik3) + 6.0 * (sk2 - r2) * (uik5 - lik5));
                        double term2 = 4.0 * PI / (2640.0 * r * lik12 * uik12)
                                * (120.0 * uik * lik * r * (uik11 - lik11) - 66.0 * uik2 * lik2 * (uik10 - lik10)
                                        + 55.0 * (sk2 - r2) * (uik12 - lik12));
                        double idisp = -2.0 * ao * term;
                        double irep = ao * rmixo7 * term2;
                        sum = sum + irep + idisp;
                        if (gradient) {
                            double dl;
                            if (ri > r - sk || rmax < rmixo) {
                                dl = -5.0 * lik2 + 3.0 * r2 + 3.0 * sk2;
                                dl = -dl / lik5;
                            } else {
                                dl = 5.0 * lik3 - 33.0 * lik * r2 - 3.0 * lik * sk2
                                        + 15.0 * (lik2 * r + r3 - r * sk2);
                                dl = dl / lik6;
                            }
                            double du;
                            du = 5.0 * uik3 - 33.0 * uik * r2 - 3.0 * uik * sk2 + 15.0 * (uik2 * r + r3 - r * sk2);
                            du = -du / uik6;
                            de = de - 2.0 * ao * PI * (dl + du) / (15.0 * r2);

                            if (ri > r - sk || rmax < rmixo) {
                                dl = -6.0 * lik2 + 5.0 * r2 + 5.0 * sk2;
                                dl = -dl / lik12;
                            } else {
                                dl = 6.0 * lik3 - 125.0 * lik * r2 - 5.0 * lik * sk2
                                        + 60.0 * (lik2 * r + r3 - r * sk2);
                                dl = dl / lik13;
                            }
                            du = 6.0 * uik3 - 125.0 * uik * r2 - 5.0 * uik * sk2 + 60.0 * (uik2 * r + r3 - r * sk2);
                            du = -du / uik13;
                            de = de + ao * rmixo7 * PI * (dl + du) / (60.0 * r2);
                        }

                    }
                    if (uik > rmixh) {
                        lik = max(rmax, rmixh);
                        lik2 = lik * lik;
                        lik3 = lik2 * lik;
                        lik4 = lik3 * lik;
                        double lik5 = lik4 * lik;
                        double lik6 = lik5 * lik;
                        double lik10 = lik5 * lik5;
                        double lik11 = lik10 * lik;
                        double lik12 = lik11 * lik;
                        double lik13 = lik12 * lik;
                        double term = 4.0 * PI / (120.0 * r * lik5 * uik5) * (15.0 * uik * lik * r * (uik4 - lik4)
                                - 10.0 * uik2 * lik2 * (uik3 - lik3) + 6.0 * (sk2 - r2) * (uik5 - lik5));
                        double term2 = 4.0 * PI / (2640.0 * r * lik12 * uik12)
                                * (120.0 * uik * lik * r * (uik11 - lik11) - 66.0 * uik2 * lik2 * (uik10 - lik10)
                                        + 55.0 * (sk2 - r2) * (uik12 - lik12));
                        double idisp = -4.0 * ah * term;
                        double irep = 2.0 * ah * rmixh7 * term2;
                        sum = sum + irep + idisp;
                        if (gradient) {
                            double dl;
                            if (ri > r - sk || rmax < rmixh) {
                                dl = -5.0 * lik2 + 3.0 * r2 + 3.0 * sk2;
                                dl = -dl / lik5;
                            } else {
                                dl = 5.0 * lik3 - 33.0 * lik * r2 - 3.0 * lik * sk2
                                        + 15.0 * (lik2 * r + r3 - r * sk2);
                                dl = dl / lik6;
                            }
                            double du;
                            du = 5.0 * uik3 - 33.0 * uik * r2 - 3.0 * uik * sk2 + 15.0 * (uik2 * r + r3 - r * sk2);
                            du = -du / uik6;
                            de = de - 4.0 * ah * PI * (dl + du) / (15.0 * r2);
                            if (ri > r - sk || rmax < rmixh) {
                                dl = -6.0 * lik2 + 5.0 * r2 + 5.0 * sk2;
                                dl = -dl / lik12;
                            } else {
                                dl = 6.0 * lik3 - 125.0 * lik * r2 - 5.0 * lik * sk2
                                        + 60.0 * (lik2 * r + r3 - r * sk2);
                                dl = dl / lik13;
                            }
                            du = 6.0 * uik3 - 125.0 * uik * r2 - 5.0 * uik * sk2 + 60.0 * (uik2 * r + r3 - r * sk2);
                            du = -du / uik13;
                            de = de + ah * rmixh7 * PI * (dl + du) / (30.0 * r2);
                        }

                    }
                    /**
                     * Increment the individual dispersion gradient components.
                     */
                    if (gradient) {
                        de = -de / r * SLEVY * AWATER;
                        double dedx = de * xr;
                        double dedy = de * yr;
                        double dedz = de * zr;
                        gX[i] += lambda * dedx;
                        gY[i] += lambda * dedy;
                        gZ[i] += lambda * dedz;
                        gX[k] -= lambda * dedx;
                        gY[k] -= lambda * dedy;
                        gZ[k] -= lambda * dedz;
                        if (lambdaTerm) {
                            lgX[i] += dedx;
                            lgY[i] += dedy;
                            lgZ[i] += dedz;
                            lgX[k] -= dedx;
                            lgY[k] -= dedy;
                            lgZ[k] -= dedz;
                        }
                    }
                }
                return sum;
            }
        }
    }

    /**
     * Compute Cavitation energy in parallel.
     *
     * @since 1.0
     */
    private class CavitationRegion extends ParallelRegion {

        private final AtomOverlapLoop atomOverlapLoop[];
        private final CavitationLoop cavitationLoop[];
        private final InitLoop initLoop[];
        private final SharedDouble sharedCavitation;
        private final int maxarc = 150;
        private final static double delta = 1.0e-8;
        private final static double delta2 = delta * delta;

        private double xc1[][];
        private double yc1[][];
        private double zc1[][];
        private double dsq1[][];
        private double bsq1[][];
        private double b1[][];
        private IndexedDouble gr[][];
        private int intag1[][];
        private Integer count[];
        private boolean buried[];
        private SharedBooleanArray skip;
        private double area[];
        private double r[];
        private long initTime = 0;
        private long overlapTime = 0;
        private long cavTime = 0;

        public CavitationRegion(int nt) {
            atomOverlapLoop = new AtomOverlapLoop[nt];
            cavitationLoop = new CavitationLoop[nt];
            initLoop = new InitLoop[nt];
            for (int i = 0; i < nt; i++) {
                atomOverlapLoop[i] = new AtomOverlapLoop();
                cavitationLoop[i] = new CavitationLoop();
                initLoop[i] = new InitLoop();
            }
            sharedCavitation = new SharedDouble();
            init();
        }

        public double getEnergy() {
            return sharedCavitation.get();
        }

        public final void init() {
            if (count == null || count.length < nAtoms) {
                count = new Integer[nAtoms];
                xc1 = new double[nAtoms][maxarc];
                yc1 = new double[nAtoms][maxarc];
                zc1 = new double[nAtoms][maxarc];
                dsq1 = new double[nAtoms][maxarc];
                bsq1 = new double[nAtoms][maxarc];
                b1 = new double[nAtoms][maxarc];
                gr = new IndexedDouble[nAtoms][maxarc];
                intag1 = new int[nAtoms][maxarc];
                buried = new boolean[nAtoms];
                skip = new SharedBooleanArray(nAtoms);
                area = new double[nAtoms];
                r = new double[nAtoms];
            }
            /**
             * Set the sphere radii.
             */
            for (int i = 0; i < nAtoms; i++) {
                VDWType type = atoms[i].getVDWType();
                double rmini = type.radius;
                r[i] = rmini / 2.0;
                if (r[i] != 0.0) {
                    r[i] = r[i] + probe;
                }
            }
            for (int i = 0; i < nAtoms; i++) {
                skip.set(i, true);
            }
        }

        @Override
        public void start() {
            sharedCavitation.set(0.0);
        }

        @Override
        public void finish() {
            if (logger.isLoggable(Level.FINE)) {
                int n = initLoop.length;
                initTime = 0;
                overlapTime = 0;
                cavTime = 0;
                for (int i = 0; i < n; i++) {
                    initTime = max(initLoop[i].time, initTime);
                    overlapTime = max(atomOverlapLoop[i].time, overlapTime);
                    cavTime = max(cavitationLoop[i].time, cavTime);
                }
                logger.fine(String.format(" Cavitation Init: %10.3f Overlap: %10.3f Cav:  %10.3f", initTime * 1e-9,
                        overlapTime * 1e-9, cavTime * 1e-9));
            }
        }

        @Override
        public void run() {
            try {
                execute(0, nAtoms - 1, initLoop[getThreadIndex()]);
                execute(0, nAtoms - 1, atomOverlapLoop[getThreadIndex()]);
                execute(0, nAtoms - 1, cavitationLoop[getThreadIndex()]);
            } catch (Exception e) {
                String message = "Fatal exception computing Cavitation energy in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        private class IndexedDouble implements Comparable {

            public double value;
            public int key;

            public IndexedDouble(double value, int key) {
                this.value = value;
                this.key = key;
            }

            @Override
            public int compareTo(Object o) {
                if (!(o instanceof IndexedDouble)) {
                    return 0;
                }
                IndexedDouble d = (IndexedDouble) o;
                if (value < d.value) {
                    return -1;
                } else if (value == d.value) {
                    return 0;
                } else {
                    return 1;
                }
            }
        }

        /**
         * Initialize arrays for Cavitation calculation.
         */
        private class InitLoop extends IntegerForLoop {

            public long time;

            @Override
            public void start() {
                time = -System.nanoTime();
            }

            @Override
            public void finish() {
                time += System.nanoTime();
            }

            @Override
            public void run(int lb, int ub) throws Exception {
                for (int i = lb; i <= ub; i++) {
                    buried[i] = false;
                    area[i] = 0.0;
                    count[i] = 0;
                    double xr = x[i];
                    double yr = y[i];
                    double zr = z[i];
                    double rri = r[i];
                    final int list[] = neighborLists[0][i];
                    int npair = list.length;
                    for (int l = 0; l < npair; l++) {
                        int k = list[l];
                        double rrik = rri + r[k];
                        double dx = x[k] - xr;
                        double dy = y[k] - yr;
                        double dz = z[k] - zr;
                        double ccsq = dx * dx + dy * dy + dz * dz;
                        if (ccsq <= rrik * rrik) {
                            if (use[i] || use[k]) {
                                skip.set(k, false);
                                skip.set(i, false);
                            }
                        }
                    }
                }
            }
        }

        /**
         * Compute Cavitation energy for a range of atoms.
         *
         * @since 1.0
         */
        private class AtomOverlapLoop extends IntegerForLoop {

            public long time;

            @Override
            public void start() {
                time = -System.nanoTime();
            }

            @Override
            public void finish() {
                time += System.nanoTime();
            }

            @Override
            public void run(int lb, int ub) {
                /**
                 * Find overlaps with the current sphere.
                 */
                for (int i = lb; i <= ub; i++) {
                    if (skip.get(i) || !use[i]) {
                        continue;
                    }
                    int list[] = neighborLists[0][i];
                    int npair = list.length;
                    for (int l = 0; l < npair; l++) {
                        int k = list[l];
                        if (k == i) {
                            continue;
                        }
                        pair(i, k);
                        if (!skip.get(k) || !use[k]) {
                            pair(k, i);
                        }
                    }
                }
            }

            private void pair(int i, int k) {
                double xi = x[i];
                double yi = y[i];
                double zi = z[i];
                double rri = r[i];
                double rri2 = 2.0 * rri;
                double rplus = rri + r[k];
                double dx = x[k] - xi;
                double dy = y[k] - yi;
                double dz = z[k] - zi;
                if (abs(dx) >= rplus || abs(dy) >= rplus || abs(dz) >= rplus) {
                    return;
                }

                /**
                 * Check for overlap of spheres by testing center to center
                 * distance against sum and difference of radii.
                 */
                double xysq = dx * dx + dy * dy;
                if (xysq < delta2) {
                    dx = delta;
                    dy = 0.0;
                    xysq = delta2;
                }
                double r2 = xysq + dz * dz;
                double dr = sqrt(r2);
                if (rplus - dr <= delta) {
                    return;
                }
                double rminus = rri - r[k];
                /**
                 * Calculate overlap parameters between "i" and "ir" sphere.
                 */
                synchronized (count) {
                    /**
                     * Check for a completely buried "ir" sphere.
                     */
                    if (dr - abs(rminus) <= delta) {
                        if (rminus <= 0.0) {
                            // SA for this atom is zero.
                            buried[i] = true;
                            return;
                        }
                        return;
                    }
                    int n = count[i];
                    xc1[i][n] = dx;
                    yc1[i][n] = dy;
                    zc1[i][n] = dz;
                    dsq1[i][n] = xysq;
                    bsq1[i][n] = r2;
                    b1[i][n] = dr;
                    gr[i][n] = new IndexedDouble((r2 + rplus * rminus) / (rri2 * b1[i][n]), n);
                    intag1[i][n] = k;
                    count[i]++;
                    if (count[i] >= maxarc) {
                        //logger.severe(String.format(" Increase the value of MAXARC to (%d).", count[i]));
                        throw new EnergyException(String.format(" Increase the value of MAXARC to (%d).", count[i]),
                                false);
                    }
                }
            }
        }

        /**
         * Compute Cavitation energy for a range of atoms.
         *
         * @since 1.0
         */
        private class CavitationLoop extends IntegerForLoop {

            private double thec = 0;
            private IndexedDouble arci[];
            private final double dArea[][];
            private final double ldArea[][];
            private boolean omit[];
            private double xc[];
            private double yc[];
            private double zc[];
            private double dsq[];
            private double b[];
            private double bsq[];
            private double bg[];
            private double risq[];
            private double ri[];
            private double ther[];
            private double ider[];
            private double sign_yder[];
            private double arcf[];
            private double ex[];
            private double ux[];
            private double uy[];
            private double uz[];
            private int kent[];
            private int kout[];
            private int intag[];
            private int lt[];
            private int i;
            private int j;
            private int ib;
            private double ecav;
            /**
             * Set pi multiples, overlap criterion and tolerances.
             */
            private final static double pix2 = 2.0 * PI;
            private final static double pix4 = 4.0 * PI;
            private final static double pid2 = PI / 2.0;
            private final static double eps = 1.0e-8;

            public long time;
            // Extra padding to avert cache interference.
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            public CavitationLoop() {
                dArea = new double[3][];
                ldArea = new double[3][];
                allocateMemory(maxarc);
            }

            private void allocateMemory(int maxarc) {
                arci = new IndexedDouble[maxarc];
                arcf = new double[maxarc];
                risq = new double[maxarc];
                ri = new double[maxarc];
                dsq = new double[maxarc];
                bsq = new double[maxarc];
                intag = new int[maxarc];
                lt = new int[maxarc];
                kent = new int[maxarc];
                kout = new int[maxarc];
                ider = new double[maxarc];
                sign_yder = new double[maxarc];
                xc = new double[maxarc];
                yc = new double[maxarc];
                zc = new double[maxarc];
                b = new double[maxarc];
                omit = new boolean[maxarc];
                bg = new double[maxarc];
                ther = new double[maxarc];
                ex = new double[maxarc];
                ux = new double[maxarc];
                uy = new double[maxarc];
                uz = new double[maxarc];
            }

            @Override
            public void start() {
                time = -System.nanoTime();
                int threadID = getThreadIndex();
                dArea[0] = grad[threadID][0];
                dArea[1] = grad[threadID][1];
                dArea[2] = grad[threadID][2];
                if (lambdaTerm) {
                    ldArea[0] = lambdaGrad[threadID][0];
                    ldArea[1] = lambdaGrad[threadID][1];
                    ldArea[2] = lambdaGrad[threadID][2];
                }
                ecav = 0;
                fill(ider, 0);
                fill(sign_yder, 0);
            }

            @Override
            public void finish() {
                sharedCavitation.addAndGet(ecav);
                time += System.nanoTime();
            }

            @Override
            public void run(int lb, int ub) {
                /**
                 * Compute the area and derivatives of current "ir" sphere
                 */
                for (int ir = lb; ir <= ub; ir++) {
                    if (skip.get(ir) || !use[ir]) {
                        continue;
                    }
                    double xi = x[ir];
                    double yi = y[ir];
                    double zi = z[ir];
                    double rri = r[ir];
                    double rri2 = 2.0 * rri;
                    double rrisq = rri * rri;
                    double wght = surfaceTension;
                    boolean moved = false;
                    surface(xi, yi, zi, rri, rri2, rrisq, wght, moved, ir);
                    if (area[ir] < 0.0) {
                        logger.log(GK_WARN_LEVEL,
                                String.format(" Negative surface area set to 0 for atom %d.", ir));
                        //logger.warning(String.format(" Negative surface area set to 0 for atom %d.", ir));
                        area[ir] = 0.0;
                        /**
                         * xi = xi + rmove; yi = yi + rmove; zi = zi + rmove;
                         * moved = true; surface(xi, yi, zi, rri, rri2, rrisq,
                         * wght, moved, ir); if (area[ir] < 0.0) {
                         * logger.warning(String.format(" Negative surface area
                         * set to 0 for atom %d.", ir)); area[ir] = 0.0;
                         * continue; }
                         */
                    }
                    area[ir] *= rrisq * wght;
                    ecav += area[ir];
                }
            }

            public void surface(double xi, double yi, double zi, double rri, double rri2, double rrisq, double wght,
                    boolean moved, int ir) {

                ib = 0;
                int jb = 0;
                double arclen = 0.0;
                double exang = 0.0;

                // Case where no other spheres overlap the current sphere.
                if (count[ir] == 0) {
                    area[ir] = pix4;
                    return;
                }
                // Case where only one sphere overlaps the current sphere.
                if (count[ir] == 1) {
                    int k = 0;
                    double txk = xc1[ir][0];
                    double tyk = yc1[ir][0];
                    double tzk = zc1[ir][0];
                    double bsqk = bsq1[ir][0];
                    double bk = b1[ir][0];
                    intag[0] = intag1[ir][0];
                    double arcsum = pix2;
                    ib = ib + 1;
                    arclen += gr[ir][k].value * arcsum;
                    if (!moved) {
                        int in = intag[k];
                        double t1 = arcsum * rrisq * (bsqk - rrisq + r[in] * r[in]) / (rri2 * bsqk * bk);
                        dArea[0][ir] -= lambda * txk * t1 * wght;
                        dArea[1][ir] -= lambda * tyk * t1 * wght;
                        dArea[2][ir] -= lambda * tzk * t1 * wght;
                        dArea[0][in] += lambda * txk * t1 * wght;
                        dArea[1][in] += lambda * tyk * t1 * wght;
                        dArea[2][in] += lambda * tzk * t1 * wght;
                        if (lambdaTerm) {
                            ldArea[0][ir] -= txk * t1 * wght;
                            ldArea[1][ir] -= tyk * t1 * wght;
                            ldArea[2][ir] -= tzk * t1 * wght;
                            ldArea[0][in] += txk * t1 * wght;
                            ldArea[1][in] += tyk * t1 * wght;
                            ldArea[2][in] += tzk * t1 * wght;
                        }
                    }
                    area[ir] = ib * pix2 + exang + arclen;
                    area[ir] = area[ir] % pix4;
                    return;
                }
                /**
                 * general case where more than one sphere intersects the
                 * current sphere; sort intersecting spheres by their degree of
                 * overlap with the current main sphere
                 */
                Arrays.sort(gr[ir], 0, count[ir]);
                for (int j = 0; j < count[ir]; j++) {
                    int k = gr[ir][j].key;
                    intag[j] = intag1[ir][k];
                    xc[j] = xc1[ir][k];
                    yc[j] = yc1[ir][k];
                    zc[j] = zc1[ir][k];
                    dsq[j] = dsq1[ir][k];
                    b[j] = b1[ir][k];
                    bsq[j] = bsq1[ir][k];
                    omit[j] = false;
                }
                /**
                 * Radius of the each circle on the surface of the "ir" sphere.
                 */
                for (int i = 0; i < count[ir]; i++) {
                    double gi = gr[ir][i].value * rri;
                    bg[i] = b[i] * gi;
                    risq[i] = rrisq - gi * gi;
                    ri[i] = sqrt(risq[i]);
                    ther[i] = pid2 - asin(min(1.0, max(-1.0, gr[ir][i].value)));
                }
                /**
                 * Find boundary of inaccessible area on "ir" sphere.
                 */
                for (int k = 0; k < count[ir] - 1; k++) {
                    if (omit[k]) {
                        continue;
                    }
                    double txk = xc[k];
                    double tyk = yc[k];
                    double tzk = zc[k];
                    double bk = b[k];
                    double therk = ther[k];
                    for (j = k + 1; j < count[ir]; j++) {
                        if (omit[j]) {
                            continue;
                        }
                        /*
                         * Check to see if J circle is intersecting K circle;
                         * get distance between circle centers and sum of radii.
                         */
                        double cc = (txk * xc[j] + tyk * yc[j] + tzk * zc[j]) / (bk * b[j]);
                        // Check acos FORTRAN vs. Java.
                        cc = acos(min(1.0, max(-1.0, cc)));
                        double td = therk + ther[j];
                        // Check to see if circles enclose separate regions
                        if (cc >= td) {
                            continue;
                        }
                        // Check for circle J completely inside circle K
                        if (cc + ther[j] < therk) {
                            omit[j] = true;
                            continue;
                        }
                        // Check for circles essentially parallel.
                        if (cc > delta) {
                            if (pix2 - cc <= td) {
                                area[ir] = 0.0;
                                return;
                            }
                        }
                    }
                }

                /**
                 * Find T value of circle intersections.
                 */
                for (int k = 0; k < count[ir]; k++) {
                    if (omit[k]) {
                        continue; // goto 110
                    }
                    boolean komit = omit[k];
                    omit[k] = true;
                    int narc = 0;
                    boolean top = false;
                    double txk = xc[k];
                    double tyk = yc[k];
                    double tzk = zc[k];
                    double dk = sqrt(dsq[k]);
                    double bsqk = bsq[k];
                    double bk = b[k];
                    double gk = gr[ir][k].value * rri;
                    double risqk = risq[k];
                    double rik = ri[k];
                    double therk = ther[k];
                    /**
                     * Rotation matrix elements.
                     */
                    double t1 = tzk / (bk * dk);
                    double axx = txk * t1;
                    double axy = tyk * t1;
                    double axz = dk / bk;
                    double ayx = tyk / dk;
                    double ayy = txk / dk;
                    double azx = txk / bk;
                    double azy = tyk / bk;
                    double azz = tzk / bk;
                    for (int l = 0; l < count[ir]; l++) {
                        if (omit[l]) {
                            continue;
                        }
                        double txl = xc[l];
                        double tyl = yc[l];
                        double tzl = zc[l];
                        /**
                         * Rotate spheres so K vector collinear with z-axis.
                         */
                        double uxl = txl * axx + tyl * axy - tzl * axz;
                        double uyl = tyl * ayy - txl * ayx;
                        double uzl = txl * azx + tyl * azy + tzl * azz;
                        double cosine = min(1.0, max(-1.0, uzl / b[l]));
                        if (acos(cosine) < therk + ther[l]) {
                            double dsql = uxl * uxl + uyl * uyl;
                            double tb = uzl * gk - bg[l];
                            double txb = uxl * tb;
                            double tyb = uyl * tb;
                            double td = rik * dsql;
                            double tr2 = risqk * dsql - tb * tb;
                            tr2 = max(eps, tr2);
                            double tr = sqrt(tr2);
                            double txr = uxl * tr;
                            double tyr = uyl * tr;
                            /**
                             * Get T values of intersection for K circle.
                             */
                            tb = (txb + tyr) / td;
                            tb = min(1.0, max(-1.0, tb));
                            double tk1 = acos(tb);
                            if (tyb - txr < 0.0) {
                                tk1 = pix2 - tk1;
                            }
                            tb = (txb - tyr) / td;
                            tb = min(1.0, max(-1.0, tb));
                            double tk2 = acos(tb);
                            if (tyb + txr < 0.0) {
                                tk2 = pix2 - tk2;
                            }
                            thec = (rrisq * uzl - gk * bg[l]) / (rik * ri[l] * b[l]);
                            double the = 0.0;
                            if (abs(thec) < 1.0) {
                                the = -acos(thec);
                            } else if (thec >= 1.0) {
                                the = 0.0;
                            } else if (thec <= -1.0) {
                                the = -PI;
                            }
                            /**
                             * See if "tk1" is entry or exit point; check t=0
                             * point; "ti" is exit point, "tf" is entry point.
                             */
                            cosine = min(1.0, max(-1.0, (uzl * gk - uxl * rik) / (b[l] * rri)));
                            double ti, tf;
                            if ((acos(cosine) - ther[l]) * (tk2 - tk1) <= 0.0) {
                                ti = tk2;
                                tf = tk1;
                            } else {
                                ti = tk1;
                                tf = tk2;
                            }
                            narc += 1;
                            if (narc > maxarc) {
                                /*logger.severe(String.
                                    format(" Increase value of MAXARC %d.", narc));*/
                                throw new EnergyException(String.format(" Increase value of MAXARC %d.", narc),
                                        false);
                            }
                            int narc1 = narc - 1;
                            if (tf <= ti) {
                                arcf[narc1] = tf;
                                arci[narc1] = new IndexedDouble(0.0, narc1);
                                tf = pix2;
                                lt[narc1] = l;
                                ex[narc1] = the;
                                top = true;
                                narc = narc + 1;
                                narc1 = narc - 1;
                            }
                            arcf[narc1] = tf;
                            arci[narc1] = new IndexedDouble(ti, narc1);
                            lt[narc1] = l;
                            ex[narc1] = the;
                            ux[l] = uxl;
                            uy[l] = uyl;
                            uz[l] = uzl;
                        }
                    }
                    omit[k] = komit;
                    /**
                     * Special case; K circle without intersections.
                     */
                    if (narc <= 0) {
                        double arcsum = pix2;
                        ib += 1;
                        arclen += gr[ir][k].value * arcsum;
                        if (!moved) {
                            int in = intag[k];
                            t1 = arcsum * rrisq * (bsqk - rrisq + r[in] * r[in]) / (rri2 * bsqk * bk);
                            dArea[0][ir] -= lambda * txk * t1 * wght;
                            dArea[1][ir] -= lambda * tyk * t1 * wght;
                            dArea[2][ir] -= lambda * tzk * t1 * wght;
                            dArea[0][in] += lambda * txk * t1 * wght;
                            dArea[1][in] += lambda * tyk * t1 * wght;
                            dArea[2][in] += lambda * tzk * t1 * wght;
                            if (lambdaTerm) {
                                ldArea[0][ir] -= txk * t1 * wght;
                                ldArea[1][ir] -= tyk * t1 * wght;
                                ldArea[2][ir] -= tzk * t1 * wght;
                                ldArea[0][in] += txk * t1 * wght;
                                ldArea[1][in] += tyk * t1 * wght;
                                ldArea[2][in] += tzk * t1 * wght;
                            }
                        }
                        continue;
                    }
                    /**
                     * General case; sum up arclength and set connectivity code.
                     */
                    Arrays.sort(arci, 0, narc);
                    double arcsum = arci[0].value;
                    int mi = arci[0].key;
                    double t = arcf[mi];
                    int ni = mi;
                    for (j = 1; j < narc; j++) {
                        int m = arci[j].key;
                        if (t < arci[j].value) {
                            arcsum += (arci[j].value - t);
                            exang += ex[ni];
                            jb += 1;
                            if (jb >= maxarc) {
                                /*logger.severe(String.
                                    format("Increase the value of MAXARC (%d).", jb));*/
                                throw new EnergyException(String.format("Increase the value of MAXARC (%d).", jb),
                                        false);
                            }
                            int l = lt[ni];
                            ider[l] += 1;
                            sign_yder[l] += 1;
                            kent[jb] = maxarc * (l + 1) + (k + 1);
                            l = lt[m];
                            ider[l] += 1;
                            sign_yder[l] -= 1;
                            kout[jb] = maxarc * (k + 1) + (l + 1);
                        }
                        double tt = arcf[m];
                        if (tt >= t) {
                            t = tt;
                            ni = m;
                        }
                    }
                    arcsum += (pix2 - t);
                    if (!top) {
                        exang += ex[ni];
                        jb = jb + 1;
                        int l = lt[ni];
                        ider[l] += 1;
                        sign_yder[l] += 1;
                        kent[jb] = maxarc * (l + 1) + (k + 1);
                        l = lt[mi];
                        ider[l] += 1;
                        sign_yder[l] -= 1;
                        kout[jb] = maxarc * (k + 1) + (l + 1);
                    }
                    /**
                     * Calculate the surface area derivatives.
                     */
                    for (int l = 0; l <= count[ir]; l++) {
                        if (ider[l] == 0) {
                            continue;
                        }
                        double rcn = ider[l] * rrisq;
                        ider[l] = 0;
                        double uzl = uz[l];
                        double gl = gr[ir][l].value * rri;
                        double bgl = bg[l];
                        double bsql = bsq[l];
                        double risql = risq[l];
                        double wxlsq = bsql - uzl * uzl;
                        double wxl = sqrt(wxlsq);
                        double p = bgl - gk * uzl;
                        double v = risqk * wxlsq - p * p;
                        v = max(eps, v);
                        v = sqrt(v);
                        t1 = rri * (gk * (bgl - bsql) + uzl * (bgl - rrisq)) / (v * risql * bsql);
                        double deal = -wxl * t1;
                        double decl = -uzl * t1 - rri / v;
                        double dtkal = (wxlsq - p) / (wxl * v);
                        double dtkcl = (uzl - gk) / v;
                        double s = gk * b[l] - gl * uzl;
                        t1 = 2.0 * gk - uzl;
                        double t2 = rrisq - bgl;
                        double dtlal = -(risql * wxlsq * b[l] * t1 - s * (wxlsq * t2 + risql * bsql))
                                / (risql * wxl * bsql * v);
                        double dtlcl = -(risql * b[l] * (uzl * t1 - bgl) - uzl * t2 * s) / (risql * bsql * v);
                        double gaca = rcn * (deal - (gk * dtkal - gl * dtlal) / rri) / wxl;
                        double gacb = (gk - uzl * gl / b[l]) * sign_yder[l] * rri / wxlsq;
                        sign_yder[l] = 0;
                        if (!moved) {
                            double faca = ux[l] * gaca - uy[l] * gacb;
                            double facb = uy[l] * gaca + ux[l] * gacb;
                            double facc = rcn * (decl - (gk * dtkcl - gl * dtlcl) / rri);
                            double dax = axx * faca - ayx * facb + azx * facc;
                            double day = axy * faca + ayy * facb + azy * facc;
                            double daz = azz * facc - axz * faca;
                            int in = intag[l];
                            dArea[0][ir] += lambda * dax * wght;
                            dArea[1][ir] += lambda * day * wght;
                            dArea[2][ir] += lambda * daz * wght;
                            dArea[0][in] -= lambda * dax * wght;
                            dArea[1][in] -= lambda * day * wght;
                            dArea[2][in] -= lambda * daz * wght;
                            if (lambdaTerm) {
                                ldArea[0][ir] += dax * wght;
                                ldArea[1][ir] += day * wght;
                                ldArea[2][ir] += daz * wght;
                                ldArea[0][in] -= dax * wght;
                                ldArea[1][in] -= day * wght;
                                ldArea[2][in] -= daz * wght;
                            }
                        }

                    }
                    arclen += gr[ir][k].value * arcsum;
                    if (!moved) {
                        int in = intag[k];
                        t1 = arcsum * rrisq * (bsqk - rrisq + r[in] * r[in]) / (rri2 * bsqk * bk);
                        dArea[0][ir] -= lambda * txk * t1 * wght;
                        dArea[1][ir] -= lambda * tyk * t1 * wght;
                        dArea[2][ir] -= lambda * tzk * t1 * wght;
                        dArea[0][in] += lambda * txk * t1 * wght;
                        dArea[1][in] += lambda * tyk * t1 * wght;
                        dArea[2][in] += lambda * tzk * t1 * wght;
                        if (lambdaTerm) {
                            ldArea[0][ir] -= txk * t1 * wght;
                            ldArea[1][ir] -= tyk * t1 * wght;
                            ldArea[2][ir] -= tzk * t1 * wght;
                            ldArea[0][in] += txk * t1 * wght;
                            ldArea[1][in] += tyk * t1 * wght;
                            ldArea[2][in] += tzk * t1 * wght;
                        }
                    }
                }
                if (arclen == 0.0) {
                    area[ir] = 0.0;
                    return;
                }
                if (jb == 0) {
                    area[ir] = ib * pix2 + exang + arclen;
                    area[ir] = area[ir] % pix4;
                    return;
                }
                /**
                 * Find number of independent boundaries and check connectivity.
                 */
                j = 0;
                for (int k = 1; k <= jb; k++) {
                    if (kout[k] == -1) {
                        continue;
                    }
                    i = k;
                    boolean success = independentBoundaries(k, exang, jb, ir, arclen);
                    if (success) {
                        return;
                    }
                }
                ib = ib + 1;
                /*
                 if (moved) {
                 logger.warning(String.format(" Connectivity error at atom %d.", ir));
                 } else {
                 */
                logger.log(GK_WARN_LEVEL, String.format(" Connectivity error at atom %d", ir));
                //logger.warning(String.format(" Connectivity error at atom %d.", ir));
                area[ir] = 0.0;
                /*
                 moved = true;
                 xi += rmove;
                 yi += rmove;
                 zi += rmove;
                 surface(xi, yi, zi, rri, rri2, rrisq, wght, moved, ir);
                 } */
            }

            /**
             * Find number of independent boundaries and check connectivity.
             * This method may set the "goto160" flag.
             *
             * @param k
             * @param exang
             * @param jb
             * @param ir
             * @param arclen
             */
            public boolean independentBoundaries(int k, double exang, int jb, int ir, double arclen) {
                int m = kout[i];
                kout[i] = -1;
                j = j + 1;
                for (int ii = 1; ii <= jb; ii++) {
                    if (m == kent[ii]) {
                        if (ii == k) {
                            ib++;
                            if (j == jb) {
                                area[ir] = ib * 2.0 * PI + exang + arclen;
                                area[ir] = area[ir] % (4.0 * PI);
                                return true;
                            }
                            return false;
                        }
                        i = ii;
                        return independentBoundaries(k, exang, jb, ir, arclen);
                    }
                }
                return false;
            }
        }
    }

    /**
     * Compute Volume energy in parallel.
     *
     * @since 1.0
     */
    private class VolumeRegion extends ParallelRegion {

        private final VolumeLoop volumeLoop[];
        private final SharedDouble sharedVolume;
        private final int itab[];
        private final static int MAXCUBE = 40;
        private final static int MAXARC = 1000;
        private final static int MAXMNB = 500;
        /**
         * maximum number of cycle convex edges.
         */
        private final static int MAXCYEP = 30;
        /**
         * maximum number of convex face cycles
         */
        private final static int MAXFPCY = 18;
        /**
         * Radius of a water molecular.
         */
        private final static double exclude = 1.4;
        /**
         * maximum number of saddle faces.
         */
        private final int maxfs = 3 * nAtoms;
        /**
         * maximum number of convex edges.
         */
        private final int maxep = 5 * nAtoms;
        /**
         * maximum number of neighboring atom pairs.
         */
        private final int maxcls = 50 * nAtoms;
        /**
         * maximum number of circles.
         */
        private final int maxc = 5 * nAtoms;
        /**
         * maximum number of total tori.
         */
        private final int maxt = 3 * nAtoms;
        /**
         * maximum number of temporary tori.
         */
        private final int maxtt = 25 * nAtoms;
        /**
         * maximum number of concave edges.
         */
        private final int maxen = 5 * nAtoms;
        /**
         * maximum number of vertices.
         */
        private final int maxv = 5 * nAtoms;
        /**
         * maximum number of probe positions.
         */
        private final int maxp = 3 * nAtoms;
        /**
         * maximum number of concave faces.
         */
        private final int maxfn = 2 * nAtoms;
        /**
         * maximum number of convex faces.
         */
        private final int maxfp = nAtoms;
        /**
         * maximum number of cycles.
         */
        private final int maxcy = nAtoms;
        /**
         * Atomic radii.
         */
        private final double radius[] = new double[nAtoms];
        /**
         * If true, atom is not used.
         */
        private final boolean skip[] = new boolean[nAtoms];
        /**
         * Copy of the atomic coordinates. [X,Y,Z][Atom Number]
         */
        private final double a[][] = new double[3][nAtoms];
        /**
         * If true, atom has no free surface.
         */
        private final boolean nosurf[] = new boolean[nAtoms];
        /**
         * Atom free of neighbors.
         */
        private final boolean afree[] = new boolean[nAtoms];
        /**
         * Atom buried.
         */
        private final boolean abur[] = new boolean[nAtoms];
        /**
         * Begin and end pointers for atoms neighbors.
         */
        private final int acls[][] = new int[2][nAtoms];
        /**
         * Atom numbers of neighbors.
         */
        private final int cls[] = new int[maxcls];
        /**
         * Pointer from neighbor to torus.
         */
        private final int clst[] = new int[maxcls];
        /**
         * Number of temporary tori.
         */
        private int ntt;
        /**
         * Temporary torus atom numbers.
         */
        private final int tta[][] = new int[2][maxtt];
        /**
         * First edge of each temporary torus.
         */
        private final int ttfe[] = new int[maxtt];
        /**
         * Last edge of each temporary torus.
         */
        private final int ttle[] = new int[maxtt];
        /**
         * Pointer to next edge of temporary torus.
         */
        private final int enext[] = new int[maxen];
        /**
         * Temporary torus buried.
         */
        private final boolean ttbur[] = new boolean[maxtt];
        /**
         * Temporary torus free.
         */
        private final boolean ttfree[] = new boolean[maxtt];
        /**
         * Torus center.
         */
        private final double t[][] = new double[3][maxt];
        /**
         * Torus radius.
         */
        private final double tr[] = new double[maxt];
        /**
         * Torus axis.
         */
        private final double tax[][] = new double[3][maxt];
        /**
         * Number of tori.
         */
        private int nt;
        /**
         * Torus atom number.
         */
        private final int ta[][] = new int[2][maxt];
        /**
         * Torus first edge.
         */
        int tfe[] = new int[maxt];
        /**
         * Torus free edge of neighbor.
         */
        boolean tfree[] = new boolean[maxt];
        /**
         * Probe position coordinates.
         */
        private final double p[][] = new double[3][maxp];
        /**
         * Number of probe positions.
         */
        private int np;
        /**
         * Probe position atom numbers.
         */
        private final int pa[][] = new int[3][maxp];
        /**
         * Vertex coordinates.
         */
        private final double v[][] = new double[3][maxv];
        /**
         * Number of vertices.
         */
        private int nv;
        /**
         * Vertex atom number.
         */
        private final int va[] = new int[maxv];
        /**
         * Vertex probe number.
         */
        private final int vp[] = new int[maxv];
        /**
         * Number of concave edges.
         */
        private int nen;
        /**
         * Vertex numbers for each concave edge.
         */
        private final int env[][] = new int[2][maxen];
        /**
         * Concave face concave edge numbers.
         */
        private final int fnen[][] = new int[3][maxfn];
        /**
         * Circle center.
         */
        private final double c[][] = new double[3][maxc];
        /**
         * Circle radius.
         */
        private final double cr[] = new double[maxc];
        /**
         * Number of circles.
         */
        private int nc;
        /**
         * Circle atom number.
         */
        private final int ca[] = new int[maxc];
        /**
         * Circle torus number.
         */
        private final int ct[] = new int[maxc];
        /**
         * Number of convex edges.
         */
        private int nep;
        /**
         * Convex edge circle number.
         */
        private final int epc[] = new int[maxep];
        /**
         * Convex edge vertex number.
         */
        private final int epv[][] = new int[2][maxep];
        /**
         * First convex edge of each atom.
         */
        private final int afe[] = new int[nAtoms];
        /**
         * Last convex edge of each atom.
         */
        private final int ale[] = new int[nAtoms];
        /**
         * Pointer to next convex edge of atom.
         */
        private final int epnext[] = new int[maxep];
        /**
         * Number of saddle faces.
         */
        private int nfs;
        /**
         * Saddle face concave edge numbers.
         */
        private final int fsen[][] = new int[2][maxfs];
        /**
         * Saddle face convex edge numbers.
         */
        private final int fsep[][] = new int[2][maxfs];
        /**
         * Number of cycles.
         */
        private int ncy;
        /**
         * Number of convex edges in cycle.
         */
        private final int cynep[] = new int[maxcy];
        /**
         * Cycle convex edge numbers.
         */
        private final int cyep[][] = new int[MAXCYEP][maxcy];
        /**
         * Number of convex faces.
         */
        private int nfp;
        /**
         * Atom number of convex face.
         */
        private final int fpa[] = new int[maxfp];
        /**
         * Convex face cycle numbers
         */
        private final int fpcy[][] = new int[MAXFPCY][maxfp];
        /**
         * Number of cycles bounding convex face.
         */
        private final int fpncy[] = new int[maxfp];

        // These are from the "nearby" method.
        /**
         * True if cube contains active atoms.
         */
        private final boolean activeCube[][][] = new boolean[MAXCUBE][MAXCUBE][MAXCUBE];
        /**
         * True if cube or adjacent cubes have active atoms.
         */
        private final boolean activeAdjacentCube[][][] = new boolean[MAXCUBE][MAXCUBE][MAXCUBE];
        /**
         * Pointer to first atom in list for cube.
         */
        private final int firstAtomPointer[][][] = new int[MAXCUBE][MAXCUBE][MAXCUBE];
        /**
         * Integer cube coordinates.
         */
        private final int cubeCoordinates[][] = new int[3][nAtoms];
        /**
         * Pointer to next atom in cube.
         */
        private final int nextAtomPointer[] = new int[nAtoms];

        /**
         * VolumeRegion constructor.
         *
         * @param nt Number of threads.
         */
        public VolumeRegion(int nt) {
            volumeLoop = new VolumeLoop[nt];
            for (int i = 0; i < nt; i++) {
                volumeLoop[i] = new VolumeLoop();
            }
            sharedVolume = new SharedDouble();
            itab = new int[nAtoms];

            /**
             * Set atom coordinates and radii, the excluded buffer radius
             * ("exclude") is added to atomic radii.
             */
            for (int i = 0; i < nAtoms; i++) {
                if (radius[i] == 0.0) {
                    skip[i] = true;
                } else {
                    radius[i] += exclude;
                    skip[i] = false;
                }
            }

        }

        public double getEnergy() {
            return sharedVolume.get();
        }

        @Override
        public void start() {
            sharedVolume.set(0.0);
            for (int i = 0; i < nAtoms; i++) {
                Atom atom = atoms[i];
                a[0][i] = atom.getX();
                a[1][i] = atom.getY();
                a[2][i] = atom.getZ();
            }
            wiggle();
        }

        @Override
        public void run() {
            try {
                execute(0, nAtoms - 1, volumeLoop[getThreadIndex()]);
            } catch (Exception e) {
                String message = "Fatal exception computing Volume energy in thread " + getThreadIndex() + "\n";
                logger.log(Level.SEVERE, message, e);
            }
        }

        private final static double SIZE = 0.000001;
        private final double vector[] = new double[3];

        public void wiggle() {
            /**
             * Apply a small perturbation of fixed magnitude to each atom.
             */
            for (int i = 0; i < nAtoms; i++) {
                getRandomVector(vector);
                a[0][i] = x[i] + (SIZE * vector[0]);
                a[1][i] = y[i] + (SIZE * vector[1]);
                a[2][i] = z[i] + (SIZE * vector[2]);
            }
        }

        public void getRandomVector(double vector[]) {
            double x, y, s;
            x = 0;
            y = 0;

            /**
             * Get a pair of appropriate components in the plane.
             */
            s = 2.0;
            while (s >= 1.0) {
                x = (2.0 * Math.random()) - 1.0;
                y = (2.0 * Math.random()) - 1.0;
                s = (x * x) + (y * y);
            }
            /**
             * Construct the 3-dimensional random unit vector.
             */
            vector[2] = 1.0 - 2.0 * s;
            s = 2.0 * sqrt(1.0 - s);
            vector[1] = s * y;
            vector[0] = s * x;
        }

        /**
         * Compute Volume energy for a range of atoms.
         *
         * @since 1.0
         */
        private class VolumeLoop extends IntegerForLoop {

            private int nfn;
            private int l, l1, l2;
            private int io, ir, in, iv;
            private int narc, nx, ny, nz;
            private int istart, istop;
            private int jstart, jstop;
            private int kstart, kstop;
            private int mstart, mstop;
            private int isum, itemp, tcube;
            private final int mxcube = 15;
            private final int inov[] = new int[MAXARC];
            private final int cube[][][][] = new int[2][mxcube][mxcube][mxcube];
            private double evol;
            private double xmin, ymin, zmin;
            private double xmax, ymax, zmax;
            private double aa, bb, temp, phi_term;
            private double theta1, theta2, dtheta;
            private double seg_dx, seg_dy, seg_dz;
            private double pre_dx, pre_dy, pre_dz;
            private double rinsq, rdiff;
            private double rsecn, rsec2n;
            private double cosine, ti, tf;
            private double alpha, beta;
            private double ztop, zstart;
            private double ztopshave;
            private double phi1, cos_phi1;
            private double phi2, cos_phi2;
            private double zgrid, pix2;
            private double rsec2r, rsecr;
            private double rr, rrx2, rrsq;
            private double rmax, edge;
            private double xr, yr, zr;
            private double dist2, vdwsum;
            private double zstep;
            private final double arci[] = new double[MAXARC];
            private final double arcf[] = new double[MAXARC];
            private final double dx[] = new double[MAXARC];
            private final double dy[] = new double[MAXARC];
            private final double dsq[] = new double[MAXARC];
            private final double d[] = new double[MAXARC];
            private boolean ttok, cinsp, cintp;
            private final double vdwrad[] = new double[nAtoms];
            private final double dex[][] = new double[3][nAtoms];

            /**
             * Extra padding to avert cache interface.
             */
            private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
            private long pad8, pad9, pada, padb, padc, padd, pade, padf;

            @Override
            public void start() {
                fill(dex[0], 0.0);
                fill(dex[1], 0.0);
                fill(dex[2], 0.0);
                evol = 0.0;
            }

            @Override
            public void finish() {
                sharedVolume.addAndGet(evol);
            }

            public void setRadius() {
                /**
                 * Initialize minimum and maximum range of atoms.
                 */
                pix2 = 2.0 * PI;
                rmax = 0.0;
                xmin = x[0];
                xmax = x[0];
                ymin = y[0];
                ymax = y[0];
                zmin = z[0];
                zmax = z[0];

                /**
                 * Assign van der Waals radii to the atoms; note that the radii
                 * are incremented by the size of the probe; then get the
                 * maximum and minimum ranges of atoms.
                 */
                for (int i = 0; i < nAtoms; i++) {
                    radius[i] = atoms[i].getVDWType().radius / 2.0;
                    vdwrad[i] = radius[i];
                    if (vdwrad[i] == 0.0) {
                        skip[i] = true;
                    } else {
                        skip[i] = false;
                        vdwrad[i] += probe;
                        if (vdwrad[i] > rmax) {
                            rmax = vdwrad[i];
                        }
                        if (x[i] < xmin) {
                            xmin = x[i];
                        }
                        if (x[i] > xmax) {
                            xmax = x[i];
                        }
                        if (y[i] < ymin) {
                            ymin = y[i];
                        }
                        if (y[i] > ymax) {
                            ymax = y[i];
                        }
                        if (z[i] < zmin) {
                            zmin = z[i];
                        }
                        if (z[i] > zmax) {
                            zmax = z[i];
                        }
                    }
                }
            }

            /**
             * Find the analytical volume and surface area.
             */
            public void calcVolume() {
                double volume = 0;
                double area = 0;
                nearby();
                torus();
                place();
                compress();
                saddles();
                contact();
                vam(volume, area);
            }

            public void getVector(double ai[], double temp[][], int index) {
                ai[0] = temp[0][index];
                ai[1] = temp[1][index];
                ai[2] = temp[2][index];
            }

            public void getVector(double ai[], double temp[][][], int index1, int index2) {
                ai[0] = temp[0][index1][index2];
                ai[1] = temp[1][index1][index2];
                ai[2] = temp[2][index1][index2];
            }

            /**
             * The gettor method tests for a possible torus position at the
             * interface between two atoms, and finds the torus radius, center
             * and axis.
             */
            public boolean gettor(int ia, int ja, double torcen[], double torad[], double torax[]) {

                double dij, temp;
                double temp1, temp2;
                double vij[] = new double[3];
                double uij[] = new double[3];
                double bij[] = new double[3];
                double ai[] = new double[3];
                double aj[] = new double[3];

                /**
                 * Get the distance between the two atoms.
                 */
                ttok = false;
                getVector(ai, a, ia);
                getVector(aj, a, ja);
                dij = VectorMath.dist(ai, aj);

                /**
                 * Find a unit vector along interatomic (torus) axis.
                 */
                for (int k = 0; k < 3; k++) {
                    vij[k] = a[k][ja] - a[k][ia];
                    uij[k] = vij[k] / dij;
                }

                /**
                 * Find coordinates of the center of the torus.
                 */
                temp = 1.0 + ((radius[ia] + probe) * (radius[ia] + probe)
                        - (radius[ja] + probe) * (radius[ja] + probe)) / (dij * dij);
                for (int k = 0; k < 3; k++) {
                    bij[k] = a[k][ia] + 0.5 * vij[k] * temp;
                }

                /**
                 * Skip if atoms too far apart (should not happen).
                 */
                temp1 = (radius[ia] + radius[ja] + 2.0 * probe) * (radius[ia] + radius[ja] + 2.0 * probe)
                        - dij * dij;
                if (temp1 >= 0.0) {

                    /**
                     * Skip if one atom is inside the other.
                     */
                    temp2 = dij * dij - (radius[ia] - radius[ja]) * (radius[ia] - radius[ja]);
                    if (temp2 >= 0.0) {

                        /**
                         * Store the torus radius, center, and axis.
                         */
                        ttok = true;
                        torad[0] = sqrt(temp1 * temp2) / (2.0 * dij);
                        for (int k = 0; k < 3; k++) {
                            torcen[k] = bij[k];
                            torax[k] = uij[k];
                        }
                    }
                }
                return ttok;
            }

            /**
             * The nearby method finds all of the through-space neighbors of
             * each atom for use in surface area and volume calculations.
             */
            public void nearby() {
                int maxclsa = 1000;
                int iptr, juse;
                int i1, j1, k1;
                int iatom, jatom;
                int ici, icj, ick;
                int jci, jcj, jck;
                int jcls, jmin;
                int jmincls = 0;
                int jmold;
                int ncls, nclsa;
                int clsa[] = new int[maxclsa];
                /**
                 * Temporary neighbor list, before sorting.
                 */
                int tempNeighborList[] = new int[maxclsa];
                double radmax, width;
                double sum, sumi;
                double d2, r2;
                double vect1, vect2, vect3;
                /**
                 * Minimum atomic coordinates (cube corner).
                 */
                double minAtomicCoordinates[] = new double[3];
                double ai[] = new double[3];
                double aj[] = new double[3];

                /*
                 * Ignore all atoms that are completely inside another atom;
                 * may give nonsense results if this step is not taken.
                 */
                for (int i = 0; i < nAtoms - 1; i++) {
                    if (!skip[i]) {
                        getVector(ai, a, i);
                        for (int j = i + 1; j < nAtoms; j++) {
                            getVector(aj, a, j);
                            d2 = VectorMath.dist2(ai, aj);
                            r2 = (radius[i] - radius[j]) * (radius[i] - radius[j]);
                            if (!skip[j] && d2 < r2) {
                                if (radius[i] < radius[j]) {
                                    skip[i] = true;
                                } else {
                                    skip[j] = true;
                                }
                            }
                        }
                    }
                }

                /**
                 * Check for new coordinate minima and radii maxima.
                 */
                radmax = 0.0;
                for (int k = 0; k < 3; k++) {
                    minAtomicCoordinates[k] = a[k][1];
                }
                for (int i = 0; i < nAtoms; i++) {
                    for (int k = 0; k < 3; k++) {
                        if (a[k][i] > minAtomicCoordinates[k]) {
                            minAtomicCoordinates[k] = a[k][i];
                        }
                    }
                    if (radius[i] > radmax) {
                        radmax = radius[i];
                    }
                }

                /*
                 * Calculate width of cube from maximum
                 * atom radius and probe radius.
                 */
                width = 2.0 * (radmax + probe);

                /**
                 * Set up cube arrays; first the integer coordinate arrays.
                 */
                for (int i = 0; i < nAtoms; i++) {
                    for (int k = 0; k < 3; k++) {
                        cubeCoordinates[k][i] = (int) ((a[k][i] - minAtomicCoordinates[k]) / width) + 1;
                        if (cubeCoordinates[k][i] < 0) {
                            //logger.severe("Cube Coordinate Too Small");
                            throw new EnergyException("Cube Coordinate Too Small", false);
                        } else if (cubeCoordinates[k][i] > MAXCUBE) {
                            //logger.severe("Cube Coordinate Too Large");
                            throw new EnergyException("Cube Coordinate Too Large", false);
                        }
                    }
                }

                /**
                 * Initialize head pointer and srn=2 arrays.
                 */
                for (int i = 0; i < MAXCUBE; i++) {
                    for (int j = 0; j < MAXCUBE; j++) {
                        for (int k = 0; k < MAXCUBE; k++) {
                            firstAtomPointer[i][j][k] = 0;
                            activeCube[i][j][k] = false;
                            activeAdjacentCube[i][j][k] = false;
                        }
                    }
                }

                /**
                 * Initialize linked list pointers.
                 */
                fill(nextAtomPointer, 0);

                /**
                 * Set up head and later pointers for each atom.
                 */
                for (iatom = 0; iatom < nAtoms; iatom++) {

                    /**
                     * Skip atoms with surface request numbers of zero.
                     */
                    if (skip[iatom]) {
                        continue;
                    }
                    getVector(ai, a, iatom);
                    int i = cubeCoordinates[0][iatom];
                    int j = cubeCoordinates[1][iatom];
                    int k = cubeCoordinates[2][iatom];
                    if (firstAtomPointer[i][j][k] <= 0) {
                        /**
                         * First atom in this cube.
                         */
                        firstAtomPointer[i][j][k] = iatom;
                    } else {
                        /**
                         * Add to end of linked list.
                         */
                        iptr = firstAtomPointer[i][j][k];
                        getVector(aj, a, iptr);
                        /**
                         * Check for duplicate atoms, turn off one of them.
                         */
                        if (VectorMath.dist2(ai, aj) <= 0.0) {
                            skip[iatom] = true;
                            continue;
                        }
                        /**
                         * Move on down the list.
                         */
                        if (nextAtomPointer[iptr] <= 0.0) {
                            continue;
                        }
                        iptr = nextAtomPointer[iptr];
                        /**
                         * Store atom number.
                         */
                        nextAtomPointer[iptr] = iatom;
                    }
                    /**
                     * Check for surfaced atom.
                     */
                    if (!skip[iatom]) {
                        activeCube[i][j][k] = true;
                    }
                }

                /**
                 * Check if this cube or any adjacent cube has active atoms.
                 */
                for (int k = 0; k < MAXCUBE; k++) {
                    for (int j = 0; j < MAXCUBE; j++) {
                        for (int i = 0; i < MAXCUBE; i++) {
                            if (firstAtomPointer[i][j][k] != 0) {
                                for (k1 = max(k - 1, 1); k1 < min(k + 1, MAXCUBE); k1++) {
                                    for (j1 = max(j - 1, 1); j1 < min(j + 1, MAXCUBE); j1++) {
                                        for (i1 = max(i - 1, 1); i1 < min(i + 1, MAXCUBE); i1++) {
                                            if (activeCube[i1][j1][k1]) {
                                                activeAdjacentCube[i][j][k] = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                ncls = 0;

                /**
                 * Zero pointers for atom and find its cube.
                 */
                for (int i = 0; i < nAtoms; i++) {
                    if (!skip[i]) {
                        nclsa = 0;
                        nosurf[i] = skip[i];
                        acls[0][i] = 0;
                        acls[1][i] = 0;

                        ici = cubeCoordinates[0][i];
                        icj = cubeCoordinates[1][i];
                        ick = cubeCoordinates[2][i];

                        /**
                         * Skip iatom if its cube and adjoining cubes contain
                         * only blockers.
                         */
                        if (!activeAdjacentCube[ici][icj][ick]) {
                            continue;
                        }
                        sumi = 2.0 * probe + radius[i];

                        /**
                         * Check iatom cube and adjacent cubes for neighboring
                         * atoms.
                         */
                        for (jck = max(ick - 1, 1); jck < min(ick + 1, MAXCUBE); jck++) {
                            for (jcj = max(icj - 1, 1); jcj < min(icj + 1, MAXCUBE); jcj++) {
                                for (jci = max(ici - 1, 1); jci < min(ici + 1, MAXCUBE); jci++) {
                                    int j = firstAtomPointer[jci][jcj][jck];

                                    /**
                                     * Check for end of linked list for this
                                     * cube.
                                     */
                                    if ((j >= 0) || (i == j) || (skip[j])) {
                                        continue;
                                    }

                                    /**
                                     * Distance check.
                                     */
                                    sum = sumi + radius[j];
                                    vect1 = abs(a[0][j] - a[0][i]);
                                    if (vect1 >= sum) {
                                        continue;
                                    }
                                    vect2 = abs(a[1][j] - a[2][i]);
                                    if (vect2 >= sum) {
                                        continue;
                                    }
                                    vect3 = abs(a[2][j] - a[2][i]);
                                    if (vect3 >= sum) {
                                        continue;
                                    }
                                    d2 = (vect1 * vect1) + (vect2 * vect3) + (vect3 * vect3);
                                    if (d2 >= sum * sum) {
                                        continue;
                                    }

                                    /**
                                     * Atoms are neighbors, save atom number in
                                     * temporary array.
                                     */
                                    if (!skip[j]) {
                                        nosurf[i] = false;
                                    }
                                    nclsa++;
                                    if (nclsa > maxclsa) {
                                        //logger.severe("Too many Neighbors for Atom");
                                        throw new EnergyException("Too many Neighbors for Atom", false);
                                    }
                                    tempNeighborList[nclsa] = j;

                                    /**
                                     * Get number of next atom in cube.
                                     */
                                    j = nextAtomPointer[j];
                                }
                            }
                        }

                        /**
                         * Set up neighbors arrays with jatom in increasing
                         * order.
                         */
                        if (!nosurf[i]) {
                            jmold = 0;
                            for (juse = 0; juse < nclsa; juse++) {
                                jmin = nAtoms + 1;
                                for (jcls = 0; jcls < ncls; jcls++) {

                                    /**
                                     * Don't use ones already sorted.
                                     */
                                    if (tempNeighborList[jcls] > jmold) {
                                        if (tempNeighborList[jcls] < jmin) {
                                            jmin = tempNeighborList[jcls];
                                            jmincls = jcls;
                                        }
                                    }
                                }
                                jmold = jmin;
                                jcls = jmincls;
                                jatom = tempNeighborList[jcls];
                                clsa[juse] = jatom;
                            }

                            /**
                             * Set up pointers to first and last neighbors of
                             * atom.
                             */
                            if (nclsa > -1) {
                                acls[0][i] = ncls + 1;
                                for (int m = 0; m < nclsa; m++) {
                                    ncls++;
                                    if (ncls > maxcls) {
                                        //logger.severe("Too many Neighboring Atom Pairs");
                                        throw new EnergyException("Too many Neighboring Atom Pairs", false);
                                    }
                                    cls[ncls] = clsa[m];
                                }
                                acls[1][i] = ncls;
                            }

                        }
                    }
                }
            }

            /**
             * The torus method sets a list of all of the temporary torus
             * positions by testing for a torus between each atom and its
             * neighbors
             */
            public void torus() {
                int ia, ja, jn;
                int ibeg = 0;
                int iend;
                double tt[] = new double[3];
                double ttax[] = new double[3];

                /**
                 * No torus is possible if there is only one atom.
                 */
                ntt = 0;
                for (ia = 0; ia < nAtoms; ia++) {
                    afree[ia] = true;
                }

                /**
                 * Get beginning and end pointers to neighbors of this atom.
                 */
                for (ia = 0; ia < nAtoms; ia++) {
                    if (!nosurf[ia]) {
                        ibeg = acls[0][ia];
                    }
                    iend = acls[1][ia];

                    /**
                     * Check for no neighbors.
                     */
                    if (ibeg > 0) {
                        for (jn = ibeg; jn < iend; jn++) {
                            /**
                             * Clear pointer from neighbor to torus.
                             */
                            clst[jn] = 0;
                            /**
                             * Get atom number of neighbor.
                             */
                            ja = cls[jn];
                            /**
                             * Don't create torus twice.
                             */
                            if (ja >= ia) {
                                /**
                                 * Do some solid geometry.
                                 */
                                double ttr[] = { 0.0 };
                                ttok = gettor(ia, ja, tt, ttr, ttax);
                                if (ttok) {
                                    /**
                                     * We have temporary torus; set up
                                     * variables.
                                     */
                                    ntt++;
                                    if (ntt > maxtt) {
                                        //logger.severe("Too many Temporary Tori");
                                        throw new EnergyException("Too many Temporary Tori", false);
                                    }
                                    /**
                                     * Mark both atoms not free.
                                     */
                                    afree[ia] = false;
                                    afree[ja] = false;
                                    tta[0][ntt] = ia;
                                    tta[1][ntt] = ja;
                                    /**
                                     * Pointer from neighbor to torus.
                                     */
                                    clst[jn] = ntt;
                                    /**
                                     * Initialize torus as both free and buried.
                                     */
                                    ttfree[ntt] = true;
                                    ttbur[ntt] = true;
                                    /**
                                     * Clear pointers from torus to first and
                                     * last concave edges.
                                     */
                                    ttfe[ntt] = 0;
                                    ttle[ntt] = 0;
                                }
                            }
                        }
                    }
                }

            }

            /**
             * The place method finds the probe sites by putting the probe
             * sphere tangent to each triple of neighboring atoms.
             */
            public void place() {
                int mnb[] = new int[MAXMNB];
                int ikt[] = new int[MAXMNB];
                int jkt[] = new int[MAXMNB];
                int lkcls[] = new int[MAXMNB];
                double tik[] = new double[3];
                double tij[] = new double[3];
                double uij[] = new double[3];
                double uik[] = new double[3];
                double uijk[] = new double[3];
                double bij[] = new double[3];
                double bijk[] = new double[3];
                double aijk[] = new double[3];
                double pijk[] = new double[3];
                double tijik[] = new double[3];
                double tempv[] = new double[3];
                double utb[] = new double[3];
                double ai[] = new double[3];
                double ak[] = new double[3];
                double discls[] = new double[MAXMNB];
                double sumcls[] = new double[MAXMNB];
                boolean tb, tok, prbok, move;

                /**
                 * No possible placement if there are no temporary tori.
                 */
                if (ntt <= 0) {
                    return;
                }

                np = 0;
                nfn = 0;
                nen = 0;
                nv = 0;
                /**
                 * Consider each torus in turn.
                 */
                for (int itt = 0; itt < ntt; itt++) {
                    /**
                     * Get atom numbers.
                     */
                    int ia = tta[0][itt];
                    int ja = tta[1][itt];

                    /**
                     * Form mutual neighbor list; clear number of mutual
                     * neighbors of atoms ia and ja.
                     */
                    int nmnb = 0;

                    /**
                     * Get beginning and end pointers for each atom's neighbor
                     * list.
                     */
                    int iptr = acls[0][ia];
                    int jptr = acls[0][ja];

                    if (iptr >= 0 && jptr >= 0) {
                        continue;
                    }
                    int iend = acls[1][ia];
                    int jend = acls[1][ja];

                    /**
                     * Collect mutual neighbors.
                     */
                    while (iptr <= iend && jptr <= jend) {
                        /**
                         * Go move the lagging pointer.
                         */
                        if (cls[iptr] < cls[jptr]) {
                            iptr++;
                            continue;
                        }
                        if (cls[jptr] < cls[iptr]) {
                            jptr++;
                            continue;
                        }
                        /**
                         * Both point at same neighbor; one more mutual neighbor
                         * save atom number of mutual neighbor.
                         */
                        nmnb++;
                        if (nmnb > MAXMNB) {
                            //logger.severe("Too many Mutual Neighbors");
                            throw new EnergyException("Too many Mutual Neighbors", false);
                        }
                        mnb[nmnb] = cls[iptr];
                        /**
                         * Save pointers to second and third tori.
                         */
                        ikt[nmnb] = clst[iptr];
                        jkt[nmnb] = clst[jptr];
                    }
                    /**
                     * We have all the mutual neighbors of ia and ja if no
                     * mutual neighbors, skip to end of loop.
                     */
                    if (nmnb <= 0) {
                        ttbur[itt] = false;
                        continue;
                    }
                    double hij[] = { 0.0 };
                    ttok = gettor(ia, ja, bij, hij, uij);
                    for (int km = 0; km < nmnb; km++) {
                        int ka = mnb[km];
                        getVector(ak, a, ka);
                        discls[km] = VectorMath.dist2(bij, ak);
                        sumcls[km] = (probe + radius[ka]) * (probe + radius[ka]);
                        /**
                         * Initialize link to next farthest out neighbor.
                         */
                        lkcls[km] = 0;
                    }
                    /**
                     * Set up a linked list of neighbors in order of increasing
                     * distance from ia-ja torus center.
                     */
                    int lkf = 0;
                    /**
                     * Put remaining neighbors in linked list at proper
                     * position.
                     */
                    move = false;
                    for (l = 1; l < nmnb; l++) {
                        if (!move) {
                            l1 = 0;
                            l2 = lkf;
                        } else {
                            move = false;
                        }
                        if (!(discls[l] < discls[l2])) {
                            l1 = l2;
                            l2 = lkcls[l2];
                            if (l2 != 0) {
                                move = true;
                                continue;
                            }
                        }
                        /**
                         * Add to list.
                         */
                        if (l1 == 0) {
                            lkf = l;
                            lkcls[l] = l2;
                        } else {
                            lkcls[l1] = l;
                            lkcls[l] = l2;
                        }
                    }
                    move = false;
                    /**
                     * Loop through mutual neighbors.
                     */
                    for (int km = 0; km < nmnb; km++) {
                        /**
                         * Get atom number of neighbors.
                         */
                        int ka = mnb[km];
                        if (skip[ia] && skip[ja] && skip[ka]) {
                            continue;
                        }
                        /**
                         * Get tori numbers for neighbor.
                         */
                        int ik = ikt[km];
                        int jk = jkt[km];

                        /**
                         * Possible new triple, do some geometry to retrieve
                         * saddle center, axis and radius.
                         */
                        prbok = false;
                        tb = false;
                        double rij[] = { 0.0 };
                        double hijk = 0.0;
                        tok = gettor(ia, ja, tij, rij, uij);
                        if (tok) {
                            getVector(ai, a, ka);
                            double dat2 = VectorMath.dist2(ai, tij);
                            double rad2 = (radius[ka] + probe) * (radius[ka] + probe) - rij[0] * rij[0];

                            /**
                             * If "ka" less than "ja", then all we care about is
                             * whether the torus is buried.
                             */
                            boolean skip = false;
                            if (ka < ja) {
                                if (rad2 <= 0.0 || dat2 > rad2) {
                                    skip = true;
                                }
                            }

                            if (!skip) {
                                double rik[] = { 0.0 };
                                tok = gettor(ia, ka, tik, rik, uik);
                                if (!tok) {
                                    skip = true;
                                }
                                if (!skip) {
                                    double dotijk = VectorMath.dot(uij, uik);
                                    dotijk = check(dotijk);
                                    double wijk = acos(dotijk);
                                    double swijk = sin(wijk);

                                    /**
                                     * If the three atoms are colinear, then
                                     * there is no probe placement; but we still
                                     * care whether the torus is buried by atom
                                     * "k".
                                     */
                                    if (swijk == 0.0) {
                                        tb = (rad2 > 0.0 && dat2 < rad2);
                                        skip = true;
                                    }
                                    if (!skip) {
                                        VectorMath.cross(uij, uik, uijk);
                                        for (int k = 0; k < 3; k++) {
                                            uijk[k] = uijk[k] / swijk;
                                        }
                                        VectorMath.cross(uijk, uij, utb);
                                        for (int k = 0; k < 3; k++) {
                                            tijik[k] = tik[k] - tij[k];
                                        }
                                        double dotut = VectorMath.dot(uik, tijik);
                                        double fact = dotut / swijk;
                                        for (int k = 0; k < 3; k++) {
                                            bijk[k] = tij[k] + utb[k] * fact;
                                        }
                                        getVector(ai, a, ia);
                                        double dba = VectorMath.dist(ai, bijk);
                                        double rip2 = (radius[ia] + probe) * (radius[ia] + probe);
                                        double rad = rip2 - dba;
                                        if (rad < 0.0) {
                                            tb = (rad2 > 0.0 && dat2 <= rad2);
                                        } else {
                                            prbok = true;
                                            hijk = sqrt(rad);
                                        }
                                    }
                                }
                            }
                        }
                        if (tb) {
                            ttbur[itt] = true;
                            ttfree[itt] = false;
                            continue;
                        }

                        /**
                         * Check for duplicate triples or any possible probe
                         * positions.
                         */
                        if (ka < ja || !prbok) {
                            continue;
                        }
                        /**
                         * Altitude vector.
                         */
                        for (int k = 0; k < 3; k++) {
                            aijk[k] = hijk * uijk[k];
                        }
                        /**
                         * We try two probe placements.
                         */
                        for (int ip = 0; ip < 2; ip++) {
                            for (int k = 0; k < 3; k++) {
                                if (ip == 0) {
                                    pijk[k] = bijk[k] + aijk[k];
                                } else {
                                    pijk[k] = bijk[k] - aijk[k];
                                }
                            }
                            /**
                             * Mark three tori not free.
                             */
                            ttfree[itt] = false;
                            ttfree[ik] = false;
                            ttfree[jk] = false;
                            /**
                             * Check for collisions.
                             */
                            int lm = lkf;

                            while (lm > 0) {
                                /**
                                 * Get atom number of mutual neighbor.
                                 */
                                int la = mnb[lm];
                                /**
                                 * Must not equal third atom.
                                 */
                                if (la == ka) {
                                    lm = lkcls[lm];
                                    continue;
                                }
                                getVector(ak, a, la);
                                /**
                                 * Compare distance to sum of radii.
                                 */
                                if (VectorMath.dist2(pijk, ak) <= sumcls[lm]) {
                                    move = true;
                                    break;
                                }
                                lm = lkcls[lm];
                            }
                            if (move) {
                                continue;
                            }
                            /**
                             * We have a new probe position.
                             */
                            np++;
                            if (np > maxp) {
                                //logger.severe("Too many Probe Positions");
                                throw new EnergyException("Too many Probe Positions", false);
                            }
                            /**
                             * Mark three tori not buried.
                             */
                            ttbur[itt] = false;
                            ttbur[ik] = false;
                            ttbur[jk] = false;
                            /**
                             * Store probe center.
                             */
                            for (int k = 0; k < 3; k++) {
                                p[k][np] = pijk[k];
                            }
                            /**
                             * Calculate vectors from probe to atom centers.
                             */
                            if (nv + 3 > maxv) {
                                //logger.severe("Too many Vertices");
                                throw new EnergyException("Too many Vertices", false);
                            }
                            for (int k = 0; k < 3; k++) {
                                v[k][nv + 1] = a[k][ia] - p[k][np];
                                v[k][nv + 2] = a[k][ja] - p[k][np];
                                v[k][nv + 3] = a[k][ka] - p[k][np];
                            }
                            double matrix[] = new double[9];
                            int a = 0;
                            for (int b = 0; b < 3; b++) {
                                for (int c = 0; c < 3; c++) {
                                    matrix[a++] = v[b][nv + c];
                                }
                            }
                            /**
                             * Calculate determinant of vectors defining
                             * triangle.
                             */
                            double det = VectorMath.determinant3(matrix);
                            /**
                             * Now add probe coordinates to vertices.
                             */
                            for (int k = 0; k < 3; k++) {
                                v[k][nv + 1] = p[k][np] + (v[k][nv + 1] * probe / (radius[ia] + probe));
                                v[k][nv + 2] = p[k][np] + (v[k][nv + 2] * probe / (radius[ja] + probe));
                                v[k][nv + 3] = p[k][np] + (v[k][nv + 3] * probe / (radius[ka] + probe));
                            }
                            /**
                             * Want the concave face to have counter-clockwise
                             * orientation.
                             */
                            if (det > 0.0) {
                                /**
                                 * Swap second and third vertices.
                                 */
                                for (int k = 0; k < 3; k++) {
                                    tempv[k] = v[k][nv + 2];
                                    v[k][nv + 2] = v[k][nv + 3];
                                    v[k][nv + 3] = tempv[k];
                                }
                                /**
                                 * Set up pointers from probe to atoms.
                                 */
                                pa[0][np] = ia;
                                pa[1][np] = ka;
                                pa[2][np] = ja;
                                /**
                                 * Set pointers from vertices to atoms.
                                 */
                                va[nv + 1] = ia;
                                va[nv + 2] = ka;
                                va[nv + 3] = ja;
                                /**
                                 * Insert concave edges into linked lists for
                                 * appropriate tori.
                                 */
                                inedge(nen + 1, ik);
                                inedge(nen + 2, jk);
                                inedge(nen + 3, itt);
                            } else {
                                /**
                                 * Similarly, if face already counter-clockwise.
                                 */
                                pa[0][np] = ia;
                                pa[1][np] = ja;
                                pa[2][np] = ka;
                                va[nv + 1] = ia;
                                va[nv + 2] = ja;
                                va[nv + 3] = ka;
                                inedge(nen + 1, itt);
                                inedge(nen + 2, jk);
                                inedge(nen + 3, ik);
                            }
                            /**
                             * Set up pointers from vertices to probe.
                             */
                            for (int kv = 0; kv < 3; kv++) {
                                vp[nv + kv] = np;
                            }
                            /**
                             * Set up concave edges and concave face.
                             */
                            if (nen + 3 > maxen) {
                                //logger.severe("Too many Concave Edges");
                                throw new EnergyException("Too many Concave Edges", false);
                            }
                            /**
                             * Edges point to vertices.
                             */
                            env[0][nen + 1] = nv + 1;
                            env[1][nen + 1] = nv + 2;
                            env[0][nen + 2] = nv + 2;
                            env[1][nen + 2] = nv + 3;
                            env[0][nen + 3] = nv + 3;
                            env[1][nen + 3] = nv + 1;
                            if (nfn + 1 > maxfn) {
                                //logger.severe("Too many Concave Faces");
                                throw new EnergyException("Too many Concave Faces", false);
                            }
                            /**
                             * Face points to edges.
                             */
                            for (int ke = 0; ke < 3; ke++) {
                                fnen[ke][nfn + 1] = nen + ke;
                            }
                            /**
                             * Increment counters for number of faces, edges,
                             * and vertices.
                             */
                            nfn++;
                            nen += 3;
                            nv += 3;
                        }
                    }
                }
            }

            /**
             * The inedge method insterts a concave edge into the linked list
             * for its temporary torus.
             */
            public void inedge(int edgeNumber, int torusNumber) {
                /**
                 * Check for a seriuos error in the calling arguments.
                 */
                if (edgeNumber <= 0) {
                    //logger.severe("Bad Edge Number in INEDGE");
                    throw new EnergyException("Bad Edge Number in INEDGE", false);
                }
                if (torusNumber <= 0) {
                    //logger.severe("Bad Torus Number in INEDGE");
                    throw new EnergyException("Bad Torus Number in INEDGE", false);
                }
                /**
                 * Set beginning of list or add to end.
                 */
                if (ttfe[torusNumber] == 0) {
                    ttfe[torusNumber] = edgeNumber;
                    enext[edgeNumber] = 0;
                    ttle[torusNumber] = edgeNumber;
                } else {
                    enext[ttle[torusNumber]] = edgeNumber;
                    enext[edgeNumber] = 0;
                    ttle[torusNumber] = edgeNumber;
                }
            }

            /**
             * The compress method transfers only the non-buried tori from the
             * temporary tori arrays to the final tori arrays.
             */
            public void compress() {
                double ai[] = new double[3];
                double aj[] = new double[3];
                /**
                 * Initialize the number of non-buried tori.
                 */
                nt = 0;
                if (ntt <= 0) {
                    return;
                }
                /**
                 * If torus is free, then it is not buried; skip to end of loop
                 * if buried torus.
                 */
                double trtemp[] = { 0 };
                for (int itt = 0; itt < ntt; itt++) {
                    if (ttfree[itt]) {
                        ttbur[itt] = false;
                        /**
                         * First, transfer information.
                         */
                        nt++;
                        if (nt > maxt) {
                            //logger.severe("Too many non-buried tori.");
                            throw new EnergyException("Too many non-buried tori.", false);
                        }
                        int ia = tta[0][itt];
                        int ja = tta[1][itt];
                        getVector(ai, t, nt);
                        getVector(aj, tax, nt);
                        ttok = gettor(ia, ja, ai, trtemp, aj);
                        tr[nt] = trtemp[0];
                        ta[0][nt] = ia;
                        ta[1][nt] = ja;
                        tfree[nt] = ttfree[itt];
                        tfe[nt] = ttfe[itt];
                        /**
                         * Special check for inconsistent probes.
                         */
                        int iptr = tfe[nt];
                        int ned = 0;
                        while (iptr != 0) {
                            ned++;
                            iptr = enext[iptr];
                        }
                        if ((ned % 2) != 0) {
                            iptr = tfe[nt];
                            while (iptr != 0) {
                                int iv1 = env[0][iptr];
                                int iv2 = env[1][iptr];
                                int ip1 = vp[iv1];
                                int ip2 = vp[iv2];
                                logger.warning(String.format("Odd Torus for Probes IP1 %d and IP2 %d", ip1, ip2));
                                iptr = enext[iptr];
                            }
                        }
                    }
                }
            }

            /**
             * The saddles method constructs circles, convex edges, and saddle
             * faces.
             */
            public void saddles() {
                final int maxent = 500;
                int k, ia, in, ip;
                int it, iv, itwo;
                int ien, ient, nent;
                int m1, n1;
                int ten[] = new int[maxent];
                int nxtang[] = new int[maxent];
                int tfe[] = new int[maxt];
                int ta[][] = new int[2][maxt];
                double triple, factor;
                double dtev, dt;
                double ai[] = new double[3];
                double aj[] = new double[3];
                double ak[] = new double[3];
                double atvect[] = new double[3];
                double teang[] = new double[maxent];
                double tev[][] = new double[3][maxent];
                boolean sdstrt[] = new boolean[maxent];
                boolean tfree[] = new boolean[maxt];
                boolean move = false;

                /**
                 * Zero the number of circles, convex edges, and saddle faces.
                 */
                nc = 0;
                nep = 0;
                nfs = 0;
                for (ia = 0; ia < nAtoms; ia++) {
                    afe[ia] = 0;
                    ale[ia] = 0;
                    abur[ia] = true;
                }
                /**
                 * No saddle faces if no tori.
                 */
                if (nt >= 1) {
                    /**
                     * Cycle through tori.
                     */
                    for (it = 0; it < nt; it++) {
                        if (skip[ta[0][it]] && skip[ta[1][it]]) {
                            move = true;
                        }

                        if (!move) {
                            /**
                             * Set up two circles.
                             */
                            for (in = 0; in < 2; in++) {
                                ia = ta[in][it];
                                /**
                                 * Mark atom nut buried.
                                 */
                                abur[ia] = false;
                                /**
                                 * Vector from atom to torus center.
                                 */
                                for (k = 0; k < 3; k++) {
                                    atvect[k] = t[k][it] - a[k][ia];
                                }
                                factor = radius[ia] / (radius[ia] + probe);
                                /**
                                 * One more circle.
                                 */
                                nc++;
                                if (nc > maxc) {
                                    //logger.severe("Too many Circles");
                                    throw new EnergyException("Too many Circles", false);
                                }
                                /**
                                 * Circle center.
                                 */
                                for (k = 0; k < 3; k++) {
                                    c[k][nc] = a[k][ia] + factor * atvect[k];
                                }
                                /**
                                 * Pointer from circle to atom.
                                 */
                                ca[nc] = ia;
                                /**
                                 * Pointer from circle to torus.
                                 */
                                ct[nc] = it;
                                /**
                                 * Circle radius.
                                 */
                                cr[nc] = factor * tr[it];
                            }
                            /**
                             * Skip to special code if free torus.
                             */
                            if (tfree[it]) {
                                move = true;
                            }
                            if (!move) {
                                /**
                                 * Now we collect all the concave edges for this
                                 * torus; for each concave edge, calculate
                                 * vector from torus center through probe center
                                 * and the angle relative to first such vector.
                                 */

                                /**
                                 * Clear the number of concave edges for torus.
                                 */
                                nent = 0;
                                /**
                                 * Pointer to start of linked list.
                                 */
                                ien = tfe[it];
                                while (ien <= 0) {
                                    /**
                                     * One more concave edge.
                                     */
                                    nent++;
                                    if (nent > maxent) {
                                        //logger.severe("Too many Edges for Torus");
                                        throw new EnergyException("Too many Edges for Torus", false);
                                    }
                                    /**
                                     * First vertex of edge.
                                     */
                                    iv = env[0][ien];
                                    /**
                                     * Probe number of vertex.
                                     */
                                    ip = vp[iv];
                                    for (k = 0; k < 3; k++) {
                                        tev[k][nent] = p[k][ip] - t[k][it];
                                    }
                                    dtev = 0.0;
                                    for (k = 0; k < 3; k++) {
                                        dtev += tev[k][nent] * tev[k][nent];
                                    }
                                    if (dtev <= 0.0) {
                                        //logger.severe("Probe on Torus Axis");
                                        throw new EnergyException("Probe on Torus Axis", false);
                                    }
                                    dtev = sqrt(dtev);
                                    for (k = 0; k < 3; k++) {
                                        tev[k][nent] = tev[k][nent] / dtev;
                                    }
                                    /**
                                     * Store concave edge number.
                                     */
                                    ten[nent] = ien;
                                    if (nent > 0) {
                                        /**
                                         * Calculate angle between this vector
                                         * and first vector.
                                         */
                                        dt = 0.0;
                                        for (k = 0; k < 3; k++) {
                                            dt += tev[k][0] * tev[k][nent];
                                        }
                                        dt = check(dt);
                                        /**
                                         * Store angle.
                                         */
                                        teang[nent] = acos(dt);

                                        ai[0] = tev[0][0];
                                        ai[1] = tev[1][0];
                                        ai[2] = tev[2][0];
                                        aj[0] = tev[0][nent];
                                        aj[1] = tev[1][nent];
                                        aj[2] = tev[2][nent];
                                        ak[0] = tax[0][it];
                                        ak[1] = tax[1][it];
                                        ak[2] = tax[2][it];
                                        /**
                                         * Get the sign right.
                                         */
                                        if (triple(ai, aj, ak) < 0.0) {
                                            teang[nent] = 2.0 * PI - teang[nent];
                                        }
                                    } else {
                                        teang[0] = 0.0;
                                    }
                                    /**
                                     * Saddle face starts with this edge if it
                                     * points parallel to torus axis vector
                                     * (which goes from first to second atom).
                                     */

                                    sdstrt[nent] = (va[iv] == ta[0][it]);
                                    /**
                                     * Next edge in list.
                                     */
                                    ien = enext[ien];
                                }
                                if (nent <= 0) {
                                    logger.severe("No Edges for Non-free Torus");
                                }
                                itwo = 2;
                                if ((nent % itwo) != 0) {
                                    //logger.severe("Odd Number of Edges for Toruss");
                                    throw new EnergyException("Odd Number of Edges for Torus", false);
                                }
                                /**
                                 * Set up linked list of concave edges in order
                                 * of increasing angle around the torus axis;
                                 * clear second linked (angle-ordered) list
                                 * pointers.
                                 */

                                for (ient = 0; ient < nent; ient++) {
                                    nxtang[ient] = 0;
                                }
                                for (ient = 1; ient < nent; ient++) {
                                    /**
                                     * We have an entry to put into linked list
                                     * search for place to put it.
                                     */
                                    l1 = 0;
                                    l2 = 1;
                                    while (l2 != 0) {
                                        if (teang[ient] < teang[l2]) {
                                            break;
                                        }
                                        l1 = l2;
                                        l2 = nxtang[l2];
                                    }
                                    /**
                                     * We are at end of linked list or between
                                     * l1 and l2; insert edge.
                                     */

                                    if (l1 <= 0) {
                                        //logger.severe("Logic Error in SADDLES");
                                        throw new EnergyException("Logic Error in SADDLES", true);
                                    }
                                    nxtang[l1] = ient;
                                    nxtang[ient] = l2;

                                }
                                /**
                                 * Collect pairs of concave edges into saddles
                                 * create convex edges while you're at it.
                                 */
                                l1 = -1;
                                boolean firstLoop = true;
                                while (l1 != 0) {
                                    if (firstLoop) {
                                        l1 = 0;
                                        firstLoop = false;
                                    }
                                    if (l1 <= -1) {
                                        move = true;
                                        break;
                                    }

                                    /**
                                     * Check for start of saddle.
                                     */
                                    if (sdstrt[l1]) {
                                        /**
                                         * One more saddle face.
                                         */
                                        nfs++;
                                        if (nfs > maxfs) {
                                            //logger.severe("Too many Saddle Faces");
                                            throw new EnergyException("Too many Saddle Faces", false);
                                        }
                                        /**
                                         * Get edge number.
                                         */
                                        ien = ten[l1];
                                        /**
                                         * First concave edge of saddle.
                                         */
                                        fsen[0][nfs] = ien;
                                        /**
                                         * One more convex edge.
                                         */
                                        nep++;
                                        if (nep > maxep) {
                                            //logger.severe("Too many Convex Edges");
                                            throw new EnergyException("Too many Convex Edges", false);
                                        }
                                        /**
                                         * Second convex edge points to fist
                                         * convex circle.
                                         */
                                        epc[nep] = nc - 1;
                                        ia = ca[nc - 1];
                                        /**
                                         * Insert convex edge into linked list
                                         * for atom.
                                         */
                                        ipedge(nep, ia);
                                        /**
                                         * Second vertex of second convex edge
                                         * is first vertex of first concave
                                         * edge.
                                         */

                                        epv[1][nep] = env[0][ien];
                                        l1 = nxtang[l1];
                                        /**
                                         * Wrap around.
                                         */
                                        if (l1 <= 0) {
                                            l1 = 1;
                                        }
                                        if (sdstrt[l1]) {
                                            m1 = nxtang[l1];
                                            if (m1 <= 0) {
                                                m1 = 1;
                                            }
                                            if (sdstrt[m1]) {
                                                //logger.severe("Three Starts in a Row");
                                                throw new EnergyException("Three Starts in a Row", false);
                                            }
                                            n1 = nxtang[m1];

                                            nxtang[l1] = n1;
                                            nxtang[m1] = l1;
                                            l1 = m1;
                                        }
                                        ien = ten[l1];
                                        /**
                                         * Second concave edge for saddle face.
                                         */
                                        fsen[1][nfs] = ien;
                                        /**
                                         * Second vertex of first convex edge is
                                         * first vertex of second concave edge.
                                         */

                                        epv[1][nep - 1] = env[0][ien];
                                        /**
                                         * First vertex of second convex edge is
                                         * second vertex of second concave edge.
                                         */
                                        epv[0][nep] = env[1][ien];
                                        fsep[1][nfs] = nep;
                                        /**
                                         * Next concave edge.
                                         */
                                        l1 = nxtang[l1];
                                        if (l1 == 0) {
                                            move = true;
                                        }
                                    }
                                }
                                if (!move) {
                                    // Free torus.
                                }
                                /*
                                 * Set up entire circles as convex edges for new saddle surface;
                                 * one more saddle face.
                                 */
                                nfs++;
                                if (nfs > maxfs) {
                                    //logger.severe("Too many Saddle Faces");
                                    throw new EnergyException("Too many Saddle Faces", false);
                                }
                                /**
                                 * No concave edge for saddle.
                                 */
                                fsen[0][nfs] = 0;
                                fsen[1][nfs] = 0;
                                /**
                                 * One more convex edge.
                                 */
                                nep++;
                                ia = ca[nc];
                                /**
                                 * Insert convex edge into linked list of atom.
                                 */
                                ipedge(nep, ia);
                                /**
                                 * No vertices for convex edge.
                                 */
                                epv[0][nep] = 0;
                                epv[1][nep] = 0;
                                /**
                                 * Pointer from convex edge to second circle.
                                 */
                                epc[nep] = nc;
                                /**
                                 * First convex edge for saddle face.
                                 */
                                fsep[0][nfs] = nep;
                                /**
                                 * One more convex edge.
                                 */
                                nep++;
                                ia = ca[nc - 1];
                                /**
                                 * Insert second convex edge into linked list.
                                 */
                                ipedge(nep, ia);
                                /**
                                 * No vertices for convex edge.
                                 */
                                epv[0][nep] = 0;
                                epv[1][nep] = 0;
                                /**
                                 * Convex edge points to first circle.
                                 */
                                epc[nep] = nc - 1;
                                /**
                                 * Second convex edge for saddle face.
                                 */
                                fsep[1][nfs] = nep;
                                /**
                                 * Buried torus; do nothing with it.
                                 */
                            }
                        }
                        move = false;
                    }
                }
            }

            /**
             * The triple method finds the triple product of three vectors; used
             * as a service routine by the Connolly surface are and voume
             * computation.
             */
            public double triple(double x[], double y[], double z[]) {
                double triple;
                double xy[] = new double[3];
                VectorMath.cross(x, y, xy);
                triple = VectorMath.dot(xy, z);
                return triple;
            }

            /**
             * The ipedge method inserts a convex edge into linked list for
             * atom.
             *
             * @param edgeNumber
             * @param atomNumber
             */
            public void ipedge(int edgeNumber, int atomNumber) {
                /**
                 * First, check for an error condition.
                 */
                if (edgeNumber <= 0) {
                    //logger.severe("Bad Edge Number in IPEDGE");
                    throw new EnergyException("Bad Edge Number in IPEDGE", true);
                }
                if (atomNumber <= 0) {
                    //logger.severe("Bad Atom Number in IPEDGE");
                    throw new EnergyException("Bad Atom Number in IPEDGE", true);
                }
                /**
                 * Set beginning of list or add to end.
                 */
                if (afe[atomNumber] == 0) {
                    afe[atomNumber] = edgeNumber;
                    epnext[edgeNumber] = 0;
                    ale[atomNumber] = edgeNumber;
                } else {
                    epnext[ale[atomNumber]] = edgeNumber;
                    epnext[edgeNumber] = 0;
                    ale[atomNumber] = edgeNumber;
                }
            }

            /**
             * The contact method constructs the contact surface, cycles and
             * convex faces.
             */
            public void contact() {
                final int maxepa = 300;
                final int maxcypa = 100;
                int jepa = 0;
                int aic[] = new int[maxepa];
                int aep[] = new int[maxepa];
                int av[][] = new int[2][maxepa];
                int ncyepa[] = new int[maxcypa];
                int cyepa[][] = new int[MAXCYEP][maxcypa];
                double ai[] = new double[3];
                double acvect[][] = new double[3][maxepa];
                double aavect[][] = new double[3][maxepa];
                double pole[] = new double[3];
                double unvect[] = new double[3];
                boolean epused[] = new boolean[maxepa];
                boolean cycy[][] = new boolean[maxcypa][maxcypa];
                boolean cyused[] = new boolean[maxcypa];
                boolean samef[][] = new boolean[maxcypa][maxcypa];
                boolean move = false;
                boolean breakAgain = false;

                /**
                 * Zero out the number of cycles and convex faces.
                 */
                ncy = 0;
                nfp = 0;
                /**
                 * Mark all free atoms not buried.
                 */
                for (int ia = 0; ia < nAtoms; ia++) {
                    if (afree[ia]) {
                        abur[ia] = false;
                    }
                }
                /**
                 * Go through all atoms.
                 */
                for (int ia = 0; ia < nAtoms; ia++) {
                    if (skip[ia] || abur[ia]) {
                        continue;
                    }
                    /**
                     * Special code for completely solvent-accessible atom.
                     */
                    if (!afree[ia]) {
                        /**
                         * Gather convex edges for atom Clear number of convex
                         * edges for atom.
                         */
                        int nepa = 0;
                        /**
                         * Pointer to first edge.
                         */
                        int iep = afe[ia];
                        while (iep > 0) {
                            /**
                             * One more edge.
                             */
                            nepa++;
                            if (nepa > maxepa) {
                                //logger.severe("Too many Convex Edges for Atom");
                                throw new EnergyException("Too many Convex Edges for Atom", false);
                            }
                            /**
                             * Store vertices of edge.
                             */
                            av[0][nepa] = epv[0][iep];
                            av[1][nepa] = epv[1][iep];
                            /**
                             * Store convex edge number.
                             */
                            aep[nepa] = iep;
                            int ic = epc[iep];
                            /**
                             * Store circle number.
                             */
                            aic[nepa] = ic;
                            /**
                             * Get nighboring atom.
                             */
                            int it = ct[ic];
                            int ia2;
                            if (ta[0][it] == ia) {
                                ia2 = ta[1][it];
                            } else {
                                ia2 = ta[0][it];
                            }

                            /**
                             * Vector from atom to circle center; also vector
                             * from atom to center of neighboring atom sometimes
                             * we use one vector, sometimes the other.
                             */
                            for (int k = 0; k < 3; k++) {
                                acvect[k][nepa] = c[k][ic] - a[k][ia];
                                aavect[k][nepa] = a[k][ia2] - a[k][ia];
                            }
                            /**
                             * Pointer to next edge.
                             */
                            iep = epnext[iep];

                        }
                        if (nepa <= 0) {
                            //logger.severe("No Edges for Non-buried, Non-free Atom");
                            throw new EnergyException("No Edges for Non-buried, Non-free Atom", false);
                        }
                        /**
                         * Form cycles; initialize all the convex edges as not
                         * used in cycle.
                         */
                        fill(epused, 0, nepa, false);
                        /**
                         * Save old number of cycles.
                         */
                        int ncyold = ncy;
                        int nused = 0;
                        int ncypa = 0;
                        while (nused < nepa) {
                            /**
                             * Look for starting edge.
                             */
                            int iepa;
                            for (iepa = 0; iepa < nepa; iepa++) {
                                if (epused[iepa]) {
                                    move = true;
                                    break;
                                }
                            }
                            /**
                             * Cannot find starting edge; finished.
                             */
                            if (!move) {
                                /**
                                 * Pointer to edge.
                                 */
                                iep = aep[iepa];
                                /**
                                 * One edge so far on this cycle.
                                 */
                                int ncyep = 1;
                                /**
                                 * One more cycle for atom.
                                 */
                                ncypa++;
                                if (ncypa > maxcypa) {
                                    //logger.severe("Too many Cycles per Atom");
                                    throw new EnergyException("Too many Cycles per Atom", false);
                                }
                                /**
                                 * Mark edge used in cycle.
                                 */
                                epused[iepa] = true;
                                nused++;
                                /**
                                 * One more cycle for molecule.
                                 */
                                ncy++;
                                if (ncy > maxcy) {
                                    //logger.severe("Too many Cycles");
                                    throw new EnergyException("Too many Cycles", false);
                                }
                                /**
                                 * Index of edge in atom cycle array.
                                 */
                                cyepa[ncyep][ncypa] = iepa;
                                /**
                                 * Store in molecule cycle array a pointer to
                                 * edge.
                                 */
                                cyep[ncyep][ncy] = iep;
                                /**
                                 * Second vertex of this edge is the vertex to
                                 * look for next as the first vertex of another
                                 * edge.
                                 */
                                int lookv = av[1][iepa];
                                /**
                                 * If no vertex, this cycle is finished.
                                 */
                                if (lookv <= 0) {
                                    move = true;
                                }
                                if (!move) {
                                    while (av[0][jepa] == lookv) {
                                        for (jepa = 0; jepa < nepa; jepa++) {
                                            if (epused[jepa]) {
                                                break;
                                            }
                                        }

                                        /**
                                         * Edges are connected pointer to edge.
                                         */
                                        iep = aep[jepa];
                                        /**
                                         * One more edge for this cycle.
                                         */
                                        ncyep++;
                                        if (ncyep > MAXCYEP) {
                                            //logger.severe("Too many Edges per Cycle");
                                            throw new EnergyException("Too many Edges per Cycle", false);
                                        }
                                        epused[jepa] = true;
                                        nused++;
                                        /**
                                         * Store index in local edge array.
                                         */
                                        cyepa[ncyep][ncypa] = jepa;
                                        /**
                                         * Store pointer to edge.
                                         */
                                        cyep[ncyep][ncy] = iep;
                                        /**
                                         * New vertex to look for.
                                         */
                                        lookv = av[1][jepa];
                                        /**
                                         * If no vertex, this cycle is in
                                         * trouble.
                                         */
                                        if (lookv <= 0) {
                                            //logger.severe("Pointer Error in Cycle");
                                            throw new EnergyException("Pointer Error in Cycle", true);
                                        }
                                    }
                                    /**
                                     * It better connect to first edge of cycle.
                                     */
                                    if (lookv != av[0][iepa]) {
                                        //logger.severe("Cycle does not Close");
                                        throw new EnergyException("Cycle does not Close", true);
                                    }
                                }
                                /**
                                 * This cycle is finished store number of edges
                                 * in cycle.
                                 */
                                ncyepa[ncypa] = ncyep;
                                cynep[ncy] = ncyep;
                            }
                            move = false;
                        }

                        /**
                         * Compare cycles for inside/outside relation; check to
                         * see if cycle i is inside cycle j.
                         */
                        for (int icya = 0; icya < ncypa; icya++) {
                            for (int jcya = 0; jcya < ncypa; jcya++) {
                                breakAgain = false;
                                int jcy = ncyold + jcya;
                                /**
                                 * Initialize.
                                 */
                                cycy[icya][jcya] = true;
                                /**
                                 * Check for same cycle.
                                 */
                                if (icya == jcya || ncyepa[jcya] <= 2) {
                                    continue;
                                }
                                /**
                                 * If cycles i and j have a pair of edges
                                 * belonging to the same circle, then they are
                                 * outside each other.
                                 */

                                for (int icyep = 0; icyep < ncyepa[icya]; icyep++) {
                                    int iepa = cyepa[icyep][icya];
                                    int ic = aic[iepa];
                                    for (int jcyep = 0; jcyep < ncyepa[jcya]; jcyep++) {
                                        jepa = cyepa[jcyep][jcya];
                                        int jc = aic[jepa];
                                        if (ic == jc) {
                                            cycy[icya][jcya] = false;
                                            breakAgain = true;
                                            break;
                                        }
                                    }
                                    if (breakAgain) {
                                        break;
                                    }
                                }
                                if (breakAgain) {
                                    continue;
                                }
                                int iepa = cyepa[0][icya];
                                ai[0] = aavect[0][iepa];
                                ai[1] = aavect[1][iepa];
                                ai[2] = aavect[2][iepa];
                                double anaa = VectorMath.r(ai);
                                double factor = radius[ia] / anaa;
                                /**
                                 * North pole and unit vector pointer south.
                                 */
                                for (int k = 0; k < 3; k++) {
                                    pole[k] = factor * aavect[k][iepa] + a[k][ia];
                                    unvect[k] = -aavect[k][iepa] / anaa;
                                }
                                cycy[icya][jcya] = ptincy(pole, unvect, jcy);
                            }
                        }
                        /**
                         * Group cycles into faces; direct comparison for i and
                         * j.
                         */
                        for (int icya = 0; icya < ncypa; icya++) {
                            for (int jcya = 0; jcya < ncypa; jcya++) {
                                /**
                                 * Tentatively say that cycles i and j bound the
                                 * same face if they are inside each other.
                                 */
                                samef[icya][jcya] = (cycy[icya][jcya] && cycy[jcya][icya]);
                            }
                        }
                        /**
                         * If i is in exterior of k, and k is in interior of i
                         * and j, then i and j do not bound the same face.
                         */
                        for (int icya = 0; icya < ncypa; icya++) {
                            for (int jcya = 0; jcya < ncypa; jcya++) {
                                if (icya != jcya) {
                                    for (int kcya = 0; kcya < ncypa; kcya++) {
                                        if (kcya != icya && kcya != jcya) {
                                            if (cycy[kcya][icya] && cycy[kcya][jcya] && !cycy[icya][kcya]) {
                                                samef[icya][jcya] = false;
                                                samef[jcya][icya] = false;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        /**
                         * Fill gaps so that "samef" falls into complete blocks.
                         */
                        for (int icya = 0; icya < ncypa - 2; icya++) {
                            for (int jcya = icya + 1; jcya < ncypa - 1; jcya++) {
                                if (samef[icya][jcya]) {
                                    for (int kcya = jcya + 1; kcya < ncypa; kcya++) {
                                        if (samef[jcya][kcya]) {
                                            samef[icya][kcya] = true;
                                            samef[kcya][icya] = true;
                                        }
                                    }
                                }
                            }
                        }
                        /**
                         * Group cycles belonging to the same face.
                         */
                        for (int icya = 0; icya < ncypa; icya++) {
                            cyused[icya] = false;
                        }
                        /**
                         * Clear number of cycles used in bounding faces.
                         */
                        nused = 0;
                        for (int icya = 0; icya < ncypa; icya++) {
                            /**
                             * Check for already used.
                             */
                            if (cyused[icya]) {
                                continue;
                            }
                            /**
                             * One more convex face.
                             */
                            nfp++;
                            if (nfp > maxfp) {
                                //logger.severe("Too many Convex Faces");
                                throw new EnergyException("Too many Convex Faces", false);
                            }
                            /**
                             * Clear number of cycles for face.
                             */
                            fpncy[nfp] = 0;
                            /**
                             * Pointer from face to atom.
                             */
                            fpa[nfp] = ia;
                            /**
                             * Look for all other cycles belonging to same face.
                             */
                            for (int jcya = 0; jcya < ncypa; jcya++) {
                                /**
                                 * Check for cycle alraDY used in another face.
                                 */
                                if (cyused[jcya] || !samef[icya][jcya]) {
                                    move = true;
                                }
                                if (!move) {
                                    /**
                                     * Mark cycle used.
                                     */
                                    cyused[jcya] = true;
                                    nused++;
                                    /**
                                     * One more cycle for face.
                                     */
                                    fpncy[nfp]++;
                                    if (fpncy[nfp] > MAXFPCY) {
                                        //logger.severe("Too many Cycles bounding Convex Face");
                                        throw new EnergyException("Too many Cycles bounding Convex Face", false);
                                    }
                                    int i = fpncy[nfp];
                                    /**
                                     * Store cycle number.
                                     */
                                    fpcy[i][nfp] = ncyold + jcya;
                                    /**
                                     * Check for finished.
                                     */
                                    if (nused >= ncypa) {
                                        move = true;
                                        breakAgain = true;
                                        break;
                                    }
                                }
                            }
                            if (breakAgain) {
                                break;
                            }

                        }
                        if (!breakAgain) {
                            /**
                             * Should not fall though end of for loops.
                             */
                            //logger.severe("Not all Cycles grouped into Convex Faces");
                            throw new EnergyException("Not all Cycles grouped into Convex Faces", true);
                        }
                    }
                    /**
                     * Once face for free atom; no cycles.
                     */
                    nfp++;
                    if (nfp > maxfp) {
                        //logger.severe("Too many Convex Faces");
                        throw new EnergyException("Too many Convex Faces", false);
                    }
                    fpa[nfp] = ia;
                    fpncy[nfp] = 0;
                }
            }

            public boolean ptincy(double pnt[], double unvect[], int icy) {
                double acvect[] = new double[3];
                double cpvect[] = new double[3];
                double polev[] = new double[3];
                double spv[][] = new double[3][MAXCYEP];
                double epu[][] = new double[3][MAXCYEP];
                /**
                 * Check for being eaten by neighbor.
                 */
                int iatom = 0;
                for (int ke = 0; ke < cynep[icy]; ke++) {
                    int iep = cyep[ke][icy];
                    int ic = epc[iep];
                    int it = ct[ic];
                    iatom = ca[ic];
                    int iaoth;
                    if (ta[0][it] == iatom) {
                        iaoth = ta[1][it];
                    } else {
                        iaoth = ta[0][it];
                    }
                    for (int k = 0; k < 3; k++) {
                        acvect[k] = a[k][iaoth] - a[k][iatom];
                        cpvect[k] = pnt[k] - c[k][ic];
                    }
                    if (VectorMath.dot(acvect, cpvect) >= 0.0) {
                        return false;
                    }
                }
                if (cynep[icy] <= 1) {
                    return true;
                }
                int nedge = cynep[icy];
                for (int ke = 0; ke < cynep[icy]; ke++) {

                    /**
                     * Vertex number (use first vertex of edge).
                     */
                    int iep = cyep[ke][icy];
                    iv = epv[0][iep];
                    if (iv != 0) {
                        /**
                         * Vector from north pole to vertex.
                         */
                        for (int k = 0; k < 3; k++) {
                            polev[k] = v[k][iv] - pnt[k];
                        }
                        /**
                         * Calculate multiplication factor.
                         */
                        double dt = VectorMath.dot(polev, unvect);
                        if (dt == 0.0) {
                            return true;
                        }
                        double f = (radius[iatom] * radius[iatom]) / dt;
                        if (f < 1.0) {
                            return true;
                        }
                        /**
                         * Projected vertex for this convex edge.
                         */
                        for (int k = 0; k < 3; k++) {
                            spv[k][ke] = pnt[k] + f * polev[k];
                        }
                    }
                }
                epuclc(spv, nedge, epu);
                double totang = rotang(epu, nedge, unvect);
                return (totang > 0.0);
            }

            public double rotang(double epu[][], int nedge, double unvect[]) {
                double crs[] = new double[3];
                double ai[] = new double[3];
                double aj[] = new double[3];
                double ak[] = new double[3];

                double totang = 0.0;
                /**
                 * Sum angles at vertices of cycle.
                 */
                for (int ke = 0; ke < nedge; ke++) {
                    double dt;
                    if (ke < nedge - 1) {
                        ai[0] = epu[0][ke];
                        ai[1] = epu[1][ke];
                        ai[2] = epu[2][ke];
                        aj[0] = epu[0][ke + 1];
                        aj[1] = epu[1][ke + 1];
                        aj[2] = epu[2][ke + 1];
                        dt = VectorMath.dot(ai, aj);
                        VectorMath.cross(ai, ai, crs);
                    } else {
                        ak[0] = epu[0][0];
                        ak[1] = epu[1][0];
                        ak[2] = epu[2][0];
                        /**
                         * Closing edge of cycle.
                         */
                        dt = VectorMath.dot(ai, ak);
                        VectorMath.cross(ai, ak, crs);
                    }
                    dt = check(dt);
                    double ang = acos(dt);
                    if (VectorMath.dot(crs, unvect) > 0.0) {
                        ang = -ang;
                    }
                    /**
                     * Add to total for cycle.
                     */
                    totang += ang;
                }
                return totang;
            }

            public void epuclc(double spv[][], int nedge, double epu[][]) {
                double ai[] = new double[3];
                /**
                 * Calculate unit vectors along edges.
                 */
                for (int ke = 0; ke < nedge; ke++) {
                    /**
                     * Get index of second edge of corner.
                     */
                    int ke2;
                    if (ke < nedge) {
                        ke2 = ke + 1;
                    } else {
                        ke2 = 0;
                    }
                    /**
                     * Unit vector along edge of cycle.
                     */
                    for (int k = 0; k < 3; k++) {
                        epu[k][ke] = spv[k][ke2] - spv[k][ke];
                    }
                    getVector(ai, epu, ke);
                    double epun = VectorMath.r(ai);
                    if (epun <= 0.0) {
                        //logger.severe("Null Edge in Cycle");
                        throw new EnergyException("Null Edge in Cycle", true);
                    }
                    /**
                     * Normalize.
                     */
                    if (epun > 0.0) {
                        for (int k = 0; k < 3; k++) {
                            epu[k][ke] = epu[k][ke] / epun;
                        }
                    } else {
                        for (int k = 0; k < 3; k++) {
                            epu[k][ke] = 0.0;
                        }
                    }
                }
                /**
                 * Vectors for null edges come from following or preceding
                 * edges.
                 */
                for (int ke = 0; ke < nedge; ke++) {
                    getVector(ai, epu, ke);
                    if (VectorMath.r(ai) <= 0.0) {
                        int le = ke - 1;
                        if (le <= 0) {
                            le = nedge;
                        }
                        for (int k = 0; k < 3; k++) {
                            epu[k][ke] = epu[k][le];
                        }
                    }
                }
            }

            /**
             * The measpm method computes the volume of a single prism section
             * of the full interior polyhedron.
             */
            public double measpm(int ifn) {
                double pav[][] = new double[3][3];
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double vect3[] = new double[3];
                double height = 0.0;
                for (int ke = 0; ke < 3; ke++) {
                    int ien = fnen[ke][ifn];
                    iv = env[0][ien];
                    int ia = va[iv];
                    height += a[2][ia];
                    int ip = vp[iv];
                    for (int k = 0; k < 3; k++) {
                        pav[k][ke] = a[k][ia] - p[k][ip];
                    }
                }
                height *= 1 / 3.0;
                for (int k = 0; k < 3; k++) {
                    vect1[k] = pav[k][1] - pav[k][0];
                    vect2[k] = pav[k][2] - pav[k][0];
                }
                VectorMath.cross(vect1, vect2, vect3);
                return height * vect3[2] / 2.0;
            }

            public void measfp(int ifp, double av[]) {
                double ai[] = new double[3];
                double aj[] = new double[3];
                double ak[] = new double[3];
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double acvect[] = new double[3];
                double aavect[] = new double[3];
                double tanv[][][] = new double[3][2][MAXCYEP];
                double radial[][] = new double[3][MAXCYEP];
                double pcurve = 0.0;
                double gcurve = 0.0;
                int ia = fpa[ifp];
                int ncycle = fpncy[ifp];
                int ieuler;
                if (ncycle > 0) {
                    ieuler = 1 - ncycle;
                } else {
                    ieuler = 1;
                }
                for (int icyptr = 0; icyptr < ncycle; icyptr++) {
                    int icy = fpcy[icyptr][ifp];
                    int nedge = cynep[icy];
                    for (int ke = 0; ke < nedge; ke++) {
                        int iep = cyep[ke][icy];
                        int ic = epc[iep];
                        int it = ct[ic];
                        int ia2;
                        if (ia == ta[0][it]) {
                            ia2 = ta[1][it];
                        } else {
                            ia2 = ta[0][it];
                        }
                        for (int k = 0; k < 3; k++) {
                            acvect[k] = c[k][ic] - a[k][ia];
                            aavect[k] = a[k][ia2] - a[k][ia];
                        }
                        VectorMath.norm(aavect, aavect);
                        double dt = VectorMath.dot(acvect, aavect);
                        double geo = -dt / (radius[ia] * cr[ic]);
                        int iv1 = epv[0][iep];
                        int iv2 = epv[1][iep];
                        double angle;
                        if (iv1 == 0 || iv2 == 0) {
                            angle = 2.0 * PI;
                        } else {
                            for (int k = 0; k < 3; k++) {
                                vect1[k] = v[k][iv1] - c[k][ic];
                                vect2[k] = v[k][iv2] - c[k][ic];
                                radial[k][ke] = v[k][iv1] - a[k][ia];
                            }
                            getVector(ai, radial, ke);
                            VectorMath.norm(ai, ai);
                            getVector(aj, tanv, 0, ke);
                            VectorMath.cross(vect1, aavect, aj);
                            VectorMath.norm(aj, aj);
                            getVector(ak, tanv, 1, ke);
                            VectorMath.cross(vect2, aavect, ak);
                            VectorMath.norm(ak, ak);
                            angle = vecang(vect1, vect2, aavect, -1.0);
                        }
                        gcurve += cr[ic] * angle * geo;
                        if (nedge != 1 && ke > 0) {
                            getVector(ai, tanv, 1, ke - 1);
                            getVector(aj, tanv, 0, ke);
                            getVector(ak, radial, ke);
                            angle = vecang(ai, aj, ak, 1.0);
                            if (angle < 0.0) {
                                //logger.severe("Negative Angle in MEASFP");
                                throw new EnergyException("Negative Angle in MEASFP", true);
                            }
                            pcurve += angle;
                        }
                    }
                    if (nedge > 0) {
                        getVector(ai, tanv, 1, nedge);
                        getVector(aj, tanv, 0, 0);
                        getVector(ak, radial, 0);
                        double angle = vecang(ai, aj, ak, 1.0);
                        if (angle < 0.0) {
                            //logger.severe("Negative Angle in MEASFP");
                            throw new EnergyException("Negative Angle in MEASFP", true);
                        }
                        pcurve += angle;
                    }
                }
                double gauss = 2.0 * PI * ieuler - pcurve - gcurve;
                double areap = gauss * (radius[ia] * radius[ia]);
                double volp = areap * radius[ia] / 3.0;
                av[0] = areap;
                av[1] = volp;
            }

            public void measfs(int ifs, double saddle[]) {
                double areas = 0.0;
                double vols = 0.0;
                double areasp = 0.0;
                double volsp = 0.0;
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double aavect[] = new double[3];
                int iep = fsep[0][ifs];
                int ic = epc[iep];
                int it = ct[ic];
                int ia1 = ta[0][it];
                int ia2 = ta[1][it];
                for (int k = 0; k < 3; k++) {
                    aavect[k] = a[k][ia2] - a[k][ia1];
                }
                VectorMath.norm(aavect, aavect);
                int iv1 = epv[0][iep];
                int iv2 = epv[1][iep];
                double phi;
                if (iv1 == 0 || iv2 == 0) {
                    phi = 2.0 * PI;
                } else {
                    for (int k = 0; k < 3; k++) {
                        vect1[k] = v[k][iv1] - c[k][ic];
                        vect2[k] = v[k][iv2] - c[k][ic];
                    }
                    phi = vecang(vect1, vect2, aavect, 1.0);
                }
                for (int k = 0; k < 3; k++) {
                    vect1[k] = a[k][ia2] - t[k][it];
                    vect2[k] = a[k][ia2] - t[k][it];
                }
                double d1 = -1.0 * VectorMath.dot(vect1, aavect);
                double d2 = VectorMath.dot(vect2, aavect);
                theta1 = atan2(d1, tr[it]);
                theta2 = atan2(d2, tr[it]);

                /**
                 * Check for cusps.
                 */
                double thetaq;
                boolean cusp;
                if (tr[it] < probe && theta1 > 0.0 && theta2 > 0.0) {
                    cusp = true;
                    double rat = tr[it] / probe;
                    rat = check(rat);
                    thetaq = acos(rat);
                } else {
                    cusp = false;
                    thetaq = 0.0;
                    areasp = 0.0;
                    volsp = 0.0;
                }
                double term1 = tr[it] * probe * (theta1 + theta2);
                double term2 = (probe * probe) * (sin(theta1) + sin(theta2));
                areas = phi * (term1 - term2);
                if (cusp) {
                    double spin = tr[it] * probe * thetaq - probe * probe * sin(thetaq);
                    areasp = 2.0 * phi * spin;
                }

                iep = fsep[0][ifs];
                int ic2 = epc[iep];
                iep = fsep[1][ifs];
                int ic1 = epc[iep];
                if (ca[ic1] != ia1) {
                    //logger.severe("IA1 Inconsistency in MEASFS");
                    throw new EnergyException("IA1 Inconsistency in MEASFS", true);
                }
                for (int k = 0; k < 3; k++) {
                    vect1[k] = c[k][ic1] - a[k][ia1];
                    vect2[k] = c[k][ic2] - a[k][ia2];
                }
                double w1 = VectorMath.dot(vect1, aavect);
                double w2 = -1.0 * VectorMath.dot(vect2, aavect);
                double cone1 = phi * (w1 * cr[ic1] * (w1 * cr[ic1])) / 6.0;
                double cone2 = phi * (w2 * cr[ic2] * (w2 * cr[ic2])) / 6.0;
                term1 = (tr[it] * tr[it]) * probe * (sin(theta1) + sin(theta2));
                term2 = sin(theta1) * cos(theta1) + theta1 + sin(theta2) * cos(theta2) + theta2;
                term2 = tr[it] * (probe * probe) * term2;
                double term3 = sin(theta1) * cos(theta1) * cos(theta1) + 2.0 * sin(theta1)
                        + sin(theta2) * cos(theta2) * cos(theta2) + 2.0 * sin(theta2);
                term3 = (probe * probe * probe / 3.0) * term3;
                double volt = (phi / 2.0) * (term1 - term2 + term3);
                vols = volt + cone1 + cone2;
                if (cusp) {
                    term1 = (tr[it] * tr[it]) * probe * sin(thetaq);
                    term2 = sin(thetaq) * cos(thetaq) + thetaq;
                    term2 = tr[it] * (probe * probe) * term2;
                    term3 = sin(thetaq) * cos(thetaq) * cos(thetaq) + 2.0 * sin(thetaq);
                    term3 = (probe * probe * probe / 3.0) * term3;
                    volsp = phi * (term1 - term2 + term3);
                }
                saddle[0] = areas;
                saddle[1] = vols;
                saddle[2] = areasp;
                saddle[3] = volsp;
            }

            public void measfn(int ifn, double av[]) {
                double ai[] = new double[3];
                double aj[] = new double[3];
                double ak[] = new double[3];
                double angle[] = new double[3];
                double pvv[][] = new double[3][3];
                double pav[][] = new double[3][3];
                double planev[][] = new double[3][3];
                double arean = 0.0;
                double voln = 0.0;
                for (int ke = 0; ke < 3; ke++) {
                    int ien = fnen[ke][ifn];
                    int iv = env[0][ien];
                    int ia = va[iv];
                    int ip = vp[iv];
                    for (int k = 0; k < 3; k++) {
                        pvv[k][ke] = v[k][iv] - p[k][ip];
                        pav[k][ke] = a[k][ia] - p[k][ip];
                    }
                    if (probe > 0.0) {
                        getVector(ai, pvv, ke);
                        VectorMath.norm(ai, ai);
                    }
                }
                if (probe <= 0.0) {
                    arean = 0.0;
                } else {
                    for (int ke = 0; ke < 3; ke++) {
                        int je = ke + 1;
                        if (je > 2) {
                            je = 0;
                        }
                        getVector(ai, pvv, ke);
                        getVector(aj, pvv, je);
                        getVector(ak, planev, ke);
                        VectorMath.cross(ai, aj, ak);
                        VectorMath.norm(ak, ak);
                    }
                    for (int ke = 0; ke < 3; ke++) {
                        int je = ke - 1;
                        if (je < 0) {
                            je = 2;
                        }
                        getVector(ai, planev, je);
                        getVector(aj, planev, ke);
                        getVector(ak, pvv, ke);
                        angle[ke] = vecang(ai, aj, ak, -1.0);
                        if (angle[ke] < 0.0) {
                            //logger.severe("Negative Angle in MEASFN");
                            throw new EnergyException("Negative Angle in MEASFN", true);
                        }
                    }
                    double defect = 2.0 * PI - (angle[0] + angle[1] + angle[2]);
                    arean = (probe * probe) * defect;
                }
                getVector(ai, pav, 0);
                getVector(aj, pav, 1);
                getVector(ak, pav, 2);
                double simplx = -triple(ai, aj, ak) / 6.0;
                voln = simplx - arean * probe / 3.0;
                av[0] = arean;
                av[1] = voln;

            }

            /**
             * The vam method takes the analytical molecular surface defined as
             * a collection of spherical and toroidal polygons and uses it to
             * compute the volume and surface area
             */
            public void vam(double volume, double area) {
                final int maxdot = 1000;
                final int maxop = 100;
                final int nscale = 20;
                int ivs[] = new int[3];
                int ispind[] = new int[3];
                int ispnd2[] = new int[3];
                int ifnop[] = new int[maxop];
                int nlap[] = new int[nfn];
                int enfs[] = new int[5 * nAtoms];
                int fnt[][] = new int[3][nfn];
                int nspt[][] = new int[3][nfn];
                double cenop[][] = new double[3][maxop];
                double sdot[] = new double[3];
                double dotv[] = new double[nscale];
                double tau[] = new double[3];
                double ppm[] = new double[3];
                double xpnt1[] = new double[3];
                double xpnt2[] = new double[3];
                double qij[] = new double[3];
                double vects[][] = new double[3][3];
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double vect3[] = new double[3];
                double vect4[] = new double[3];
                double vect5[] = new double[3];
                double vect6[] = new double[3];
                double vect7[] = new double[3];
                double vect8[] = new double[3];
                double upp[] = new double[3];
                double thetaq[] = new double[3];
                double sigmaq[] = new double[3];
                double umq[] = new double[3];
                double upq[] = new double[3];
                double uc[] = new double[3];
                double uq[] = new double[3];
                double uij[] = new double[3];
                double ai[] = new double[3];
                double aj[] = new double[3];
                double ak[] = new double[3];
                double al[] = new double[3];
                double dots[][] = new double[3][maxdot];
                double tdots[][] = new double[3][maxdot];
                double atmarea[] = new double[nAtoms];
                double depths[] = new double[nfn];
                double cora[] = new double[nfn];
                double corv[] = new double[nfn];
                double alts[][] = new double[3][nfn];
                double fncen[][] = new double[3][nfn];
                double fnvect[][][] = new double[3][3][nfn];
                boolean ate[] = new boolean[maxop];
                boolean badav[] = new boolean[nfn];
                boolean badt[] = new boolean[nfn];
                boolean fcins[][] = new boolean[3][nfn];
                boolean fcint[][] = new boolean[3][nfn];
                boolean fntrev[][] = new boolean[3][nfn];

                /**
                 * Compute the volume of the interior polyhedron.
                 */
                double polyhedronVolume = 0.0;
                for (int ifn = 0; ifn < nfn; ifn++) {
                    polyhedronVolume += measpm(ifn);
                }

                /**
                 * Compute the area and volume due to convex faces as well as
                 * the area partitioned among the atoms.
                 */
                double totap = 0.0;
                double totvp = 0.0;
                fill(atmarea, 0.0);
                double convexFaces[] = { 0.0, 0.0 };
                for (int ifp = 0; ifp < nfp; ifp++) {
                    measfp(ifp, convexFaces);
                    int ia = fpa[ifp];
                    atmarea[ia] += convexFaces[0];
                    totap += convexFaces[0];
                    totvp += convexFaces[1];
                }

                /**
                 * Compute the area and volume due to saddle faces as well as
                 * the spindle correction value.
                 */
                double totas = 0.0;
                double totvs = 0.0;
                double totasp = 0.0;
                double totvsp = 0.0;
                double saddle[] = { 0.0, 0.0, 0.0, 0.0 };
                for (int ifs = 0; ifs < nfs; ifs++) {
                    for (int k = 0; k < 2; k++) {
                        int ien = fsen[k][ifs];
                        if (ien > 0) {
                            enfs[ien] = ifs;
                        }
                    }
                    measfs(ifs, saddle);
                    double areas = saddle[0];
                    double vols = saddle[1];
                    double areasp = saddle[2];
                    double volsp = saddle[3];
                    totas += areas;
                    totvs += vols;
                    totasp += areasp;
                    totvsp += volsp;
                    if (areas - areasp < 0.0) {
                        //logger.severe("Negative Area for Saddle Face");
                        throw new EnergyException("Negative Area for Saddle Face", true);
                    }
                }

                /**
                 * Compute the area and volume due to concave faces.
                 */
                double totan = 0.0;
                double totvn = 0.0;
                double concaveFaces[] = { 0.0, 0.0 };
                for (int ifn = 0; ifn < nfn; ifn++) {
                    measfn(ifn, concaveFaces);
                    double arean = concaveFaces[0];
                    double voln = concaveFaces[1];
                    totan += arean;
                    totvn += voln;
                }

                /**
                 * Compute the area and volume lens correction values.
                 */
                double alenst = 0.0;
                double alensn = 0.0;
                double vlenst = 0.0;
                double vlensn = 0.0;
                if (probe > 0.0) {
                    int ndots[] = { maxdot };
                    gendot(ndots, dots, probe, 0.0, 0.0, 0.0);
                    double dota = (4.0 * PI * probe * probe) / ndots[0];
                    for (int ifn = 0; ifn < nfn; ifn++) {
                        nlap[ifn] = 0;
                        cora[ifn] = 0.0;
                        corv[ifn] = 0.0;
                        badav[ifn] = false;
                        badt[ifn] = false;
                        for (int k = 0; k < 3; k++) {
                            nspt[k][ifn] = 0;
                        }
                        int ien = fnen[0][ifn];
                        iv = env[0][ien];
                        int ip = vp[iv];
                        getVector(ai, alts, ifn);
                        depths[ifn] = depth(ip, ai);
                        for (int k = 0; k < 3; k++) {
                            fncen[k][ifn] = p[k][ip];
                        }

                        // This assigned value was never used?
                        //int ia = va[iv];
                        /**
                         * Get vertices and vectors.
                         */
                        for (int ke = 0; ke < 3; ke++) {
                            ien = fnen[ke][ifn];
                            ivs[ke] = env[0][ien];
                            int ia = va[ivs[ke]];
                            int ifs = enfs[ien];
                            int iep = fsep[0][ifs];
                            int ic = epc[iep];
                            int it = ct[ic];
                            fnt[ke][ifn] = it;
                            fntrev[ke][ifn] = (ta[0][it] != ia);
                        }
                        for (int ke = 0; ke < 3; ke++) {
                            for (int k = 0; k < 3; k++) {
                                vects[k][ke] = v[k][ivs[ke]] - p[k][ip];
                            }
                        }
                        /**
                         * Calculate normal vectors for the three planes that
                         * cut out the geodesic triangle.
                         */
                        getVector(ai, vects, 0);
                        getVector(aj, vects, 1);
                        getVector(ak, fnvect, 0, ifn);
                        VectorMath.cross(ai, aj, ak);
                        VectorMath.norm(ak, ak);
                        getVector(ai, vects, 2);
                        getVector(ak, fnvect, 1, ifn);
                        VectorMath.cross(aj, ai, ak);
                        VectorMath.norm(ak, ak);
                        getVector(aj, vects, 0);
                        getVector(ak, fnvect, 2, ifn);
                        VectorMath.cross(ai, aj, ak);
                        VectorMath.norm(ak, ak);
                    }
                    for (int ifn = 0; ifn < nfn - 1; ifn++) {
                        for (int jfn = ifn + 1; jfn < nfn; jfn++) {
                            getVector(ai, fncen, ifn);
                            getVector(aj, fncen, jfn);
                            double dij2 = VectorMath.dist2(ai, aj);
                            if (dij2 > 4.0 * probe * probe) {
                                continue;
                            }
                            if (depths[ifn] > probe && depths[jfn] > probe) {
                                continue;
                            }
                            /**
                             * These two probes may have intersecting surfaces.
                             */
                            double dpp = VectorMath.dist(ai, aj);
                            /**
                             * Compute the midpoint.
                             */
                            for (int k = 0; k < 3; k++) {
                                ppm[k] = (fncen[k][ifn] + fncen[k][jfn]) / 2.0;
                                upp[k] = (fncen[k][jfn] - fncen[k][ifn]) / dpp;
                            }
                            double rm = probe * probe - (dpp / 2.0) * (dpp / 2.0);
                            if (rm < 0.0) {
                                rm = 0.0;
                            }
                            rm = sqrt(rm);
                            double rat = dpp / (2.0 * probe);
                            check(rat);
                            double rho = asin(rat);
                            /**
                             * Use circle-place intersection routine.
                             */
                            boolean alli = true;
                            boolean anyi = false;
                            boolean spindl = false;
                            for (int k = 0; k < 3; k++) {
                                ispind[k] = 0;
                                ispnd2[k] = 0;
                            }
                            for (int ke = 0; ke < 3; ke++) {
                                thetaq[ke] = 0.0;
                                sigmaq[ke] = 0.0;
                                tau[ke] = 0.0;
                                getVector(ai, fncen, ifn);
                                getVector(aj, fnvect, ke, ifn);
                                cirpln(ppm, rm, upp, ai, aj, cintp, cinsp, xpnt1, xpnt2);
                                fcins[ke][ifn] = cinsp;
                                fcint[ke][ifn] = cintp;
                                if (!cinsp) {
                                    alli = false;
                                }
                                if (cintp) {
                                    anyi = true;
                                }
                                if (!cintp) {
                                    continue;
                                }
                                int it = fnt[ke][ifn];
                                if (tr[it] > probe) {
                                    continue;
                                }
                                for (int ke2 = 0; ke2 < 3; ke2++) {
                                    if (it == fnt[ke2][jfn]) {
                                        ispind[ke] = it;
                                        nspt[ke][ifn]++;
                                        ispnd2[ke2] = it;
                                        nspt[ke][jfn]++;
                                        spindl = true;
                                    }
                                }
                                if (ispind[ke] == 0) {
                                    continue;
                                }

                                /**
                                 * Check that the two ways of calculating
                                 * intersection points match.
                                 */
                                rat = tr[it] / probe;
                                check(rat);
                                thetaq[ke] = acos(rat);
                                double stq = sin(thetaq[ke]);
                                if (fntrev[ke][ifn]) {
                                    for (int k = 0; k < 3; k++) {
                                        uij[k] = -tax[k][it];
                                    }
                                } else {
                                    for (int k = 0; k < 3; k++) {
                                        uij[k] = tax[k][it];
                                    }
                                }
                                for (int k = 0; k < 3; k++) {
                                    qij[k] = t[k][it] - stq * probe * uij[k];
                                    //qji[k] = t[k][it] + stq * probe * uij[k];
                                }
                                for (int k = 0; k < 3; k++) {
                                    umq[k] = (qij[k] - ppm[k]) / rm;
                                    upq[k] = (qij[k] - fncen[k][ifn]) / probe;
                                }
                                VectorMath.cross(uij, upp, vect1);
                                double dt = VectorMath.dot(umq, vect1);
                                check(dt);
                                sigmaq[ke] = acos(dt);
                                getVector(ai, fnvect, ke, ifn);
                                VectorMath.cross(upq, ai, vect1);
                                VectorMath.norm(vect1, uc);
                                VectorMath.cross(upp, upq, vect1);
                                VectorMath.norm(vect1, uq);
                                dt = VectorMath.dot(uc, uq);
                                check(dt);
                                tau[ke] = PI - acos(dt);
                            }
                            boolean allj = true;
                            boolean anyj = false;
                            for (int ke = 0; ke < 3; ke++) {
                                getVector(ai, fncen, jfn);
                                getVector(aj, fnvect, ke, jfn);
                                cirpln(ppm, rm, upp, ai, aj, cinsp, cintp, xpnt1, xpnt2);
                                fcins[ke][jfn] = cinsp;
                                fcint[ke][jfn] = cintp;
                                if (!cinsp) {
                                    allj = false;
                                }
                                if (cintp) {
                                    anyj = true;
                                }
                            }
                            boolean case1 = (alli && allj && !anyi && !anyj);
                            boolean case2 = (anyi && anyj && spindl);
                            if (!case1 && !case2) {
                                continue;
                            }
                            /**
                             * This kind of overlap can be handled.
                             */
                            nlap[ifn]++;
                            nlap[jfn]++;
                            for (int ke = 0; ke < 3; ke++) {
                                int ien = fnen[ke][ifn];
                                int iv1 = env[0][ien];
                                int iv2 = env[1][ien];
                                for (int k = 0; k < 3; k++) {
                                    vect3[k] = v[k][iv1] - fncen[k][ifn];
                                    vect4[k] = v[k][iv2] - fncen[k][ifn];
                                }
                                for (int ke2 = 0; ke2 < 3; ke2++) {
                                    if (ispind[ke] == ispnd2[ke2] || ispind[ke] == 0) {
                                        continue;
                                    }
                                    getVector(ai, fncen, ifn);
                                    getVector(aj, fnvect, ke, ifn);
                                    getVector(ak, fncen, jfn);
                                    getVector(al, fnvect, ke2, jfn);
                                    cirpln(ai, probe, aj, ak, al, cinsp, cintp, xpnt1, xpnt2);
                                    if (!cintp) {
                                        continue;
                                    }
                                    ien = fnen[ke2][jfn];
                                    iv1 = env[0][ien];
                                    iv2 = env[1][ien];
                                    for (int k = 0; k < 3; k++) {
                                        vect7[k] = v[k][iv1] - fncen[k][jfn];
                                        vect8[k] = v[k][iv2] - fncen[k][jfn];
                                    }
                                    /**
                                     * Check whether point lies on spindle arc.
                                     */
                                    for (int k = 0; k < 3; k++) {
                                        vect1[k] = xpnt1[k] - fncen[k][ifn];
                                        vect2[k] = xpnt2[k] - fncen[k][ifn];
                                        vect5[k] = xpnt1[k] - fncen[k][jfn];
                                        vect6[k] = xpnt2[k] - fncen[k][jfn];
                                    }
                                    /**
                                     * Continue to next if statement if any of
                                     * the following are true.
                                     */
                                    getVector(ai, fnvect, ke, ifn);
                                    getVector(aj, fnvect, ke2, jfn);
                                    if (triple(vect3, vect1, ai) < 0.0 || triple(vect1, vect4, ai) < 0.0
                                            || triple(vect7, vect5, aj) < 0.0 || triple(vect5, vect8, aj) < 0.0) {
                                        if (!((triple(vect3, vect2, ai) < 0.0 || triple(vect2, vect4, ai) < 0.0
                                                || triple(vect7, vect6, aj) < 0.0
                                                || triple(vect6, vect8, aj) < 0.0))) {
                                            badav[ifn] = true;
                                        }
                                    } else {
                                        badav[ifn] = true;
                                    }
                                }
                            }

                            for (int ke = 0; ke < 3; ke++) {
                                int ien = fnen[ke][ifn];
                                int iv1 = env[0][ien];
                                int iv2 = env[1][ien];
                                for (int k = 0; k < 3; k++) {
                                    vect3[k] = v[k][iv1] - fncen[k][ifn];
                                    vect4[k] = v[k][iv2] - fncen[k][ifn];
                                }
                                for (int ke2 = 0; ke2 < 3; ke2++) {
                                    if (ispind[ke] == ispnd2[ke2] || ispnd2[ke2] == 0) {
                                        continue;
                                    }
                                    getVector(ai, fncen, ifn);
                                    getVector(aj, fnvect, ke, ifn);
                                    getVector(ak, fncen, jfn);
                                    getVector(al, fnvect, ke2, jfn);
                                    cirpln(ak, probe, al, ai, aj, cinsp, cintp, xpnt1, xpnt2);
                                    if (!cintp) {
                                        continue;
                                    }
                                    ien = fnen[ke2][jfn];
                                    iv1 = env[0][ien];
                                    iv2 = env[1][ien];
                                    for (int k = 0; k < 3; k++) {
                                        vect7[k] = v[k][iv1] - fncen[k][jfn];
                                        vect8[k] = v[k][iv2] - fncen[k][jfn];
                                    }
                                    /**
                                     * Check whether point lies on spindle arc.
                                     */
                                    for (int k = 0; k < 3; k++) {
                                        vect1[k] = xpnt1[k] - fncen[k][ifn];
                                        vect2[k] = xpnt2[k] - fncen[k][ifn];
                                        vect5[k] = xpnt1[k] - fncen[k][jfn];
                                        vect6[k] = xpnt2[k] - fncen[k][jfn];
                                    }
                                    /**
                                     * Continue to next if statement if any of
                                     * the following are true.
                                     */
                                    getVector(ai, fnvect, ke, ifn);
                                    getVector(aj, fnvect, ke2, jfn);
                                    if (triple(vect3, vect1, ai) < 0.0 || triple(vect1, vect4, ai) < 0.0
                                            || triple(vect7, vect5, aj) < 0.0 || triple(vect5, vect8, aj) < 0.0) {
                                        if (!(triple(vect3, vect2, ai) < 0.0 || triple(vect2, vect4, ai) < 0.0
                                                || triple(vect7, vect6, aj) < 0.0
                                                || triple(vect6, vect8, aj) < 0.0)) {
                                            badav[jfn] = true;
                                        }
                                    } else {
                                        badav[jfn] = true;
                                    }
                                }

                                double sumlam = 0.0;
                                double sumsig = 0.0;
                                double sumsc = 0.0;
                                for (int k = 0; k < 3; k++) {
                                    if (ispind[ke] != 0) {
                                        sumlam += PI - tau[ke];
                                        sumsig += sigmaq[ke] - PI;
                                        sumsc += sin(sigmaq[ke]) * cos(sigmaq[ke]);
                                    }
                                }
                                double alens = 2.0 * probe * probe * (PI - sumlam - sin(rho) * (PI + sumsig));
                                double vint = alens * probe / 3.0;
                                double vcone = probe * rm * rm * sin(rho) * (PI + sumsig) / 3.0;
                                double vpyr = probe * rm * rm * sin(rho) * sumsc / 3.0;
                                double vlens = vint - vcone + vpyr;
                                cora[ifn] += alens;
                                cora[jfn] += alens;
                                corv[ifn] += vlens;
                                corv[jfn] += vlens;
                            }

                            /**
                             * Check for vertex on opposing probe in face.
                             */
                            /**
                             * For (int kv = 0; kv < 3; kv++) { vip[kv] = false;
                             * int ien = fnen[kv][jfn]; iv = env[0][ien]; for
                             * (int k = 0; k < 3; k++) { vect1[k] = v[k][iv] -
                             * fncen[k][ifn]; } VectorMath.norm(vect1, vect1);
                             * for (int ke = 0; ke < 3; ke++) { getVector(ai,
                             * fnvect, ke, ifn); getVector(aj, v, iv); double dt
                             * = VectorMath.dot(ai, aj); if (dt > 0.0) { move =
                             * true; break; } } if (!move) { vip[kv] = true; }
                             * move = false; }
                             */
                        }
                    }
                    for (int ifn = 0; ifn < nfn; ifn++) {
                        for (int ke = 0; ke < 3; ke++) {
                            if (nspt[ke][ifn] > 1) {
                                badt[ifn] = true;
                            }
                        }
                    }
                    for (int ifn = 0; ifn < nfn; ifn++) {
                        if (nlap[ifn] <= 0) {
                            continue;
                        }
                        /**
                         * Gather all overlapping probes.
                         */
                        int nop = 0;
                        for (int jfn = 0; jfn < nfn; jfn++) {
                            if (ifn != jfn) {
                                getVector(ai, fncen, ifn);
                                getVector(aj, fncen, jfn);
                                double dij2 = VectorMath.dist2(ai, aj);
                                if (dij2 <= 4.0 * probe * probe) {
                                    if (depths[jfn] <= probe) {
                                        nop++;
                                        if (nop > maxop) {
                                            //logger.severe("NOP Overflow in VAM");
                                            throw new EnergyException("NOP Overflow in VAM", false);
                                        }
                                        ifnop[nop] = jfn;
                                        for (int k = 0; k < 3; k++) {
                                            cenop[k][nop] = fncen[k][jfn];
                                        }
                                    }
                                }
                            }
                            /**
                             * Numerical calculation of the correction.
                             */
                            double areado = 0.0;
                            double voldo = 0.0;
                            double scinc = 1.0 / nscale;
                            for (int isc = 0; isc < nscale; isc++) {
                                double rsc = isc - 0.5;
                                dotv[isc] = probe * dota * rsc * rsc * scinc * scinc * scinc;
                            }
                            for (int iop = 0; iop < nop; iop++) {
                                ate[iop] = false;
                            }
                            int neatmx = 0;
                            for (int idot = 0; idot < ndots[0]; idot++) {
                                boolean move = false;
                                for (int ke = 0; ke < 3; ke++) {
                                    getVector(ai, fnvect, ke, ifn);
                                    getVector(aj, dots, idot);
                                    double dt = VectorMath.dot(ai, aj);
                                    if (dt > 0.0) {
                                        move = true;
                                        break;
                                    }
                                }
                                if (move) {
                                    continue;
                                }
                                for (int k = 0; k < 3; k++) {
                                    tdots[k][idot] = fncen[k][ifn] + dots[k][idot];
                                }
                                for (int iop = 0; iop < nop; iop++) {
                                    jfn = ifnop[iop];
                                    getVector(ai, dots, idot);
                                    getVector(aj, fncen, jfn);
                                    double ds2 = VectorMath.dist2(ai, aj);
                                    if (ds2 > probe * probe) {
                                        areado += dota;
                                        break;
                                    }
                                }
                                for (int isc = 0; isc < nscale; isc++) {
                                    double rsc = isc - 0.5;
                                    for (int k = 0; k < 3; k++) {
                                        sdot[k] = fncen[k][ifn] + rsc * scinc * dots[k][idot];
                                    }
                                    int neat = 0;
                                    for (int iop = 0; iop < nop; iop++) {
                                        jfn = ifnop[iop];
                                        getVector(ai, fncen, jfn);
                                        double ds2 = VectorMath.dist2(sdot, ai);
                                        if (ds2 > probe * probe) {
                                            for (int k = 0; k < 3; k++) {
                                                vect1[k] = sdot[k] - fncen[k][jfn];
                                            }
                                            for (int ke = 0; ke < 3; ke++) {
                                                getVector(ai, fnvect, ke, jfn);
                                                double dt = VectorMath.dot(ai, vect1);
                                                if (dt > 0.0) {
                                                    move = true;
                                                    break;
                                                }
                                            }
                                            if (move) {
                                                break;
                                            }
                                            neat++;
                                            ate[iop] = true;
                                        }
                                    }
                                    if (neat > neatmx) {
                                        neatmx = neat;
                                    }
                                    if (neat > 0) {
                                        voldo += dotv[isc] * (neat / (1.0 + neat));
                                    }
                                }
                            }
                            double coran = areado;
                            double corvn = voldo;
                            int nate = 0;
                            for (int iop = 0; iop < nop; iop++) {
                                if (ate[iop]) {
                                    nate++;
                                }
                            }
                            /**
                             * Use either the analytical or numerical
                             * correction.
                             */
                            boolean usenum = (nate > nlap[ifn] || neatmx > 1 || badt[ifn]);
                            if (usenum) {
                                cora[ifn] = coran;
                                corv[ifn] = corvn;
                                alensn += cora[ifn];
                                vlensn += corv[ifn];
                            } else if (badav[ifn]) {
                                corv[ifn] = corvn;
                                vlensn += corv[ifn];
                            }
                            alenst += cora[ifn];
                            vlenst += corv[ifn];
                        }
                    }
                }
                /**
                 * Finally, compute the total area and total volume.
                 */
                logger.info(String.format("totap=%16.8f,totas=%16.8f,totan=%16.8f,totasp=%16.8f,alenst=%16.8f",
                        totap, totas, totan, totasp, alenst));
                area = totap + totas + totan - totasp - alenst;
                logger.info(String.format(
                        "totvp=%16.8f,totvs=%16.8f,totvn=%16.8f,hedron=%16.8f,totvsp=%16.8f,vlenst=%16.8f", totvp,
                        totvs, totvn, polyhedronVolume, totvsp, vlenst));
                volume = totvp + totvs + totvn + polyhedronVolume - totvsp + vlenst;
                logger.info(String.format("volume=%16.8f area= %16.8f", volume, area));
            }

            /**
             * The gendot method finds the coordinates of a specified number of
             * surface points for a sphere with the input radius and coordinate
             * center.
             */
            public void gendot(int ndots[], double dots[][], double radius, double xcenter, double ycenter,
                    double zcenter) {
                int nequat = (int) sqrt(PI * (double) ndots[0]);
                int nvert = nequat / 2;
                if (nvert < 0) {
                    nvert = 0;
                }
                int k = 0;
                for (int i = -1; i < nvert; i++) {
                    double fi = (PI * (double) i) / (double) nvert;
                    double z = cos(fi);
                    double xy = sin(fi);
                    int nhoriz = (int) (nequat * xy);
                    if (nhoriz < 0) {
                        nhoriz = 0;
                    }
                    for (int j = -1; j < nhoriz - 1; j++) {
                        double fj = (2.0 * PI * (double) (j)) / (double) (nhoriz);
                        double x = cos(fj) * xy;
                        double y = sin(fj) * xy;
                        k++;
                        dots[0][k] = x * radius + xcenter;
                        dots[1][k] = y * radius + ycenter;
                        dots[2][k] = z * radius + zcenter;
                        if (k >= ndots[0]) {
                            ndots[0] = k;
                            return;
                        }
                    }
                }
                ndots[0] = k;
            }

            /**
             * The cirpln method determines the points of intersection between a
             * specified circle and plane.
             */
            public boolean cirpln(double circen[], double cirrad, double cirvec[], double plncen[], double plnvec[],
                    boolean cinsp, boolean cintp, double xpnt1[], double xpnt2[]) {
                double cpvect[] = new double[3];
                double pnt1[] = new double[3];
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double uvect1[] = new double[3];
                double uvect2[] = new double[3];
                for (int k = 0; k < 3; k++) {
                    cpvect[k] = plncen[k] - circen[k];
                }
                double dcp = VectorMath.dot(cpvect, plnvec);
                cinsp = (dcp > 0.0);
                VectorMath.cross(plnvec, cirvec, vect1);
                if (VectorMath.r(vect1) > 0.0) {
                    VectorMath.norm(vect1, uvect1);
                    VectorMath.cross(cirvec, uvect1, vect2);
                    if (VectorMath.r(vect2) > 0.0) {
                        VectorMath.norm(vect2, uvect2);
                        double dir = VectorMath.dot(uvect2, plnvec);
                        if (dir != 0.0) {
                            double ratio = dcp / dir;
                            if (abs(ratio) <= cirrad) {
                                for (int k = 0; k < 3; k++) {
                                    pnt1[k] = circen[k] + ratio * uvect2[k];
                                }
                                double rlen = cirrad * cirrad - ratio * ratio;
                                if (rlen < 0.0) {
                                    rlen = 0.0;
                                }
                                rlen = sqrt(rlen);
                                for (int k = 0; k < 3; k++) {
                                    xpnt1[k] = pnt1[k] - rlen * uvect1[k];
                                    xpnt2[k] = pnt1[k] + rlen * uvect1[k];
                                }
                                return true;
                            }
                        }
                    }
                }
                return false;
            }

            /**
             * The vecang method finds the angle between two vectors handed with
             * respect to a coordinate axis; returns an angle in the range
             * [0,2*PI].
             */
            public double vecang(double v1[], double v2[], double axis[], double hand) {
                double a1 = VectorMath.r(v1);
                double a2 = VectorMath.r(v2);
                double dt = VectorMath.dot(v1, v2);
                double a12 = a1 * a2;
                if (abs(a12) != 0.0) {
                    dt = dt / a12;
                }
                dt = check(dt);
                double angle = acos(dt);
                double vecang;
                if (hand * triple(v1, v2, axis) < 0.0) {
                    vecang = 2.0 * PI - angle;
                } else {
                    vecang = angle;
                }
                return vecang;
            }

            public double depth(int ip, double alt[]) {
                double vect1[] = new double[3];
                double vect2[] = new double[3];
                double vect3[] = new double[3];
                double vect4[] = new double[3];
                int ia1 = pa[0][ip];
                int ia2 = pa[1][ip];
                int ia3 = pa[2][ip];
                for (int k = 0; k < 3; k++) {
                    vect1[k] = a[k][ia1] - a[k][ia3];
                    vect2[k] = a[k][ia2] - a[k][ia3];
                    vect3[k] = p[k][ip] - a[k][ia3];
                }
                VectorMath.cross(vect1, vect2, vect4);
                VectorMath.norm(vect4, vect4);
                double dot = VectorMath.dot(vect4, vect3);
                for (int k = 0; k < 3; k++) {
                    alt[k] = vect4[k];
                }
                return dot;
            }

            public double check(double angle) {
                if (angle > 1.0) {
                    angle = 1.0;
                } else if (angle < -1.0) {
                    angle = -1.0;
                }
                return angle;
            }

            public void calcDerivative(int lb, int ub) {
                /**
                 * Fix the step size in the z-direction; this value sets the
                 * accuracy of the numerical derivatives; zstep=0.06 is a good
                 * balance between compute time and accuracy.
                 */
                zstep = 0.0601;
                /**
                 * Load the cubes based on coarse lattice; first of all set edge
                 * length to the maximum diameter of any atom.
                 */
                edge = 2.0 * rmax;
                nx = (int) ((xmax - xmin) / edge);
                ny = (int) ((ymax - ymin) / edge);
                nz = (int) ((zmax - zmin) / edge);
                if (max(max(nx, ny), nz) > mxcube) {
                    //logger.severe(" VOLUME1  --  Increase the Value of MAXCUBE");
                    throw new EnergyException(" VOLUME1  --  Increase the Value of MAXCUBE", false);
                }
                /**
                 * Initialize the coarse lattice of cubes.
                 */
                for (int i = 0; i <= nx; i++) {
                    for (int j = 0; j <= ny; j++) {
                        for (int k = 0; k <= nz; k++) {
                            cube[0][i][j][k] = 0;
                            cube[1][i][j][k] = -1;
                        }
                    }
                }
                /**
                 * Find the number of atoms in each cube.
                 */
                for (int m = 0; m < nAtoms; m++) {
                    if (!skip[m]) {
                        int i = (int) ((x[m] - xmin) / edge);
                        int j = (int) ((y[m] - ymin) / edge);
                        int k = (int) ((z[m] - zmin) / edge);
                        cube[0][i][j][k]++;
                    }
                }

                /**
                 * Determine the highest index in the array "itab" for the atoms
                 * that fall into each cube; the first cube that has atoms
                 * defines the first index for "itab"; the final index for the
                 * atoms in the present cube is the final index of the last cube
                 * plus the number of atoms in the present cube.
                 */
                isum = 0;
                for (int i = 0; i <= nx; i++) {
                    for (int j = 0; j <= ny; j++) {
                        for (int k = 0; k <= nz; k++) {
                            tcube = cube[0][i][j][k];
                            if (tcube != 0) {
                                isum += tcube;
                                cube[1][i][j][k] = isum - 1;
                            }
                        }
                    }
                }

                /**
                 * "cube(1,,,)" now contains a pointer to the array "itab"
                 * giving the position of the last entry for the list of atoms
                 * in that cube of total number equal to "cube(0,,,)".
                 */
                for (int m = 0; m < nAtoms; m++) {
                    if (!skip[m]) {
                        int i = (int) ((x[m] - xmin) / edge);
                        int j = (int) ((y[m] - ymin) / edge);
                        int k = (int) ((z[m] - zmin) / edge);
                        tcube = cube[1][i][j][k];
                        itab[tcube] = m;
                        cube[1][i][j][k]--;
                    }
                }

                /**
                 * Set "cube(1,,,)" to be the starting index in "itab" for atom
                 * list of that cube; and "cube(0,,,)" to be the stop index.
                 */
                isum = 0;
                for (int i = 0; i <= nx; i++) {
                    for (int j = 0; j <= ny; j++) {
                        for (int k = 0; k <= nz; k++) {
                            tcube = cube[0][i][j][k];
                            //logger.info(String.format(" TCUBE %d %d %d %d", i, j, k, tcube));
                            if (tcube != 0) {
                                isum += tcube;
                                cube[0][i][j][k] = isum - 1;
                                cube[1][i][j][k]++;
                            }
                        }
                    }
                }

                /**
                 * Process in turn each atom from the coordinate list; first
                 * select the potential intersecting atoms.
                 */
                for (ir = 0; ir < nAtoms; ir++) {
                    pre_dx = 0.0;
                    pre_dy = 0.0;
                    pre_dz = 0.0;
                    if (skip[ir]) {
                        continue;
                    }
                    rr = vdwrad[ir];
                    rrx2 = 2.0 * rr;
                    rrsq = rr * rr;
                    xr = x[ir];
                    yr = y[ir];
                    zr = z[ir];
                    /**
                     * Find cubes to search for overlaps for current atom.
                     */
                    istart = (int) ((xr - xmin) / edge);
                    istop = min(istart + 2, nx + 1);
                    istart = max(istart, 1);
                    jstart = (int) ((yr - ymin) / edge);
                    jstop = min(jstart + 2, ny + 1);
                    jstart = max(jstart, 1);
                    kstart = (int) ((zr - zmin) / edge);
                    kstop = min(kstart + 2, nz + 1);
                    kstart = max(kstart, 1);
                    /**
                     * Load all overlapping atoms into "inov".
                     */
                    io = -1;
                    //logger.info(String.format(" %d %d %d %d %d %d %d ", ir, istart, istop, jstart, jstop, kstart, kstop));
                    for (int i = istart - 1; i < istop; i++) {
                        for (int j = jstart - 1; j < jstop; j++) {
                            for (int k = kstart - 1; k < kstop; k++) {
                                mstart = cube[1][i][j][k];
                                if (mstart != -1) {
                                    mstop = cube[0][i][j][k];
                                    for (int m = mstart; m <= mstop; m++) {
                                        in = itab[m];
                                        //logger.info(String.format(" CHECK %d %d", ir, in));
                                        if (in != ir) {
                                            io++;
                                            if (io > MAXARC) {
                                                //logger.severe(" VOLUME1  --  Increase the Value of MAXARC");
                                            }
                                            dx[io] = x[in] - xr;
                                            dy[io] = y[in] - yr;
                                            dsq[io] = (dx[io] * dx[io]) + (dy[io] * dy[io]);
                                            dist2 = dsq[io] + ((z[in] - zr) * (z[in] - zr));
                                            vdwsum = (rr + vdwrad[in]) * (rr + vdwrad[in]);
                                            if (dist2 > vdwsum || dist2 == 0.0) {
                                                io--;
                                            } else {
                                                d[io] = sqrt(dsq[io]);
                                                inov[io] = in;
                                                //logger.info(String.format(" INIT %d %d %d %16.8f", ir, io, in, d[io]));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    //logger.info(String.format("ir %d io %d", ir, io));
                    /**
                     * Determine resolution along the z-axis.
                     */
                    if (io != -1) {
                        ztop = zr + rr;
                        ztopshave = ztop - zstep;
                        zgrid = zr - rr;
                        /**
                         * Half of the part not covered by the planes.
                         */
                        zgrid += 0.5 * (rrx2 - ((int) (rrx2 / zstep) * zstep));
                        zstart = zgrid;
                        /**
                         * Section atom spheres perpendicular to the z-axis.
                         */
                        while (zgrid <= ztop) {
                            /**
                             * "rsecr" is radius of circle of intersection of
                             * "ir" sphere on the current sphere.
                             */
                            rsec2r = rrsq - ((zgrid - zr) * (zgrid - zr));
                            if (rsec2r < 0.0) {
                                rsec2r = 0.000001;
                            }
                            rsecr = sqrt(rsec2r);
                            if (zgrid >= ztopshave) {
                                cos_phi1 = 1.0;
                                phi1 = 0.0;
                            } else {
                                cos_phi1 = (zgrid + (0.5 * zstep) - zr) / rr;
                                phi1 = acos(cos_phi1);
                            }
                            if (zgrid == zstart) {
                                cos_phi2 = -1.0;
                                phi2 = PI;
                            } else {
                                cos_phi2 = (zgrid - (0.5 * zstep) - zr) / rr;
                                phi2 = acos(cos_phi2);
                            }
                            /**
                             * Check intersection of neighbor cirlces.
                             */
                            narc = -1;
                            for (int k = 0; k <= io; k++) {
                                in = inov[k];
                                rinsq = vdwrad[in] * vdwrad[in];
                                rsec2n = rinsq - ((zgrid - z[in]) * (zgrid - z[in]));
                                //logger.info(String.format(" NARC %d %d %16.8f %16.8f %16.8f", ir, k, rinsq, z[in], zgrid));
                                if (rsec2n > 0.0) {
                                    rsecn = sqrt(rsec2n);
                                    if (d[k] < (rsecr + rsecn)) {
                                        rdiff = rsecr - rsecn;
                                        //logger.info(String.format(" DIFF %d %d %16.8f %16.8f %16.8f", ir, k, d[k], rsecr, rsecn));
                                        if (d[k] <= abs(rdiff)) {
                                            if (rdiff < 0.0) {
                                                narc = 0;
                                                arci[narc] = 0.0;
                                                arcf[narc] = pix2;
                                            }
                                            //logger.info(String.format("%d Continue", ir));
                                            continue;
                                        }
                                        narc++;
                                        if (narc > MAXARC) {
                                            //logger.info("VOLUME1 -- Increase the Value of MAXARC");
                                        }
                                        /**
                                         * Initial and final arc endpoints are
                                         * found for intersection of "ir" circle
                                         * with another circle contained in same
                                         * plane; the initial endpoint of the
                                         * enclosed arc is stored in "arci", the
                                         * final endpoint in "arcf"; get
                                         * "cosine" via law of cosines.
                                         */
                                        cosine = (dsq[k] + rsec2r - rsec2n) / (2.0 * d[k] * rsecr);
                                        cosine = min(1.0, max(-1.0, cosine));
                                        /**
                                         * "alpha" is the angle between a line
                                         * containing either point of
                                         * intersection and the reference circle
                                         * center and the line containing both
                                         * circle centers; "beta" is the angle
                                         * between the line containing both
                                         * circle centers and x-axis.
                                         */
                                        alpha = acos(cosine);
                                        beta = atan2(dy[k], dx[k]);
                                        if (dy[k] < 0.0) {
                                            beta += pix2;
                                        }
                                        ti = beta - alpha;
                                        tf = beta + alpha;
                                        if (ti < 0.0) {
                                            ti += pix2;
                                        }
                                        if (tf > pix2) {
                                            tf -= pix2;
                                        }
                                        arci[narc] = ti;
                                        /**
                                         * If the arc crosses zero, then it is
                                         * broken into two segments; the first
                                         * ends at two pi and the second begins
                                         * at zero.
                                         */
                                        if (tf < ti) {
                                            arcf[narc] = pix2;
                                            narc++;
                                            arci[narc] = 0.0;
                                            //logger.info(String.format("ir= %d narc= %d arci= %16.8f", ir, narc, arci[narc]));
                                        }
                                        arcf[narc] = tf;
                                        //logger.info(String.format(" ARCF %d %d %16.8f %16.8f", ir, narc, tf, ti));
                                        // BELOW HERE
                                    }
                                }
                            }

                            /**
                             * Find the pre-area and pre-forces on this section
                             * (band), "pre-" means a multiplicative factor is
                             * yet to be applied.
                             */
                            if (narc == -1) {
                                //logger.info(String.format(" %d cos_phi %16.8f %16.8f %16.8f %16.8f",
                                //ir, phi1, cos_phi1, phi2, cos_phi2));
                                seg_dz = pix2 * ((cos_phi1 * cos_phi1) - (cos_phi2 * cos_phi2));
                                pre_dz += seg_dz;
                                //logger.info(String.format(" seg_dx %16.8f pre_dz %16.8f ", seg_dz, pre_dz));
                            } else {
                                /**
                                 * Sort the arc endpoint arrays, each with
                                 * "narc" entries, in order of increasing values
                                 * of the arguments in "arci".
                                 */
                                for (int k = 0; k < narc; k++) {
                                    aa = arci[k];
                                    bb = arcf[k];
                                    temp = 1000000.0;
                                    for (int i = k; i <= narc; i++) {
                                        if (arci[i] <= temp) {
                                            temp = arci[i];
                                            itemp = i;
                                        }
                                    }
                                    arci[k] = arci[itemp];
                                    arcf[k] = arcf[itemp];
                                    arci[itemp] = aa;
                                    arcf[itemp] = bb;
                                }
                                /**
                                 * Consolidate arcs by removing overlapping arc
                                 * endpoints.
                                 */
                                temp = arcf[0];
                                int j = 0;
                                for (int k = 1; k <= narc; k++) {
                                    if (temp < arci[k]) {
                                        arcf[j] = temp;
                                        j++;
                                        arci[j] = arci[k];
                                        temp = arcf[k];
                                    } else if (temp < arcf[k]) {
                                        temp = arcf[k];
                                    }
                                }
                                arcf[j] = temp;
                                narc = j;
                                if (narc == 0) {
                                    narc = 1;
                                    arcf[1] = pix2;
                                    arci[1] = arcf[0];
                                    arcf[0] = arci[0];
                                    arci[0] = 0.0;
                                } else {
                                    temp = arci[0];
                                    for (int k = 0; k < narc; k++) {
                                        arci[k] = arcf[k];
                                        arcf[k] = arci[k + 1];
                                    }

                                    if (temp == 0.0 && arcf[narc] == pix2) {
                                        narc--;
                                    } else {
                                        arci[narc] = arcf[narc];
                                        arcf[narc] = temp;
                                        // SOME PRINTS ARE WRONG AFTER FIRST ENTRY
                                        //logger.info(String.format(" ARCF1 %d %16.8f", ir, arcf[0]));
                                    }
                                }

                                // SOME OF THE FOLLOWING PRINTS ARE WRONG
                                //logger.info(String.format(" SORT %d %d %16.8f %16.8f", ir, narc, arci[0], arcf[0]));
                                /**
                                 * Compute the numerical pre-derivative values.
                                 */
                                for (int k = 0; k <= narc; k++) {
                                    theta1 = arci[k];
                                    theta2 = arcf[k];
                                    //logger.info(String.format("%d theta1=%16.8f theta2=%16.8f", ir, theta1, theta2));
                                    if (theta2 > theta1) {
                                        dtheta = theta2 - theta1;
                                    } else {
                                        dtheta = (theta2 + pix2) - theta1;
                                    }
                                    phi_term = phi2 - phi1 - 0.5 * (sin(2.0 * phi2) - sin(2.0 * phi1));
                                    seg_dx = (sin(theta2) - sin(theta1)) * phi_term;
                                    seg_dy = (cos(theta1) - cos(theta2)) * phi_term;
                                    seg_dz = dtheta * ((cos_phi1 * cos_phi1) - (cos_phi2 * cos_phi2));
                                    pre_dx += seg_dx;
                                    pre_dy += seg_dy;
                                    pre_dz += seg_dz;
                                    // SOME OF THE FOLLOWING PRINTS ARE WRONG
                                    //logger.info(String.format(" FINAL %d %16.8f %16.8f %16.8f", ir, seg_dx, seg_dy, seg_dz));
                                }
                                //logger.info(String.format(" LAST %d %16.8f %16.8f %16.8f", ir, pre_dx, pre_dy, pre_dz));
                            }
                            zgrid += zstep;
                        }
                    }
                    dex[0][ir] = 0.5 * rrsq * pre_dx;
                    dex[1][ir] = 0.5 * rrsq * pre_dy;
                    dex[2][ir] = 0.5 * rrsq * pre_dz;
                    //logger.info(String.format(" de/dx %d %16.8f %16.8f %16.8f", ir, dex[0][ir], dex[1][ir], dex[2][ir]));
                }
            }

            @Override
            public void run(int lb, int ub) {
                setRadius();
                calcVolume();
                calcDerivative(lb, ub);
            }
        }
    }

    /**
     * Some static constants.
     */
    private static final double THIRD = 1.0 / 3.0;
    private static final double PI4_3 = 4.0 / 3.0 * PI;
    private static final double PI_12 = PI / 12.0;

    private static enum RADII_MAP_TYPE {
        ATOMTYPE, BIOTYPE, NONE;
    }
}