View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential;
39  
40  import edu.rit.pj.IntegerForLoop;
41  import edu.rit.pj.IntegerSchedule;
42  import edu.rit.pj.ParallelRegion;
43  import edu.rit.pj.ParallelTeam;
44  import edu.rit.pj.reduction.SharedDouble;
45  import ffx.crystal.Crystal;
46  import ffx.crystal.CrystalPotential;
47  import ffx.crystal.LatticeSystem;
48  import ffx.crystal.NCSCrystal;
49  import ffx.crystal.ReplicatesCrystal;
50  import ffx.crystal.SpaceGroup;
51  import ffx.crystal.SpaceGroupDefinitions;
52  import ffx.crystal.SymOp;
53  import ffx.numerics.Constraint;
54  import ffx.numerics.atomic.AtomicDoubleArray.AtomicDoubleArrayImpl;
55  import ffx.numerics.atomic.AtomicDoubleArray3D;
56  import ffx.numerics.math.Double3;
57  import ffx.numerics.switching.ConstantSwitch;
58  import ffx.numerics.switching.UnivariateFunctionFactory;
59  import ffx.numerics.switching.UnivariateSwitchingFunction;
60  import ffx.potential.bonded.Angle;
61  import ffx.potential.bonded.AngleTorsion;
62  import ffx.potential.bonded.Atom;
63  import ffx.potential.bonded.Bond;
64  import ffx.potential.bonded.BondedTerm;
65  import ffx.potential.bonded.ImproperTorsion;
66  import ffx.potential.bonded.LambdaInterface;
67  import ffx.potential.bonded.MSNode;
68  import ffx.potential.bonded.MultiResidue;
69  import ffx.potential.bonded.OutOfPlaneBend;
70  import ffx.potential.bonded.PiOrbitalTorsion;
71  import ffx.potential.bonded.RelativeSolvation;
72  import ffx.potential.bonded.RelativeSolvation.SolvationLibrary;
73  import ffx.potential.bonded.Residue;
74  import ffx.potential.bonded.RestrainDistance;
75  import ffx.potential.bonded.RestraintTorsion;
76  import ffx.potential.bonded.StretchBend;
77  import ffx.potential.bonded.StretchTorsion;
78  import ffx.potential.bonded.Torsion;
79  import ffx.potential.bonded.TorsionTorsion;
80  import ffx.potential.bonded.UreyBradley;
81  import ffx.potential.constraint.CcmaConstraint;
82  import ffx.potential.constraint.SettleConstraint;
83  import ffx.potential.extended.ExtendedSystem;
84  import ffx.potential.nonbonded.COMRestraint;
85  import ffx.potential.nonbonded.GeneralizedKirkwood;
86  import ffx.potential.nonbonded.NCSRestraint;
87  import ffx.potential.nonbonded.ParticleMeshEwald;
88  import ffx.potential.nonbonded.RestrainGroups;
89  import ffx.potential.nonbonded.RestrainPosition;
90  import ffx.potential.nonbonded.VanDerWaals;
91  import ffx.potential.nonbonded.VanDerWaalsTornado;
92  import ffx.potential.openmm.OpenMMEnergy;
93  import ffx.potential.parameters.AngleType;
94  import ffx.potential.parameters.AngleType.AngleMode;
95  import ffx.potential.parameters.BondType;
96  import ffx.potential.parameters.ForceField;
97  import ffx.potential.parameters.ForceField.ELEC_FORM;
98  import ffx.potential.parameters.OutOfPlaneBendType;
99  import ffx.potential.parameters.TorsionType;
100 import ffx.potential.utils.ConvexHullOps;
101 import ffx.potential.utils.EnergyException;
102 import ffx.potential.utils.PotentialsFunctions;
103 import ffx.potential.utils.PotentialsUtils;
104 import ffx.utilities.FFXProperty;
105 import org.apache.commons.configuration2.CompositeConfiguration;
106 
107 import javax.annotation.Nullable;
108 import java.io.File;
109 import java.time.LocalDateTime;
110 import java.time.format.DateTimeFormatter;
111 import java.util.ArrayList;
112 import java.util.Arrays;
113 import java.util.Collections;
114 import java.util.HashSet;
115 import java.util.List;
116 import java.util.Set;
117 import java.util.logging.Level;
118 import java.util.logging.Logger;
119 import java.util.stream.Collectors;
120 import java.util.stream.Stream;
121 
122 import static edu.uiowa.jopenmm.OpenMMAmoebaLibrary.OpenMM_NmPerAngstrom;
123 import static ffx.potential.bonded.BondedTerm.removeNeuralNetworkTerms;
124 import static ffx.potential.nonbonded.RestrainPosition.parseRestrainPositions;
125 import static ffx.potential.nonbonded.VanDerWaalsForm.DEFAULT_VDW_CUTOFF;
126 import static ffx.potential.nonbonded.pme.EwaldParameters.DEFAULT_EWALD_CUTOFF;
127 import static ffx.potential.parameters.ForceField.toEnumForm;
128 import static ffx.potential.parsers.XYZFileFilter.isXYZ;
129 import static ffx.utilities.PropertyGroup.NonBondedCutoff;
130 import static ffx.utilities.PropertyGroup.PotentialFunctionSelection;
131 import static java.lang.Double.isInfinite;
132 import static java.lang.Double.isNaN;
133 import static java.lang.String.format;
134 import static java.lang.System.arraycopy;
135 import static java.util.Arrays.sort;
136 import static org.apache.commons.io.FilenameUtils.removeExtension;
137 import static org.apache.commons.math3.util.FastMath.PI;
138 import static org.apache.commons.math3.util.FastMath.max;
139 import static org.apache.commons.math3.util.FastMath.min;
140 import static org.apache.commons.math3.util.FastMath.sqrt;
141 
142 /**
143  * Compute the potential energy and derivatives of a molecular system described by a force field.
144  *
145  * @author Michael J. Schnieders
146  * @since 1.0
147  */
148 public class ForceFieldEnergy implements CrystalPotential, LambdaInterface {
149 
150   /**
151    * Default tolerance for numerical methods of solving constraints.
152    */
153   public static final double DEFAULT_CONSTRAINT_TOLERANCE = 1E-4;
154   /**
155    * A Logger for the ForceFieldEnergy class.
156    */
157   private static final Logger logger = Logger.getLogger(ForceFieldEnergy.class.getName());
158   /**
159    * Convert from nanoseconds to seconds.
160    */
161   private static final double toSeconds = 1.0e-9;
162   /**
163    * The MolecularAssembly associated with this force field energy.
164    */
165   protected final MolecularAssembly molecularAssembly;
166   /**
167    * If the absolute value of a gradient component is greater than "maxDebugGradient", verbose
168    * logging results.
169    */
170   public final double maxDebugGradient;
171   /**
172    * The Parallel Java ParallelTeam instance.
173    */
174   private final ParallelTeam parallelTeam;
175   /**
176    * An NCS restraint term.
177    */
178   private final NCSRestraint ncsRestraint;
179   /**
180    * A Center-of-Mass restraint term.
181    */
182   private final COMRestraint comRestraint;
183   /**
184    * Non-Bonded van der Waals energy.
185    */
186   private final VanDerWaals vanderWaals;
187   /**
188    * ANI2x Neutral Network Potential.
189    */
190   private final ANIEnergy aniEnergy;
191 
192   private final List<Constraint> constraints;
193 
194   @FFXProperty(name = "vdw-cutoff", propertyGroup = NonBondedCutoff, defaultValue = "12.0", description = """
195       Sets the cutoff distance value in Angstroms for van der Waals potential energy interactions.
196       The energy for any pair of van der Waals sites beyond the cutoff distance will be set to zero.
197       Other properties can be used to define the smoothing scheme near the cutoff distance.
198       The default cutoff distance in the absence of the vdw-cutoff keyword is infinite for nonperiodic
199       systems and 12.0 for periodic systems.
200       """)
201   private double vdwCutoff;
202 
203   @FFXProperty(name = "ewald-cutoff", propertyGroup = NonBondedCutoff, defaultValue = "7.0", description = """
204       Sets the value in Angstroms of the real-space distance cutoff for use during Ewald summation.
205       By default, in the absence of the ewald-cutoff property, a value of 7.0 is used.
206       """)
207   private double ewaldCutoff;
208 
209   @FFXProperty(name = "gk-cutoff", propertyGroup = NonBondedCutoff, defaultValue = "12.0", description = """
210       Sets the value in Angstroms of the generalized Kirkwood distance cutoff for use
211       during implicit solvent simulations. By default, in the absence of the gk-cutoff property,
212       no cutoff is used under aperiodic boundary conditions and the vdw-cutoff is used under PBC.
213       """)
214   private double gkCutoff;
215 
216   private static final double DEFAULT_LIST_BUFFER = 2.0;
217 
218   @FFXProperty(name = "list-buffer", propertyGroup = NonBondedCutoff, defaultValue = "2.0", description = """
219       Sets the size of the neighbor list buffer in Angstroms for potential energy functions.
220       This value is added to the actual cutoff distance to determine which pairs will be kept on the neighbor list.
221       This buffer value is used for all potential function neighbor lists.
222       The default value in the absence of the list-buffer keyword is 2.0 Angstroms.
223       """)
224   private double listBuffer;
225 
226   /**
227    * 2.0 times the neighbor list cutoff.
228    */
229   private final double cutOff2;
230   /**
231    * The non-bonded cut-off plus buffer distance (Angstroms).
232    */
233   private final double cutoffPlusBuffer;
234   /**
235    * Particle-Mesh Ewald electrostatic energy.
236    */
237   private final ParticleMeshEwald particleMeshEwald;
238   /**
239    * The Electrostatic functional form.
240    */
241   private final ELEC_FORM elecForm;
242   /**
243    * Original state of the neural network energy term flag.
244    */
245   private final boolean nnTermOrig;
246   /**
247    * Original state of the Bond energy term flag.
248    */
249   private final boolean bondTermOrig;
250   /**
251    * Original state of the Angle energy term flag.
252    */
253   private final boolean angleTermOrig;
254   /**
255    * Original state of the Stretch-Bend energy term flag.
256    */
257   private final boolean stretchBendTermOrig;
258   /**
259    * Original state of the Urey-Bradley energy term flag.
260    */
261   private final boolean ureyBradleyTermOrig;
262   /**
263    * Original state of the Out-of-Plane Bend energy term flag.
264    */
265   private final boolean outOfPlaneBendTermOrig;
266   /**
267    * Original state of the Torsion energy term flag.
268    */
269   private final boolean torsionTermOrig;
270   /**
271    * Original state of the Stretch-Torsion energy term flag.
272    */
273   private final boolean stretchTorsionTermOrig;
274   /**
275    * Original state of the Angle-Torsion energy term flag.
276    */
277   private final boolean angleTorsionTermOrig;
278   /**
279    * Original state of the Improper Torsion energy term flag.
280    */
281   private final boolean improperTorsionTermOrig;
282   /**
283    * Original state of the Pi-Orbital Torsion energy term flag.
284    */
285   private final boolean piOrbitalTorsionTermOrig;
286   /**
287    * Original state of the Torsion-Torsion energy term flag.
288    */
289   private final boolean torsionTorsionTermOrig;
290   /**
291    * Original state of the RestrainPosition term flag.
292    */
293   private final boolean restrainPositionTermOrig;
294   /**
295    * Original state of the RestrainGroup term flag.
296    */
297   private final boolean restrainGroupTermOrig;
298   /**
299    * Original state of the RestrainDistance term flag.
300    */
301   private final boolean restrainDistanceTermOrig;
302   /**
303    * Original state of the RestrainTorsion term flag.
304    */
305   private final boolean restrainTorsionTermOrig;
306 
307   /**
308    * Original state of the van der Waals energy term flag.
309    */
310   private final boolean vanderWaalsTermOrig;
311   /**
312    * Original state of the multipole energy term flag.
313    */
314   private final boolean multipoleTermOrig;
315   /**
316    * Original state of the polarization energy term flag.
317    */
318   private final boolean polarizationTermOrig;
319   /**
320    * Original state of the GK energy term flag.
321    */
322   private final boolean generalizedKirkwoodTermOrig;
323 
324   private boolean esvTermOrig;
325   /**
326    * Flag to indicate hydrogen bonded terms should be scaled up.
327    */
328   private final boolean rigidHydrogens;
329   /**
330    * Indicates application of lambda scaling to all Torsion based energy terms.
331    */
332   private final boolean lambdaTorsions;
333   /**
334    * Relative solvation term (TODO: needs further testing).
335    */
336   private final RelativeSolvation relativeSolvation;
337   private final boolean relativeSolvationTerm;
338   private final Platform platform = Platform.FFX;
339 
340   /**
341    * Indicates use of the Lambda state variable.
342    */
343   @FFXProperty(name = "lambdaterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
344       defaultValue = "false", description = "Specifies use of the Lambda state variable.")
345   protected boolean lambdaTerm;
346   /**
347    * Current value of the Lambda state variable.
348    */
349   private double lambda = 1.0;
350   /**
351    * Optimization scaling value to use for each degree of freedom.
352    */
353   protected double[] optimizationScaling = null;
354   /**
355    * Indicates only bonded energy terms effected by Lambda should be evaluated.
356    */
357   public boolean lambdaBondedTerms = false;
358   /**
359    * Indicates all bonded energy terms should be evaluated if lambdaBondedTerms is true.
360    */
361   boolean lambdaAllBondedTerms = false;
362 
363   /**
364    * Flag to indicate proper shutdown of the ForceFieldEnergy.
365    */
366   boolean destroyed = false;
367   /**
368    * An instance of the STATE enumeration to specify calculation of slowly varying energy terms, fast
369    * varying or both.
370    */
371   private STATE state = STATE.BOTH;
372   /**
373    * The array of Atoms being evaluated.
374    */
375   private Atom[] atoms;
376   /**
377    * An array of Bond terms.
378    */
379   private Bond[] bonds;
380   /**
381    * An array of Angle terms.
382    */
383   private Angle[] angles;
384   /**
385    * An array of Stretch-Bend terms.
386    */
387   private StretchBend[] stretchBends;
388   /**
389    * An array of Urey-Bradley terms.
390    */
391   private UreyBradley[] ureyBradleys;
392   /**
393    * An array of Out of Plane Bend terms.
394    */
395   private OutOfPlaneBend[] outOfPlaneBends;
396   /**
397    * An array of Torsion terms.
398    */
399   private Torsion[] torsions;
400   /**
401    * An array of Improper Torsion terms.
402    */
403   private ImproperTorsion[] improperTorsions;
404   /**
405    * An array of Pi-Orbital Torsion terms.
406    */
407   private PiOrbitalTorsion[] piOrbitalTorsions;
408   /**
409    * An array of Torsion-Torsion terms.
410    */
411   private TorsionTorsion[] torsionTorsions;
412   /**
413    * An array of Stretch-Torsion terms.
414    */
415   private StretchTorsion[] stretchTorsions;
416   /**
417    * An array of Angle-Torsion terms.
418    */
419   private AngleTorsion[] angleTorsions;
420   /**
421    * An array of Bond Restraint terms.
422    */
423   private RestrainDistance[] restrainDistances;
424   /**
425    * An array of Torsion Restraint terms.
426    */
427   private RestraintTorsion[] restrainTorsions;
428   /**
429    * An array of RestrainPosition terms.
430    */
431   private RestrainPosition[] restrainPositions;
432   /**
433    * Restrain groups
434    */
435   private final RestrainGroups restrainGroups;
436 
437   /**
438    * Number of atoms in the system.
439    */
440   private int nAtoms;
441   /**
442    * Number of bond terms in the system.
443    */
444   private int nBonds;
445   /**
446    * Number of angle terms in the system.
447    */
448   private int nAngles;
449   /**
450    * Number of stretch-bend terms in the system.
451    */
452   private int nStretchBends;
453   /**
454    * Number of Urey-Bradley terms in the system.
455    */
456   private int nUreyBradleys;
457   /**
458    * Number of Out of Plane Bend terms in the system.
459    */
460   private int nOutOfPlaneBends;
461   /**
462    * Number of Torsion terms in the system.
463    */
464   private int nTorsions;
465   /**
466    * Number of Improper Torsion terms in the system.
467    */
468   private int nImproperTorsions;
469   /**
470    * Number of Pi-Orbital Torsion terms in the system.
471    */
472   private int nPiOrbitalTorsions;
473   /**
474    * Number of Torsion-Torsion terms in the system.
475    */
476   private int nTorsionTorsions;
477   /**
478    * Number of Angle-Torsion terms in the system.
479    */
480   private int nAngleTorsions;
481   /**
482    * Number of Stretch-Torsion terms in the system.
483    */
484   private int nStretchTorsions;
485   /**
486    * Number of RestrainDistance terms in the system.
487    */
488   private int nRestrainDistances;
489   /**
490    * Number of RestraintTorsion terms in the system.
491    */
492   private int nRestrainTorsions;
493   /**
494    * Number of Restrain Positions in the system.
495    */
496   private int nRestrainPositions;
497   /**
498    * Number of Restrain Groups in the system.
499    */
500   private int nRestrainGroups;
501 
502   /**
503    * Number of van der Waals interactions evaluated.
504    */
505   private int nVanDerWaalInteractions;
506   /**
507    * Number of electrostatic interactions evaluated.
508    */
509   private int nPermanentInteractions;
510   /**
511    * Number of implicit solvent interactions evaluated.
512    */
513   private int nGKInteractions;
514 
515   /**
516    * The boundary conditions used when evaluating the force field energy.
517    */
518   private Crystal crystal;
519   /**
520    * A Parallel Java Region used to evaluate Bonded energy values.
521    */
522   private BondedRegion bondedRegion;
523 
524   /**
525    * Specifies use of an intramolecular neural network.
526    */
527   private boolean nnTerm;
528 
529   /**
530    * Specifies use of the bond stretch potential.
531    */
532   @FFXProperty(name = "bondterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
533       defaultValue = "true", description = "Specifies use of the bond stretch potential.")
534   private boolean bondTerm;
535 
536   /**
537    * Specifies use of the angle bend potential.
538    */
539   @FFXProperty(name = "angleterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
540       defaultValue = "true", description = "Specifies use of the angle bend potential.")
541   private boolean angleTerm;
542 
543   /**
544    * Evaluate Stretch-Bend energy terms.
545    */
546   @FFXProperty(name = "strbndterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
547       defaultValue = "true", description = "Specifies use of the stretch-bend potential.")
548   private boolean stretchBendTerm;
549 
550   /**
551    * Evaluate Urey-Bradley energy terms.
552    */
553   @FFXProperty(name = "ureyterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
554       defaultValue = "true", description = "Specifies use of the Urey-Bradley potential.")
555   private boolean ureyBradleyTerm;
556 
557   /**
558    * Evaluate Out of Plane Bend energy terms.
559    */
560   @FFXProperty(name = "opbendterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
561       defaultValue = "true", description = "Specifies use of the out-of-plane potential.")
562   private boolean outOfPlaneBendTerm;
563 
564   /**
565    * Evaluate Torsion energy terms.
566    */
567   @FFXProperty(name = "torsionterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
568       defaultValue = "true", description = "Specifies use of the torsional potential.")
569   private boolean torsionTerm;
570 
571   /**
572    * Evaluate Stretch-Torsion energy terms.
573    */
574   @FFXProperty(name = "strtorterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
575       defaultValue = "true", description = "Specifies use of the stretch-torsion potential.")
576   private boolean stretchTorsionTerm;
577 
578   /**
579    * Evaluate Angle-Torsion energy terms.
580    */
581   @FFXProperty(name = "angtorterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
582       defaultValue = "true", description = "Specifies use of the angle-torsion potential.")
583   private boolean angleTorsionTerm;
584 
585   /**
586    * Evaluate Improper Torsion energy terms.
587    */
588   @FFXProperty(name = "imptorterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
589       defaultValue = "true", description = "Specifies use of the improper torsion potential.")
590   private boolean improperTorsionTerm;
591 
592   /**
593    * Evaluate Pi-Orbital Torsion energy terms.
594    */
595   @FFXProperty(name = "pitorsterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
596       defaultValue = "true", description = "Specifies use of the pi-system torsion potential.")
597   private boolean piOrbitalTorsionTerm;
598 
599   /**
600    * Evaluate Torsion-Torsion energy terms.
601    */
602   @FFXProperty(name = "tortorterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
603       defaultValue = "true", description = "Specifies use of the pi-system torsion potential.")
604   private boolean torsionTorsionTerm;
605 
606   /**
607    * Evaluate van der Waals energy term.
608    */
609   @FFXProperty(name = "vdwterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
610       defaultValue = "true", description = """
611       Specifies use of the vdw der Waals potential.
612       If set to false, all non-bonded terms are turned off.
613       """)
614   private boolean vanderWaalsTerm;
615 
616   /**
617    * Evaluate permanent multipole electrostatics energy term.
618    */
619   @FFXProperty(name = "mpoleterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
620       defaultValue = "true", description = """
621       Specifies use of the fixed charge electrostatic potential.
622       Setting mpoleterm to false also turns off polarization and generalized Kirkwood,
623       overriding the polarizeterm and gkterm properties.
624       """)
625   private boolean multipoleTerm;
626 
627   /**
628    * Evaluate polarization energy term.
629    */
630   @FFXProperty(name = "polarizeterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
631       defaultValue = "true", description = """
632       Specifies use of the polarizable electrostatic potential.
633       Setting polarizeterm to false overrides the polarization property.
634       """)
635   private boolean polarizationTerm;
636 
637   /**
638    * Evaluate generalized Kirkwood energy term.
639    */
640   @FFXProperty(name = "gkterm", clazz = Boolean.class, propertyGroup = PotentialFunctionSelection,
641       defaultValue = "false", description = "Specifies use of generalized Kirkwood electrostatics.")
642   private boolean generalizedKirkwoodTerm;
643 
644   /**
645    * Evaluate COM energy term.
646    */
647   private boolean comTerm;
648   /**
649    * Original state of the COM energy term flag.
650    */
651   private boolean comTermOrig;
652   /**
653    * Evaluate NCS energy term.
654    */
655   private boolean ncsTerm;
656   /**
657    * Original state of the NCS energy term flag.
658    */
659   private boolean ncsTermOrig;
660   /**
661    * Evaluate RestrainDistance terms.
662    */
663   private boolean restrainDistanceTerm;
664   /**
665    * Evaluate RestrainTorsion terms.
666    */
667   private boolean restrainTorsionTerm;
668   /**
669    * Evaluate RestrainPosition term.
670    */
671   private boolean restrainPositionTerm;
672   /**
673    * Evaluate RestrainGroup terms.
674    */
675   private boolean restrainGroupTerm;
676 
677   /**
678    * Scale factor for increasing the strength of bonded terms involving hydrogen atoms.
679    */
680   private double rigidScale;
681 
682   /**
683    * The total neutral network energy.
684    */
685   private double nnEnergy;
686   /**
687    * The total Bond term energy.
688    */
689   private double bondEnergy;
690   /**
691    * The RMSD of all Bond distances relative to their equilibrium values.
692    */
693   private double bondRMSD;
694   /**
695    * The total Angle term energy.
696    */
697   private double angleEnergy;
698   /**
699    * The RMSD of all Angle bends relative to their equilibrium values.
700    */
701   private double angleRMSD;
702   /**
703    * The total Stretch-Bend term energy.
704    */
705   private double stretchBendEnergy;
706   /**
707    * The total Urey-Bradley term energy.
708    */
709   private double ureyBradleyEnergy;
710   /**
711    * The total Out-of-Plane term energy.
712    */
713   private double outOfPlaneBendEnergy;
714   /**
715    * The total Torsion term energy.
716    */
717   private double torsionEnergy;
718   /**
719    * The total Stretch-Torsion term energy.
720    */
721   private double stretchTorsionEnergy;
722   /**
723    * The total Angle-Torsion term energy.
724    */
725   private double angleTorsionEnergy;
726   /**
727    * The total Improper Torsion term energy.
728    */
729   private double improperTorsionEnergy;
730   /**
731    * The total Pi-Orbital Torsion term energy.
732    */
733   private double piOrbitalTorsionEnergy;
734   /**
735    * The total Torsion-Torsion term energy.
736    */
737   private double torsionTorsionEnergy;
738 
739   /**
740    * The total RestrainDistance energy.
741    */
742   private double restrainDistanceEnergy;
743   /**
744    * The RestrainPosition Energy.
745    */
746   private double restrainPositionEnergy;
747   /**
748    * The RestrainGroup Energy.
749    */
750   private double restrainGroupEnergy;
751   /**
752    * The RestrainTorsion Energy.
753    */
754   private double restrainTorsionEnergy;
755 
756   /**
757    * The total energy for all Bonded Energy terms.
758    */
759   private double totalBondedEnergy;
760   /**
761    * The total van der Waals energy.
762    */
763   private double vanDerWaalsEnergy;
764   /**
765    * The permanent Multipole energy.
766    */
767   private double permanentMultipoleEnergy;
768   /**
769    * The permanent multipole real space energy.
770    */
771   private double permanentRealSpaceEnergy;
772   /**
773    * The total polarization energy.
774    */
775   private double polarizationEnergy;
776   /**
777    * The total electrostatic energy.
778    */
779   private double totalMultipoleEnergy;
780   /**
781    * The total non-bonded energy (van der Waals plus electrostatic).
782    */
783   private double totalNonBondedEnergy;
784   /**
785    * The solvation energy (GK).
786    */
787   private double solvationEnergy;
788   /**
789    * The total NCS Energy.
790    */
791   private double ncsEnergy;
792 
793   /**
794    * The total COM Restraint Energy.
795    */
796   private double comRestraintEnergy;
797   /**
798    * The total system energy.
799    */
800   private double totalEnergy;
801 
802   /**
803    * Time to evaluate the neutral network.
804    */
805   private long nnTime;
806   /**
807    * Time to evaluate Bond terms.
808    */
809   private long bondTime;
810   /**
811    * Time to evaluate Angle terms.
812    */
813   private long angleTime;
814   /**
815    * Time to evaluate Stretch-Bend terms.
816    */
817   private long stretchBendTime;
818   /**
819    * Time to evaluate Urey-Bradley terms.
820    */
821   private long ureyBradleyTime;
822   /**
823    * Time to evaluate Out-Of-Plane Bend terms.
824    */
825   private long outOfPlaneBendTime;
826   /**
827    * Time to evaluate Torsion terms.
828    */
829   private long torsionTime;
830   /**
831    * Time to evaluate Angle-Torsion terms.
832    */
833   private long angleTorsionTime;
834   /**
835    * Time to evaluate Stretch-Torsion terms.
836    */
837   private long stretchTorsionTime;
838   /**
839    * Time to evaluate Pi-Orbital Torsion terms.
840    */
841   private long piOrbitalTorsionTime;
842   /**
843    * Time to evaluate Improper Torsion terms.
844    */
845   private long improperTorsionTime;
846   /**
847    * Time to evaluate Torsion-Torsion terms.
848    */
849   private long torsionTorsionTime;
850   /**
851    * Time to evaluate van der Waals term.
852    */
853   private long vanDerWaalsTime;
854   /**
855    * Time to evaluate electrostatics term.
856    */
857   private long electrostaticTime;
858   /**
859    * Time to evaluate RestrainDistance terms.
860    */
861   private long restrainDistanceTime;
862   /**
863    * Time to evaluate RestrainTorsion terms.
864    */
865   private long restrainTorsionTime;
866 
867   /**
868    * Time to evaluate Center of Mass restraint term.
869    */
870   private long comRestraintTime;
871   /**
872    * Time to evaluate restrain group term.
873    */
874   private long restrainGroupTime;
875   /**
876    * Time to evaluate all energy terms.
877    */
878   private long totalTime;
879   /**
880    * Value of each degree of freedom.
881    */
882   private double[] xyz;
883   /**
884    * Constant pH extended system (TODO: needs further testing).
885    */
886   private ExtendedSystem esvSystem = null;
887 
888   private boolean esvTerm;
889   private double esvBias;
890   /**
891    * Time to evaluate NCS term.
892    */
893   private long ncsTime;
894   private int nRelativeSolvations;
895   /**
896    * Time to evaluate coordinate restraint term.
897    */
898   private long restrainPositionTime;
899 
900   private double relativeSolvationEnergy;
901   /**
902    * Enable verbose printing if large energy gradient components are observed.
903    */
904   private boolean printOnFailure;
905 
906   /**
907    * Constructor for ForceFieldEnergy.
908    *
909    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
910    */
911   protected ForceFieldEnergy(MolecularAssembly molecularAssembly) {
912     this(molecularAssembly, ParallelTeam.getDefaultThreadCount());
913   }
914 
915   /**
916    * Constructor for ForceFieldEnergy.
917    *
918    * @param molecularAssembly a {@link ffx.potential.MolecularAssembly} object.
919    * @param numThreads        a int.
920    */
921   protected ForceFieldEnergy(MolecularAssembly molecularAssembly, int numThreads) {
922     // Get a reference to the sorted atom array.
923     this.molecularAssembly = molecularAssembly;
924     atoms = molecularAssembly.getAtomArray();
925     nAtoms = atoms.length;
926     xyz = new double[nAtoms * 3];
927 
928     // Check that atom ordering is correct and count the number of active atoms.
929     for (int i = 0; i < nAtoms; i++) {
930       int index = atoms[i].getXyzIndex() - 1;
931       assert (i == index);
932     }
933 
934     // Enforce that the number of threads be less than or equal to the number of atoms.
935     int nThreads = min(nAtoms, numThreads);
936     parallelTeam = new ParallelTeam(nThreads);
937 
938     ForceField forceField = molecularAssembly.getForceField();
939     CompositeConfiguration properties = forceField.getProperties();
940     String name = forceField.toString().toUpperCase();
941 
942     logger.info(format(" Constructing Force Field %s", name));
943     logger.info(format("\n SMP threads:                        %10d", nThreads));
944 
945     // Rename water protons if requested.
946     boolean standardizeAtomNames = properties.getBoolean("standardizeAtomNames", true);
947     if (standardizeAtomNames) {
948       molecularAssembly.renameWaterProtons();
949     }
950 
951     nnTerm = forceField.getBoolean("NNTERM", false);
952     // For now, all atoms are neural network atoms, or none are.
953     if (nnTerm) {
954       for (Atom atom : atoms) {
955         atom.setNeuralNetwork(true);
956       }
957     }
958     bondTerm = forceField.getBoolean("BONDTERM", true);
959     angleTerm = forceField.getBoolean("ANGLETERM", true);
960     stretchBendTerm = forceField.getBoolean("STRBNDTERM", true);
961     ureyBradleyTerm = forceField.getBoolean("UREYTERM", true);
962     outOfPlaneBendTerm = forceField.getBoolean("OPBENDTERM", true);
963     torsionTerm = forceField.getBoolean("TORSIONTERM", true);
964     stretchTorsionTerm = forceField.getBoolean("STRTORTERM", true);
965     angleTorsionTerm = forceField.getBoolean("ANGTORTERM", true);
966     piOrbitalTorsionTerm = forceField.getBoolean("PITORSTERM", true);
967     torsionTorsionTerm = forceField.getBoolean("TORTORTERM", true);
968     improperTorsionTerm = forceField.getBoolean("IMPROPERTERM", true);
969     vanderWaalsTerm = forceField.getBoolean("VDWTERM", true);
970 
971     boolean tornadoVM = forceField.getBoolean("tornado", false);
972     if (vanderWaalsTerm && !tornadoVM) {
973       multipoleTerm = forceField.getBoolean("MPOLETERM", true);
974       if (multipoleTerm) {
975         String polarizeString = forceField.getString("POLARIZATION", "NONE");
976         boolean defaultPolarizeTerm = !polarizeString.equalsIgnoreCase("NONE");
977         polarizationTerm = forceField.getBoolean("POLARIZETERM", defaultPolarizeTerm);
978         generalizedKirkwoodTerm = forceField.getBoolean("GKTERM", false);
979       } else {
980         // If multipole electrostatics is turned off, turn off all electrostatics.
981         polarizationTerm = false;
982         generalizedKirkwoodTerm = false;
983       }
984     } else {
985       // If van der Waals is turned off, turn off all non-bonded terms.
986       multipoleTerm = false;
987       polarizationTerm = false;
988       generalizedKirkwoodTerm = false;
989     }
990 
991 
992     lambdaTerm = forceField.getBoolean("LAMBDATERM", false);
993     comTerm = forceField.getBoolean("COMRESTRAINTERM", false);
994     lambdaTorsions = forceField.getBoolean("TORSION_LAMBDATERM", false);
995     printOnFailure = forceField.getBoolean("PRINT_ON_FAILURE", false);
996 
997     // Detect "restrain-distance" property.
998     if (properties.containsKey("restrain-distance")) {
999       restrainDistanceTerm = true;
1000     } else {
1001       restrainDistanceTerm = false;
1002     }
1003 
1004     // Detect "restrain-torsion-cos" property.
1005     if (properties.containsKey("restrain-torsion-cos")) {
1006       restrainTorsionTerm = true;
1007     } else {
1008       restrainTorsionTerm = false;
1009     }
1010 
1011     // Detect "restrain-groups" property.
1012     if (properties.containsKey("restrain-groups")) {
1013       restrainGroupTerm = true;
1014     } else {
1015       restrainGroupTerm = false;
1016     }
1017 
1018     // Detect "restrain-position" property.
1019     if (properties.containsKey("restrain-position") || properties.containsKey("restrain-position-lambda")) {
1020       restrainPositionTerm = true;
1021     } else {
1022       restrainPositionTerm = false;
1023     }
1024 
1025 
1026     // For RESPA
1027     nnTermOrig = nnTerm;
1028     bondTermOrig = bondTerm;
1029     angleTermOrig = angleTerm;
1030     stretchBendTermOrig = stretchBendTerm;
1031     ureyBradleyTermOrig = ureyBradleyTerm;
1032     outOfPlaneBendTermOrig = outOfPlaneBendTerm;
1033     torsionTermOrig = torsionTerm;
1034     angleTorsionTermOrig = angleTorsionTerm;
1035     stretchTorsionTermOrig = stretchTorsionTerm;
1036     improperTorsionTermOrig = improperTorsionTerm;
1037     piOrbitalTorsionTermOrig = piOrbitalTorsionTerm;
1038     torsionTorsionTermOrig = torsionTorsionTerm;
1039     vanderWaalsTermOrig = vanderWaalsTerm;
1040     multipoleTermOrig = multipoleTerm;
1041     polarizationTermOrig = polarizationTerm;
1042     generalizedKirkwoodTermOrig = generalizedKirkwoodTerm;
1043     ncsTermOrig = ncsTerm;
1044     comTermOrig = comTerm;
1045     restrainDistanceTermOrig = restrainDistanceTerm;
1046     restrainTorsionTermOrig = restrainTorsionTerm;
1047     restrainPositionTermOrig = restrainPositionTerm;
1048     restrainGroupTermOrig = restrainGroupTerm;
1049 
1050     // Determine the unit cell dimensions and space group.
1051     String spacegroup;
1052     double a, b, c, alpha, beta, gamma;
1053     boolean aperiodic;
1054     try {
1055       spacegroup = forceField.getString("SPACEGROUP", "P 1");
1056       SpaceGroup sg = SpaceGroupDefinitions.spaceGroupFactory(spacegroup);
1057       LatticeSystem latticeSystem = sg.latticeSystem;
1058       a = forceField.getDouble("A_AXIS");
1059       aperiodic = false;
1060       b = forceField.getDouble("B_AXIS", latticeSystem.getDefaultBAxis(a));
1061       c = forceField.getDouble("C_AXIS", latticeSystem.getDefaultCAxis(a, b));
1062       alpha = forceField.getDouble("ALPHA", latticeSystem.getDefaultAlpha());
1063       beta = forceField.getDouble("BETA", latticeSystem.getDefaultBeta());
1064       gamma = forceField.getDouble("GAMMA", latticeSystem.getDefaultGamma());
1065       if (!sg.latticeSystem.validParameters(a, b, c, alpha, beta, gamma)) {
1066         logger.severe(" Check lattice parameters.");
1067       }
1068       if (a == 1.0 && b == 1.0 && c == 1.0) {
1069         String message = " A-, B-, and C-axis values equal to 1.0.";
1070         logger.info(message);
1071         throw new Exception(message);
1072       }
1073       if (a <= 0.0 || b <= 0.0 || c <= 0.0 || alpha <= 0.0 || beta <= 0.0 || gamma <= 0.0) {
1074         // Parameters are not valid. Switch to aperiodic.
1075         String message = " Crystal parameters are not valid due to negative or zero value.";
1076         logger.warning(message);
1077         throw new Exception(message);
1078       }
1079     } catch (Exception e) {
1080       aperiodic = true;
1081 
1082       // Determine the maximum separation between atoms.
1083       double maxR = 0.0;
1084       if (nAtoms < 10) {
1085         for (int i = 0; i < nAtoms - 1; i++) {
1086           Double3 xi = atoms[i].getXYZ();
1087           for (int j = 1; j < nAtoms; j++) {
1088             double r = atoms[j].getXYZ().dist(xi);
1089             maxR = max(r, maxR);
1090           }
1091         }
1092       } else {
1093         try {
1094           maxR = ConvexHullOps.maxDist(ConvexHullOps.constructHull(atoms));
1095         } catch (Exception ex) {
1096           // If Convex Hull approach fails (e.g., coplanar input, brute force...
1097           logger.info(" Convex Hull operation failed with message " + ex + "\n Trying brute force approach...");
1098           if(logger.isLoggable(Level.FINE)){
1099             logger.fine(Utilities.stackTraceToString(ex));
1100           }
1101           for (int i = 0; i < nAtoms - 1; i++) {
1102             Double3 xi = atoms[i].getXYZ();
1103             for (int j = 1; j < nAtoms; j++) {
1104               double r = atoms[j].getXYZ().dist(xi);
1105               maxR = max(r, maxR);
1106             }
1107           }
1108         }
1109       }
1110       maxR = max(10.0, maxR);
1111 
1112       logger.info(format(" The system will be treated as aperiodic (max separation = %6.1f A).", maxR));
1113 
1114       // Turn off reciprocal space calculations.
1115       forceField.addProperty("EWALD_ALPHA", "0.0");
1116 
1117       // Specify some dummy values for the crystal.
1118       spacegroup = "P1";
1119       a = 2.0 * maxR;
1120       b = 2.0 * maxR;
1121       c = 2.0 * maxR;
1122       alpha = 90.0;
1123       beta = 90.0;
1124       gamma = 90.0;
1125     }
1126     Crystal unitCell = new Crystal(a, b, c, alpha, beta, gamma, spacegroup);
1127     unitCell.setAperiodic(aperiodic);
1128 
1129     // First set cutoffs assuming PBC.
1130     vdwCutoff = DEFAULT_VDW_CUTOFF;
1131     ewaldCutoff = DEFAULT_EWALD_CUTOFF;
1132     gkCutoff = vdwCutoff;
1133     if (unitCell.aperiodic()) {
1134       // Default to no cutoffs for aperiodic systems.
1135       vdwCutoff = Double.POSITIVE_INFINITY;
1136       ewaldCutoff = Double.POSITIVE_INFINITY;
1137       gkCutoff = Double.POSITIVE_INFINITY;
1138     }
1139     // Load user specified cutoffs.
1140     vdwCutoff = forceField.getDouble("VDW_CUTOFF", vdwCutoff);
1141     ewaldCutoff = forceField.getDouble("EWALD_CUTOFF", ewaldCutoff);
1142     gkCutoff = forceField.getDouble("GK_CUTOFF", gkCutoff);
1143 
1144     // Neighbor list cutoff is the maximum of the vdW, Ewald and GK cutoffs (i.e. to use a single list).
1145     double nlistCutoff = vdwCutoff;
1146     if (multipoleTerm) {
1147       nlistCutoff = max(vdwCutoff, ewaldCutoff);
1148     }
1149     if (generalizedKirkwoodTerm) {
1150       nlistCutoff = max(nlistCutoff, gkCutoff);
1151     }
1152 
1153     // Check for a frozen neighbor list.
1154     boolean disabledNeighborUpdates = forceField.getBoolean("DISABLE_NEIGHBOR_UPDATES", false);
1155     if (disabledNeighborUpdates) {
1156       logger.info(format(" Neighbor list updates disabled; interactions will "
1157           + "only be calculated between atoms that started the simulation "
1158           + "within a radius of %9.3g Angstroms of each other.", nlistCutoff));
1159       // The individual cutoffs are infinity (i.e. they are limited by interactions in the list).
1160       vdwCutoff = Double.POSITIVE_INFINITY;
1161       ewaldCutoff = Double.POSITIVE_INFINITY;
1162       gkCutoff = Double.POSITIVE_INFINITY;
1163     }
1164 
1165     listBuffer = forceField.getDouble("LIST_BUFFER", DEFAULT_LIST_BUFFER);
1166     cutoffPlusBuffer = nlistCutoff + listBuffer;
1167     cutOff2 = 2.0 * cutoffPlusBuffer;
1168     unitCell = configureNCS(forceField, unitCell);
1169 
1170     // If necessary, create a ReplicatesCrystal.
1171     if (!aperiodic) {
1172       this.crystal = ReplicatesCrystal.replicatesCrystalFactory(unitCell, cutOff2);
1173       logger.info(format("\n Density:                                %6.3f (g/cc)",
1174           crystal.getDensity(molecularAssembly.getMass())));
1175       logger.info(crystal.toString());
1176     } else {
1177       this.crystal = unitCell;
1178     }
1179 
1180     if (!unitCell.aperiodic() && unitCell.spaceGroup.number == 1) {
1181       ncsTerm = forceField.getBoolean("NCSTERM", false);
1182       ncsTermOrig = ncsTerm;
1183     } else {
1184       ncsTerm = false;
1185       ncsTermOrig = false;
1186     }
1187 
1188     // Now that the crystal is set, check for special positions.
1189     int nSpecial = checkForSpecialPositions(forceField);
1190 
1191     rigidHydrogens = forceField.getBoolean("RIGID_HYDROGENS", false);
1192     rigidScale = forceField.getDouble("RIGID_SCALE", 10.0);
1193 
1194     nRelativeSolvations = 0;
1195     String relSolvLibrary = forceField.getString("RELATIVE_SOLVATION", "NONE").toUpperCase();
1196     SolvationLibrary library = SolvationLibrary.valueOf(relSolvLibrary);
1197     if (library == SolvationLibrary.AUTO) {
1198       if (generalizedKirkwoodTerm && name.toUpperCase().contains("OPLS")) {
1199         library = SolvationLibrary.MACCALLUM_TIP4P;
1200         // Change when we have good Generalized Born numbers of our own for OPLS.
1201       } else if (generalizedKirkwoodTerm) {
1202         library = SolvationLibrary.GK;
1203       } else {
1204         library = SolvationLibrary.NONE;
1205       }
1206     }
1207     if (library != SolvationLibrary.NONE) {
1208       relativeSolvationTerm = true;
1209       relativeSolvation = new RelativeSolvation(library, forceField);
1210     } else {
1211       relativeSolvationTerm = false;
1212       relativeSolvation = null;
1213     }
1214 
1215     boolean checkAllNodeCharges = forceField.getBoolean("CHECK_ALL_NODE_CHARGES", false);
1216 
1217     if (rigidScale <= 1.0) {
1218       rigidScale = 1.0;
1219     }
1220 
1221     logger.info("\n Bonded Terms");
1222     if (rigidHydrogens && rigidScale > 1.0) {
1223       logger.info(format("  Rigid hydrogens:                   %10.2f", rigidScale));
1224     }
1225 
1226     // Collect, count, pack and sort bonds.
1227     if (bondTerm) {
1228       List<Bond> bondList = molecularAssembly.getBondList();
1229       if (nnTerm) {
1230         removeNeuralNetworkTerms(bondList);
1231       }
1232       nBonds = bondList.size();
1233       bonds = bondList.toArray(new Bond[0]);
1234       sort(bonds);
1235       if (nBonds > 0) {
1236         logger.info(format("  Bonds:                             %10d", nBonds));
1237       }
1238     } else {
1239       nBonds = 0;
1240       bonds = null;
1241     }
1242 
1243     // Collect, count, pack and sort angles.
1244     if (angleTerm) {
1245       List<Angle> angleList = molecularAssembly.getAngleList();
1246       if (nnTerm) {
1247         removeNeuralNetworkTerms(angleList);
1248       }
1249       nAngles = angleList.size();
1250       angles = angleList.toArray(new Angle[0]);
1251       sort(angles);
1252       if (nAngles > 0) {
1253         logger.info(format("  Angles:                            %10d", nAngles));
1254       }
1255     } else {
1256       nAngles = 0;
1257       angles = null;
1258     }
1259 
1260     // Collect, count, pack and sort stretch-bends.
1261     if (stretchBendTerm) {
1262       List<StretchBend> stretchBendList = molecularAssembly.getStretchBendList();
1263       if (nnTerm) {
1264         removeNeuralNetworkTerms(stretchBendList);
1265       }
1266       nStretchBends = stretchBendList.size();
1267       stretchBends = stretchBendList.toArray(new StretchBend[0]);
1268       sort(stretchBends);
1269       if (nStretchBends > 0) {
1270         logger.info(format("  Stretch-Bends:                     %10d", nStretchBends));
1271       }
1272     } else {
1273       nStretchBends = 0;
1274       stretchBends = null;
1275     }
1276 
1277     // Collect, count, pack and sort Urey-Bradleys.
1278     if (ureyBradleyTerm) {
1279       List<UreyBradley> ureyBradleyList = molecularAssembly.getUreyBradleyList();
1280       if (nnTerm) {
1281         removeNeuralNetworkTerms(ureyBradleyList);
1282       }
1283       nUreyBradleys = ureyBradleyList.size();
1284       ureyBradleys = ureyBradleyList.toArray(new UreyBradley[0]);
1285       sort(ureyBradleys);
1286       if (nUreyBradleys > 0) {
1287         logger.info(format("  Urey-Bradleys:                     %10d", nUreyBradleys));
1288       }
1289     } else {
1290       nUreyBradleys = 0;
1291       ureyBradleys = null;
1292     }
1293 
1294     // Set a multiplier on the force constants of bonded terms containing hydrogens.
1295     if (rigidHydrogens) {
1296       if (bonds != null) {
1297         for (Bond bond : bonds) {
1298           if (bond.containsHydrogen()) {
1299             bond.setRigidScale(rigidScale);
1300           }
1301         }
1302       }
1303       if (angles != null) {
1304         for (Angle angle : angles) {
1305           if (angle.containsHydrogen()) {
1306             angle.setRigidScale(rigidScale);
1307           }
1308         }
1309       }
1310       if (stretchBends != null) {
1311         for (StretchBend stretchBend : stretchBends) {
1312           if (stretchBend.containsHydrogen()) {
1313             stretchBend.setRigidScale(rigidScale);
1314           }
1315         }
1316       }
1317       if (ureyBradleys != null) {
1318         for (UreyBradley ureyBradley : ureyBradleys) {
1319           if (ureyBradley.containsHydrogen()) {
1320             ureyBradley.setRigidScale(rigidScale);
1321           }
1322         }
1323       }
1324     }
1325 
1326     // Collect, count, pack and sort out-of-plane bends.
1327     if (outOfPlaneBendTerm) {
1328       List<OutOfPlaneBend> outOfPlaneBendList = molecularAssembly.getOutOfPlaneBendList();
1329       if (nnTerm) {
1330         removeNeuralNetworkTerms(outOfPlaneBendList);
1331       }
1332       nOutOfPlaneBends = outOfPlaneBendList.size();
1333       outOfPlaneBends = outOfPlaneBendList.toArray(new OutOfPlaneBend[0]);
1334       sort(outOfPlaneBends);
1335       if (nOutOfPlaneBends > 0) {
1336         logger.info(format("  Out-of-Plane Bends:                %10d", nOutOfPlaneBends));
1337       }
1338     } else {
1339       nOutOfPlaneBends = 0;
1340       outOfPlaneBends = null;
1341     }
1342 
1343     // Collect, count and pack torsions.
1344     if (torsionTerm) {
1345       List<Torsion> torsionList = molecularAssembly.getTorsionList();
1346       if (nnTerm) {
1347         removeNeuralNetworkTerms(torsionList);
1348       }
1349       nTorsions = torsionList.size();
1350       torsions = torsionList.toArray(new Torsion[0]);
1351       if (nTorsions > 0) {
1352         logger.info(format("  Torsions:                          %10d", nTorsions));
1353       }
1354     } else {
1355       nTorsions = 0;
1356       torsions = null;
1357     }
1358 
1359     // Collect, count and pack stretch torsions.
1360     if (stretchTorsionTerm) {
1361       List<StretchTorsion> stretchTorsionList = molecularAssembly.getStretchTorsionList();
1362       if (nnTerm) {
1363         removeNeuralNetworkTerms(stretchTorsionList);
1364       }
1365       nStretchTorsions = stretchTorsionList.size();
1366       stretchTorsions = stretchTorsionList.toArray(new StretchTorsion[0]);
1367       if (nStretchTorsions > 0) {
1368         logger.info(format("  Stretch-Torsions:                  %10d", nStretchTorsions));
1369       }
1370     } else {
1371       nStretchTorsions = 0;
1372       stretchTorsions = null;
1373     }
1374 
1375     // Collect, count and pack angle torsions.
1376     if (angleTorsionTerm) {
1377       List<AngleTorsion> angleTorsionList = molecularAssembly.getAngleTorsionList();
1378       if (nnTerm) {
1379         removeNeuralNetworkTerms(angleTorsionList);
1380       }
1381       nAngleTorsions = angleTorsionList.size();
1382       angleTorsions = angleTorsionList.toArray(new AngleTorsion[0]);
1383       if (nAngleTorsions > 0) {
1384         logger.info(format("  Angle-Torsions:                    %10d", nAngleTorsions));
1385       }
1386     } else {
1387       nAngleTorsions = 0;
1388       angleTorsions = null;
1389     }
1390 
1391     // Collect, count and pack pi-orbital torsions.
1392     if (piOrbitalTorsionTerm) {
1393       List<PiOrbitalTorsion> piOrbitalTorsionList = molecularAssembly.getPiOrbitalTorsionList();
1394       if (nnTerm) {
1395         removeNeuralNetworkTerms(piOrbitalTorsionList);
1396       }
1397       nPiOrbitalTorsions = piOrbitalTorsionList.size();
1398       piOrbitalTorsions = piOrbitalTorsionList.toArray(new PiOrbitalTorsion[0]);
1399       if (nPiOrbitalTorsions > 0) {
1400         logger.info(format("  Pi-Orbital Torsions:               %10d", nPiOrbitalTorsions));
1401       }
1402     } else {
1403       nPiOrbitalTorsions = 0;
1404       piOrbitalTorsions = null;
1405     }
1406 
1407     // Collect, count and pack torsion-torsions.
1408     if (torsionTorsionTerm) {
1409       List<TorsionTorsion> torsionTorsionList = molecularAssembly.getTorsionTorsionList();
1410       if (nnTerm) {
1411         removeNeuralNetworkTerms(torsionTorsionList);
1412       }
1413       nTorsionTorsions = torsionTorsionList.size();
1414       torsionTorsions = torsionTorsionList.toArray(new TorsionTorsion[0]);
1415       if (nTorsionTorsions > 0) {
1416         logger.info(format("  Torsion-Torsions:                  %10d", nTorsionTorsions));
1417       }
1418     } else {
1419       nTorsionTorsions = 0;
1420       torsionTorsions = null;
1421     }
1422 
1423     // Collect, count and pack improper torsions.
1424     if (improperTorsionTerm) {
1425       List<ImproperTorsion> improperTorsionList = molecularAssembly.getImproperTorsionList();
1426       if (nnTerm) {
1427         removeNeuralNetworkTerms(improperTorsionList);
1428       }
1429       nImproperTorsions = improperTorsionList.size();
1430       improperTorsions = improperTorsionList.toArray(new ImproperTorsion[0]);
1431       if (nImproperTorsions > 0) {
1432         logger.info(format("  Improper Torsions:                 %10d", nImproperTorsions));
1433       }
1434     } else {
1435       nImproperTorsions = 0;
1436       improperTorsions = null;
1437     }
1438 
1439     int[] molecule = molecularAssembly.getMoleculeNumbers();
1440     if (vanderWaalsTerm) {
1441       logger.info("\n Non-Bonded Terms");
1442       boolean[] nn = null;
1443       if (nnTerm) {
1444         nn = molecularAssembly.getNeuralNetworkIdentity();
1445       } else {
1446         nn = new boolean[nAtoms];
1447         Arrays.fill(nn, false);
1448       }
1449 
1450       if (!tornadoVM) {
1451         vanderWaals = new VanDerWaals(atoms, molecule, nn, crystal, forceField, parallelTeam,
1452             vdwCutoff, nlistCutoff);
1453       } else {
1454         vanderWaals = new VanDerWaalsTornado(atoms, crystal, forceField, vdwCutoff);
1455       }
1456     } else {
1457       vanderWaals = null;
1458     }
1459 
1460     if (nnTerm) {
1461       aniEnergy = new ANIEnergy(molecularAssembly);
1462     } else {
1463       aniEnergy = null;
1464     }
1465 
1466     if (multipoleTerm) {
1467       if (name.contains("OPLS") || name.contains("AMBER") || name.contains("CHARMM")) {
1468         elecForm = ELEC_FORM.FIXED_CHARGE;
1469       } else {
1470         elecForm = ELEC_FORM.PAM;
1471       }
1472       particleMeshEwald = new ParticleMeshEwald(atoms, molecule, forceField, crystal,
1473           vanderWaals.getNeighborList(), elecForm, ewaldCutoff, gkCutoff, parallelTeam);
1474       double charge = molecularAssembly.getCharge(checkAllNodeCharges);
1475       logger.info(format("\n  Overall system charge:             %10.3f", charge));
1476     } else {
1477       elecForm = null;
1478       particleMeshEwald = null;
1479     }
1480 
1481     if (ncsTerm) {
1482       String sg = forceField.getString("NCSGROUP", "P 1");
1483       Crystal ncsCrystal = new Crystal(a, b, c, alpha, beta, gamma, sg);
1484       ncsRestraint = new NCSRestraint(atoms, forceField, ncsCrystal);
1485     } else {
1486       ncsRestraint = null;
1487     }
1488 
1489     if (restrainPositionTerm) {
1490       restrainPositions = parseRestrainPositions(molecularAssembly);
1491       if (restrainPositions != null) {
1492         nRestrainPositions = restrainPositions.length;
1493       } else {
1494         nRestrainPositions = 0;
1495       }
1496     } else {
1497       restrainPositions = null;
1498     }
1499 
1500     if (restrainGroupTerm) {
1501       restrainGroups = new RestrainGroups(molecularAssembly);
1502       nRestrainGroups = restrainGroups.getNumberOfRestraints();
1503     } else {
1504       restrainGroups = null;
1505     }
1506 
1507     if (comTerm) {
1508       comRestraint = new COMRestraint(molecularAssembly);
1509     } else {
1510       comRestraint = null;
1511     }
1512 
1513     if (restrainDistanceTerm) {
1514       // Apply restrain-distance records.
1515       configureRestraintBonds(properties);
1516     }
1517 
1518     if (restrainTorsionTerm) {
1519       nRestrainTorsions = configureRestraintTorsions(properties, forceField);
1520     }
1521 
1522 
1523     bondedRegion = new BondedRegion();
1524 
1525     maxDebugGradient = forceField.getDouble("MAX_DEBUG_GRADIENT", Double.POSITIVE_INFINITY);
1526 
1527     molecularAssembly.setPotential(this);
1528 
1529     // Configure constraints.
1530     constraints = configureConstraints(forceField);
1531 
1532     if (lambdaTerm) {
1533       this.setLambda(1.0);
1534       if (nSpecial == 0) {
1535         // For lambda calculations (e.g., polymorph searches), turn-off special position checks.
1536         // In this case, as the crystal lattice is alchemically turned on, special position overlaps are expected.
1537         crystal.setSpecialPositionCutoff(0.0);
1538       } else {
1539         // If special positions have already been identified, leave checks on.
1540         logger.info(" Special positions checking will be performed during a lambda simulation.");
1541       }
1542     }
1543   }
1544 
1545   private int checkForSpecialPositions(ForceField forceField) {
1546     // Check for atoms at special positions. These should normally be set to inactive.
1547     boolean specialPositionsInactive = forceField.getBoolean("SPECIAL_POSITIONS_INACTIVE", true);
1548     int nSpecial = 0;
1549     if (specialPositionsInactive) {
1550       int nSymm = crystal.getNumSymOps();
1551       if (nSymm > 1) {
1552         SpaceGroup spaceGroup = crystal.spaceGroup;
1553         double sp2 = crystal.getSpecialPositionCutoff2();
1554         double[] mate = new double[3];
1555         StringBuilder sb = new StringBuilder("\n Atoms at Special Positions set to Inactive:\n");
1556         for (int i = 0; i < nAtoms; i++) {
1557           Atom atom = atoms[i];
1558           double[] xyz = atom.getXYZ(null);
1559           for (int iSymm = 1; iSymm < nSymm; iSymm++) {
1560             SymOp symOp = spaceGroup.getSymOp(iSymm);
1561             crystal.applySymOp(xyz, mate, symOp);
1562             double dr2 = crystal.image(xyz, mate);
1563             if (dr2 < sp2) {
1564               sb.append(
1565                   format("  %s separation with SymOp %d at %8.6f A.\n", atoms[i], iSymm, sqrt(dr2)));
1566               atom.setActive(false);
1567               nSpecial++;
1568               break;
1569             }
1570           }
1571         }
1572         if (nSpecial > 0) {
1573           logger.info(sb.toString());
1574         }
1575       }
1576     }
1577     return nSpecial;
1578   }
1579 
1580   /**
1581    * Method to parse the restrain-torsion-cos records.
1582    *
1583    * @param properties Configuration properties.
1584    * @param forceField Force field properties.
1585    * @return Number of restraint torsions.
1586    */
1587   private int configureRestraintTorsions(CompositeConfiguration properties, ForceField forceField) {
1588     String[] restrainTorsionCos = properties.getStringArray("restrain-torsion-cos");
1589     double torsUnits = forceField.getDouble("TORSIONUNIT", TorsionType.DEFAULT_TORSION_UNIT);
1590     List<RestraintTorsion> rTorsList = new ArrayList<>(restrainTorsionCos.length);
1591     for (String rtc : restrainTorsionCos) {
1592       String[] toks = rtc.split("\\s+");
1593       int nTok = toks.length;
1594       if (nTok < 7) {
1595         throw new IllegalArgumentException(format(
1596             "restrain-torsion-cos record %s must have 4 atom indices, amplitude, phase shift and periodicity!",
1597             rtc));
1598       }
1599       int ai1 = Integer.parseInt(toks[0]) - 1;
1600       int ai2 = Integer.parseInt(toks[1]) - 1;
1601       int ai3 = Integer.parseInt(toks[2]) - 1;
1602       int ai4 = Integer.parseInt(toks[3]) - 1;
1603       Atom a1 = atoms[ai1];
1604       Atom a2 = atoms[ai2];
1605       Atom a3 = atoms[ai3];
1606       Atom a4 = atoms[ai4];
1607       int[] atomClasses = new int[]{-1, -1, -1, -1};
1608 
1609       int startTerms = 4;
1610       boolean lamEnabled = false;
1611       boolean revLam = false;
1612       if (toks[4].equalsIgnoreCase("lambda")) {
1613         ++startTerms;
1614         lamEnabled = true;
1615         if (toks[5].toLowerCase().startsWith("reverse")) {
1616           ++startTerms;
1617           revLam = true;
1618         }
1619       }
1620       if (!lambdaTerm) {
1621         lamEnabled = false;
1622       }
1623 
1624       int nTerms = (nTok - startTerms) / 3;
1625       assert (nTok - startTerms) % 3 == 0;
1626       double[] amp = new double[nTerms];
1627       double[] phase = new double[nTerms];
1628       int[] period = new int[nTerms];
1629 
1630       for (int i = 0; i < nTerms; i++) {
1631         int i0 = startTerms + (3 * i);
1632         amp[i] = Double.parseDouble(toks[i0]);
1633         phase[i] = Double.parseDouble(toks[i0 + 1]);
1634         period[i] = Integer.parseInt(toks[i0 + 2]);
1635       }
1636 
1637       // Lambda-enabled torsion restraints require lambda term to be true.
1638       assert !lamEnabled || lambdaTerm;
1639       TorsionType tType = new TorsionType(atomClasses, amp, phase, period);
1640       rTorsList.add(new RestraintTorsion(a1, a2, a3, a4, tType, lamEnabled, revLam, torsUnits));
1641     }
1642 
1643     if (!rTorsList.isEmpty()) {
1644       logger.info(format(" Adding %4d cosine-based torsion restraints.", nRestrainTorsions));
1645       restrainTorsionTerm = true;
1646       restrainTorsions = rTorsList.toArray(new RestraintTorsion[0]);
1647       restrainTorsionEnergy = 0;
1648       restrainTorsionTime = 0;
1649       return rTorsList.size();
1650     }
1651     return 0;
1652   }
1653 
1654   /**
1655    * Method to parse the restrain-distance records.
1656    *
1657    * @param properties Configuration properties.
1658    */
1659   private void configureRestraintBonds(CompositeConfiguration properties) {
1660 
1661     String[] bondRestraints = properties.getStringArray("restrain-distance");
1662     for (String bondRest : bondRestraints) {
1663       try {
1664         String[] toks = bondRest.split("\\s+");
1665         if (toks.length < 2) {
1666           throw new IllegalArgumentException(
1667               format(" restrain-distance value %s could not be parsed!", bondRest));
1668         }
1669         // Internally, everything starts with 0, but restrain distance starts at 1, so that 1 has to
1670         // be subtracted
1671         int at1 = Integer.parseInt(toks[0]) - 1;
1672         int at2 = Integer.parseInt(toks[1]) - 1;
1673 
1674         double forceConst = 100.0;
1675         double flatBottomRadius = 0;
1676         Atom a1 = atoms[at1];
1677         Atom a2 = atoms[at2];
1678 
1679         if (toks.length > 2) {
1680           forceConst = Double.parseDouble(toks[2]);
1681         }
1682         double dist;
1683         switch (toks.length) {
1684           case 2:
1685           case 3:
1686             double[] xyz1 = new double[3];
1687             xyz1 = a1.getXYZ(xyz1);
1688             double[] xyz2 = new double[3];
1689             xyz2 = a2.getXYZ(xyz2);
1690             // Current distance between restrained atoms
1691             dist = crystal.minDistOverSymOps(xyz1, xyz2);
1692             break;
1693           case 4:
1694             dist = Double.parseDouble(toks[3]);
1695             break;
1696           case 5:
1697           default:
1698             double minDist = Double.parseDouble(toks[3]);
1699             double maxDist = Double.parseDouble(toks[4]);
1700             dist = 0.5 * (minDist + maxDist);
1701             flatBottomRadius = 0.5 * Math.abs(maxDist - minDist);
1702             break;
1703         }
1704 
1705         UnivariateSwitchingFunction switchF;
1706         double lamStart = RestrainDistance.DEFAULT_RB_LAM_START;
1707         double lamEnd = RestrainDistance.DEFAULT_RB_LAM_END;
1708         if (toks.length > 5) {
1709           int offset = 5;
1710           if (toks[5].matches("^[01](?:\\.[0-9]*)?")) {
1711             offset = 6;
1712             lamStart = Double.parseDouble(toks[5]);
1713             if (toks[6].matches("^[01](?:\\.[0-9]*)?")) {
1714               offset = 7;
1715               lamEnd = Double.parseDouble(toks[6]);
1716             }
1717           }
1718           switchF = UnivariateFunctionFactory.parseUSF(toks, offset);
1719         } else {
1720           switchF = new ConstantSwitch();
1721         }
1722 
1723         setRestrainDistance(a1, a2, dist, forceConst, flatBottomRadius, lamStart, lamEnd, switchF);
1724       } catch (Exception ex) {
1725         logger.info(format(" Exception in parsing restrain-distance: %s", ex));
1726       }
1727     }
1728   }
1729 
1730   /**
1731    * Configure bonded constraints.
1732    *
1733    * @param forceField Force field properties.
1734    * @return List of constraints.
1735    */
1736   private List<Constraint> configureConstraints(ForceField forceField) {
1737     String constraintStrings = forceField.getString("CONSTRAIN", forceField.getString("RATTLE", null));
1738     if (constraintStrings == null) {
1739       return Collections.emptyList();
1740     }
1741 
1742     ArrayList<Constraint> constraints = new ArrayList<>();
1743     logger.info(format(" Experimental: parsing constraints option %s", constraintStrings));
1744 
1745     Set<Bond> numericBonds = new HashSet<>(1);
1746     Set<Angle> numericAngles = new HashSet<>(1);
1747 
1748     // Totally empty constrain option: constrain only X-H bonds. No other options applied.
1749     if (constraintStrings.isEmpty() || constraintStrings.matches("^\\s*$")) {
1750       // Assume constraining only X-H bonds (i.e. RIGID-HYDROGEN).
1751       logger.info(" Constraining X-H bonds.");
1752       numericBonds = Arrays.stream(bonds).filter(
1753           (Bond bond) -> bond.getAtom(0).getAtomicNumber() == 1
1754               || bond.getAtom(1).getAtomicNumber() == 1).collect(Collectors.toSet());
1755     } else {
1756       String[] constraintToks = constraintStrings.split("\\s+");
1757 
1758       // First, accumulate SETTLE constraints.
1759       for (String tok : constraintToks) {
1760         if (tok.equalsIgnoreCase("WATER")) {
1761           logger.info(" Constraining waters to be rigid based on angle & bonds.");
1762           // XYZ files, in particular, have waters mislabeled as generic Molecules.
1763           // First, find any such mislabeled water.
1764           Stream<MSNode> settleStream = molecularAssembly.getMolecules().stream()
1765               .filter((MSNode m) -> m.getAtomList().size() == 3).filter((MSNode m) -> {
1766                 List<Atom> atoms = m.getAtomList();
1767                 Atom O = null;
1768                 List<Atom> H = new ArrayList<>(2);
1769                 for (Atom at : atoms) {
1770                   int atN = at.getAtomicNumber();
1771                   if (atN == 8) {
1772                     O = at;
1773                   } else if (atN == 1) {
1774                     H.add(at);
1775                   }
1776                 }
1777                 return O != null && H.size() == 2;
1778               });
1779           // Now concatenate the stream with the properly labeled waters.
1780           settleStream = Stream.concat(settleStream, molecularAssembly.getWater().stream());
1781           // Map them into new Settle constraints and collect.
1782           List<SettleConstraint> settleConstraints = settleStream.map(
1783               (MSNode m) -> m.getAngleList().getFirst()).map(SettleConstraint::settleFactory).toList();
1784           constraints.addAll(settleConstraints);
1785 
1786         } else if (tok.equalsIgnoreCase("DIATOMIC")) {
1787           logger.severe(" Diatomic distance constraints not yet implemented properly.");
1788         } else if (tok.equalsIgnoreCase("TRIATOMIC")) {
1789           logger.severe(
1790               " Triatomic SETTLE constraints for non-water molecules not yet implemented properly.");
1791         }
1792       }
1793 
1794       // Second, accumulate bond/angle constraints.
1795       for (String tok : constraintToks) {
1796         if (tok.equalsIgnoreCase("BONDS")) {
1797           numericBonds = new HashSet<>(Arrays.asList(bonds));
1798         } else if (tok.equalsIgnoreCase("ANGLES")) {
1799           numericAngles = new HashSet<>(Arrays.asList(angles));
1800         }
1801       }
1802     }
1803 
1804     // Remove bonds that are already dealt with via angles.
1805     for (Angle angle : numericAngles) {
1806       angle.getBondList().forEach(numericBonds::remove);
1807     }
1808 
1809     // Remove already-constrained angles and bonds (e.g. SETTLE-constrained ones).
1810     List<Angle> ccmaAngles = numericAngles.stream().filter((Angle ang) -> !ang.isConstrained())
1811         .collect(Collectors.toList());
1812     List<Bond> ccmaBonds = numericBonds.stream().filter((Bond bond) -> !bond.isConstrained())
1813         .collect(Collectors.toList());
1814 
1815     CcmaConstraint ccmaConstraint = CcmaConstraint.ccmaFactory(ccmaBonds, ccmaAngles, atoms,
1816         getMass(), CcmaConstraint.DEFAULT_CCMA_NONZERO_CUTOFF);
1817     constraints.add(ccmaConstraint);
1818     logger.info(format(" Added %d constraints.", constraints.size()));
1819 
1820     return constraints;
1821   }
1822 
1823   /**
1824    * Static factory method to create a ForceFieldEnergy, possibly via FFX or OpenMM implementations.
1825    *
1826    * @param assembly To create FFE over
1827    * @return a {@link ffx.potential.ForceFieldEnergy} object.
1828    */
1829   public static ForceFieldEnergy energyFactory(MolecularAssembly assembly) {
1830     return energyFactory(assembly, ParallelTeam.getDefaultThreadCount());
1831   }
1832 
1833   /**
1834    * Static factory method to create a ForceFieldEnergy, possibly via FFX or OpenMM implementations.
1835    *
1836    * @param assembly   To create FFE over
1837    * @param numThreads Number of threads to use for FFX energy
1838    * @return A ForceFieldEnergy on some Platform
1839    */
1840   @SuppressWarnings("fallthrough")
1841   public static ForceFieldEnergy energyFactory(MolecularAssembly assembly, int numThreads) {
1842     ForceField forceField = assembly.getForceField();
1843     String platformString = toEnumForm(forceField.getString("PLATFORM", "FFX"));
1844     try {
1845       Platform platform = Platform.valueOf(platformString);
1846       switch (platform) {
1847         case OMM, OMM_REF, OMM_CUDA, OMM_OPENCL:
1848           try {
1849             return new OpenMMEnergy(assembly, platform, numThreads);
1850           } catch (Exception ex) {
1851             logger.warning(format(" Exception creating OpenMMEnergy: %s", ex));
1852             ForceFieldEnergy ffxEnergy = assembly.getPotentialEnergy();
1853             if (ffxEnergy == null) {
1854               ffxEnergy = new ForceFieldEnergy(assembly, numThreads);
1855               assembly.setPotential(ffxEnergy);
1856             }
1857             return ffxEnergy;
1858           }
1859         case OMM_CPU:
1860           logger.warning(format(" Platform %s not supported; defaulting to FFX", platform));
1861         default:
1862           ForceFieldEnergy ffxEnergy = assembly.getPotentialEnergy();
1863           if (ffxEnergy == null) {
1864             ffxEnergy = new ForceFieldEnergy(assembly, numThreads);
1865             assembly.setPotential(ffxEnergy);
1866           }
1867           return ffxEnergy;
1868       }
1869     } catch (IllegalArgumentException | NullPointerException ex) {
1870       logger.warning(
1871           format(" String %s did not match a known energy implementation", platformString));
1872       ForceFieldEnergy ffxEnergy = assembly.getPotentialEnergy();
1873       if (ffxEnergy == null) {
1874         ffxEnergy = new ForceFieldEnergy(assembly, numThreads);
1875         assembly.setPotential(ffxEnergy);
1876       }
1877       return ffxEnergy;
1878     }
1879   }
1880 
1881   /**
1882    * Applies constraints to positions
1883    *
1884    * @param xPrior Prior coodinates.
1885    * @param xNew   New coordinates.
1886    */
1887   public void applyAllConstraintPositions(double[] xPrior, double[] xNew) {
1888     applyAllConstraintPositions(xPrior, xNew, DEFAULT_CONSTRAINT_TOLERANCE);
1889   }
1890 
1891   /**
1892    * Applies constraints to positions
1893    *
1894    * @param xPrior Prior coodinates.
1895    * @param xNew   New coordinates.
1896    * @param tol    Constraint Tolerance.
1897    */
1898   public void applyAllConstraintPositions(double[] xPrior, double[] xNew, double tol) {
1899     if (xPrior == null) {
1900       xPrior = Arrays.copyOf(xNew, xNew.length);
1901     }
1902     for (Constraint constraint : constraints) {
1903       constraint.applyConstraintToStep(xPrior, xNew, getMass(), tol);
1904     }
1905   }
1906 
1907   /**
1908    * Overwrites current esvSystem if present. Multiple ExtendedSystems is possible but unnecessary;
1909    * add all ESVs to one system (per FFE, at least).
1910    *
1911    * @param system a {@link ffx.potential.extended.ExtendedSystem} object.
1912    */
1913   public void attachExtendedSystem(ExtendedSystem system) {
1914     if (system == null) {
1915       throw new IllegalArgumentException();
1916     }
1917     esvTerm = true;
1918     esvTermOrig = esvTerm;
1919     esvSystem = system;
1920     if (vanderWaalsTerm) {
1921       if (vanderWaals == null) {
1922         logger.warning("Null VdW during ESV setup.");
1923       } else {
1924         vanderWaals.attachExtendedSystem(system);
1925       }
1926     }
1927     if (multipoleTerm) {
1928       if (particleMeshEwald == null) {
1929         logger.warning("Null PME during ESV setup.");
1930       } else {
1931         particleMeshEwald.attachExtendedSystem(system);
1932       }
1933     }
1934   }
1935 
1936   /**
1937    * {@inheritDoc}
1938    *
1939    * <p>Returns true if lambda term is not enabled for this ForceFieldEnergy.
1940    */
1941   @Override
1942   public boolean dEdLZeroAtEnds() {
1943     // This may actually be true even with softcored atoms.
1944     // For now, serves the purpose of reporting true when nothing is softcored.
1945     return !lambdaTerm;
1946   }
1947 
1948   /**
1949    * Frees up assets associated with this ForceFieldEnergy, such as worker Threads.
1950    *
1951    * @return If successful in freeing up assets.
1952    */
1953   public boolean destroy() {
1954     if (destroyed) {
1955       // This regularly occurs with Repex OST, as multiple OrthogonalSpaceTempering objects wrap a
1956       // single FFE.
1957       logger.fine(format(" This ForceFieldEnergy is already destroyed: %s", this));
1958       return true;
1959     } else {
1960       try {
1961         if (parallelTeam != null) {
1962           parallelTeam.shutdown();
1963         }
1964         if (vanderWaals != null) {
1965           vanderWaals.destroy();
1966         }
1967         if (particleMeshEwald != null) {
1968           particleMeshEwald.destroy();
1969         }
1970         molecularAssembly.finishDestruction();
1971         destroyed = true;
1972         return true;
1973       } catch (Exception ex) {
1974         logger.warning(format(" Exception in shutting down a ForceFieldEnergy: %s", ex));
1975         logger.info(Utilities.stackTraceToString(ex));
1976         return false;
1977       }
1978     }
1979   }
1980 
1981   /**
1982    * energy.
1983    *
1984    * @return a double.
1985    */
1986   public double energy() {
1987     return energy(false, false);
1988   }
1989 
1990   /**
1991    * {@inheritDoc}
1992    *
1993    * <p>energy
1994    */
1995   public double energy(boolean gradient, boolean print) {
1996     try {
1997       totalTime = System.nanoTime();
1998       nnTime = 0;
1999       bondTime = 0;
2000       angleTime = 0;
2001       stretchBendTime = 0;
2002       ureyBradleyTime = 0;
2003       outOfPlaneBendTime = 0;
2004       torsionTime = 0;
2005       stretchTorsionTime = 0;
2006       angleTorsionTime = 0;
2007       piOrbitalTorsionTime = 0;
2008       torsionTorsionTime = 0;
2009       improperTorsionTime = 0;
2010       vanDerWaalsTime = 0;
2011       electrostaticTime = 0;
2012       restrainDistanceTime = 0;
2013       restrainTorsionTime = 0;
2014       restrainPositionTime = 0;
2015       restrainGroupTime = 0;
2016       ncsTime = 0;
2017 
2018       // Zero out the potential energy of each bonded term.
2019       nnEnergy = 0.0;
2020       bondEnergy = 0.0;
2021       angleEnergy = 0.0;
2022       stretchBendEnergy = 0.0;
2023       ureyBradleyEnergy = 0.0;
2024       outOfPlaneBendEnergy = 0.0;
2025       torsionEnergy = 0.0;
2026       angleTorsionEnergy = 0.0;
2027       stretchTorsionEnergy = 0.0;
2028       piOrbitalTorsionEnergy = 0.0;
2029       torsionTorsionEnergy = 0.0;
2030       improperTorsionEnergy = 0.0;
2031       totalBondedEnergy = 0.0;
2032 
2033       // Zero out potential energy of restraint terms
2034       restrainDistanceEnergy = 0.0;
2035       restrainTorsionEnergy = 0.0;
2036       restrainPositionEnergy = 0.0;
2037       restrainGroupEnergy = 0.0;
2038       ncsEnergy = 0.0;
2039 
2040       // Zero out bond and angle RMSDs.
2041       bondRMSD = 0.0;
2042       angleRMSD = 0.0;
2043 
2044       // Zero out the potential energy of each non-bonded term.
2045       vanDerWaalsEnergy = 0.0;
2046       permanentMultipoleEnergy = 0.0;
2047       permanentRealSpaceEnergy = 0.0;
2048       polarizationEnergy = 0.0;
2049       totalMultipoleEnergy = 0.0;
2050       totalNonBondedEnergy = 0.0;
2051 
2052       // Zero out the solvation energy.
2053       solvationEnergy = 0.0;
2054 
2055       // Zero out the relative solvation energy (sequence optimization)
2056       relativeSolvationEnergy = 0.0;
2057       nRelativeSolvations = 0;
2058 
2059       esvBias = 0.0;
2060 
2061       // Zero out the total potential energy.
2062       totalEnergy = 0.0;
2063 
2064       // Zero out the Cartesian coordinate gradient for each atom.
2065       //            if (gradient) {
2066       //                for (int i = 0; i < nAtoms; i++) {
2067       //                    atoms[i].setXYZGradient(0.0, 0.0, 0.0);
2068       //                    atoms[i].setLambdaXYZGradient(0.0, 0.0, 0.0);
2069       //                }
2070       //            }
2071 
2072       // Computed the bonded energy terms in parallel.
2073       try {
2074         bondedRegion.setGradient(gradient);
2075         parallelTeam.execute(bondedRegion);
2076       } catch (RuntimeException ex) {
2077         logger.warning("Runtime exception during bonded term calculation.");
2078         throw ex;
2079       } catch (Exception ex) {
2080         logger.info(Utilities.stackTraceToString(ex));
2081         logger.severe(ex.toString());
2082       }
2083 
2084       if (!lambdaBondedTerms) {
2085         // Compute restraint terms.
2086         if (ncsTerm) {
2087           ncsTime = -System.nanoTime();
2088           ncsEnergy = ncsRestraint.residual(gradient, print);
2089           ncsTime += System.nanoTime();
2090         }
2091 
2092         /*
2093         if (restrainPositionTerm) {
2094           restrainPositionTime = -System.nanoTime();
2095           for (RestrainPosition restrainPosition : restrainPositions) {
2096             restrainPositionEnergy += restrainPosition.residual(gradient);
2097           }
2098           restrainPositionTime += System.nanoTime();
2099         }
2100         */
2101 
2102         if (restrainGroupTerm) {
2103           restrainGroupTime = -System.nanoTime();
2104           restrainGroupEnergy = restrainGroups.energy(gradient);
2105           restrainGroupTime += System.nanoTime();
2106         }
2107 
2108         if (comTerm) {
2109           comRestraintTime = -System.nanoTime();
2110           comRestraintEnergy = comRestraint.residual(gradient, print);
2111           comRestraintTime += System.nanoTime();
2112         }
2113 
2114 
2115         // Compute the neural network term.
2116         if (nnTerm) {
2117           nnTime = -System.nanoTime();
2118           nnEnergy = aniEnergy.energy(gradient, print);
2119           nnTime += System.nanoTime();
2120         }
2121 
2122         // Compute non-bonded terms.
2123         if (vanderWaalsTerm) {
2124           vanDerWaalsTime = -System.nanoTime();
2125           vanDerWaalsEnergy = vanderWaals.energy(gradient, print);
2126           nVanDerWaalInteractions = this.vanderWaals.getInteractions();
2127           vanDerWaalsTime += System.nanoTime();
2128         }
2129 
2130         if (multipoleTerm) {
2131           electrostaticTime = -System.nanoTime();
2132           totalMultipoleEnergy = particleMeshEwald.energy(gradient, print);
2133           permanentMultipoleEnergy = particleMeshEwald.getPermanentEnergy();
2134           permanentRealSpaceEnergy = particleMeshEwald.getPermRealEnergy();
2135           polarizationEnergy = particleMeshEwald.getPolarizationEnergy();
2136           nPermanentInteractions = particleMeshEwald.getInteractions();
2137           solvationEnergy = particleMeshEwald.getSolvationEnergy();
2138           nGKInteractions = particleMeshEwald.getGKInteractions();
2139           electrostaticTime += System.nanoTime();
2140         }
2141       }
2142 
2143       if (relativeSolvationTerm) {
2144         List<Residue> residuesList = molecularAssembly.getResidueList();
2145         for (Residue residue : residuesList) {
2146           if (residue instanceof MultiResidue) {
2147             Atom refAtom = residue.getSideChainAtoms().get(0);
2148             if (refAtom != null && refAtom.getUse()) {
2149               // Reasonably confident that it should be -=,
2150               // as we are trying to penalize residues with strong solvation energy.
2151               double thisSolvation = relativeSolvation.getSolvationEnergy(residue, false);
2152               relativeSolvationEnergy -= thisSolvation;
2153               if (thisSolvation != 0) {
2154                 nRelativeSolvations++;
2155               }
2156             }
2157           }
2158         }
2159       }
2160 
2161       totalTime = System.nanoTime() - totalTime;
2162 
2163       totalBondedEnergy = nnEnergy + bondEnergy + angleEnergy + stretchBendEnergy + ureyBradleyEnergy
2164           + outOfPlaneBendEnergy + torsionEnergy + angleTorsionEnergy + stretchTorsionEnergy
2165           + piOrbitalTorsionEnergy + improperTorsionEnergy + torsionTorsionEnergy
2166           + ncsEnergy + comRestraintEnergy
2167           + restrainDistanceEnergy + restrainPositionEnergy + restrainGroupEnergy + restrainTorsionEnergy ;
2168 
2169       totalNonBondedEnergy = vanDerWaalsEnergy + totalMultipoleEnergy + relativeSolvationEnergy;
2170       totalEnergy = totalBondedEnergy + totalNonBondedEnergy + solvationEnergy;
2171       if (esvTerm) {
2172         esvBias = esvSystem.getBiasEnergy();
2173         totalEnergy += esvBias;
2174       }
2175 
2176       if (print) {
2177         StringBuilder sb = new StringBuilder();
2178         if (gradient) {
2179           sb.append("\n Computed Potential Energy and Atomic Coordinate Gradients\n");
2180         } else {
2181           sb.append("\n Computed Potential Energy\n");
2182         }
2183         sb.append(this);
2184         logger.info(sb.toString());
2185       }
2186       return totalEnergy;
2187     } catch (EnergyException ex) {
2188       if (printOnFailure) {
2189         printFailure();
2190       }
2191       if (ex.doCauseSevere()) {
2192         logger.info(Utilities.stackTraceToString(ex));
2193         logger.log(Level.SEVERE, " Error in calculating energies or gradients", ex);
2194       } else {
2195         logger.log(Level.INFO, format(" Exception in energy calculation:\n %s", ex));
2196       }
2197       throw ex;
2198     }
2199   }
2200 
2201   /**
2202    * {@inheritDoc}
2203    */
2204   @Override
2205   public double energy(double[] x) {
2206     return energy(x, false);
2207   }
2208 
2209   /**
2210    * {@inheritDoc}
2211    */
2212   @Override
2213   public double energy(double[] x, boolean verbose) {
2214     assert Arrays.stream(x).allMatch(Double::isFinite);
2215 
2216     // Unscale the coordinates.
2217     unscaleCoordinates(x);
2218 
2219     // Set coordinates.
2220     setCoordinates(x);
2221 
2222     double e = this.energy(false, verbose);
2223 
2224     // Rescale the coordinates.
2225     scaleCoordinates(x);
2226 
2227     return e;
2228   }
2229 
2230   /**
2231    * {@inheritDoc}
2232    */
2233   @Override
2234   public double energyAndGradient(double[] x, double[] g) {
2235     return energyAndGradient(x, g, false);
2236   }
2237 
2238   /**
2239    * {@inheritDoc}
2240    */
2241   @Override
2242   public double energyAndGradient(double[] x, double[] g, boolean verbose) {
2243     assert Arrays.stream(x).allMatch(Double::isFinite);
2244     // Un-scale the coordinates.
2245     unscaleCoordinates(x);
2246     // Set coordinates.
2247     setCoordinates(x);
2248     double e = energy(true, verbose);
2249 
2250     // Try block already exists inside energy(boolean, boolean), so only
2251     // need to try-catch fillGradient.
2252     try {
2253       fillGradient(g);
2254 
2255       // Scale the coordinates and gradients.
2256       scaleCoordinatesAndGradient(x, g);
2257 
2258       if (maxDebugGradient < Double.MAX_VALUE) {
2259         boolean extremeGrad = Arrays.stream(g)
2260             .anyMatch((double gi) -> (gi > maxDebugGradient || gi < -maxDebugGradient));
2261         if (extremeGrad) {
2262           File origFile = molecularAssembly.getFile();
2263           String timeString = LocalDateTime.now()
2264               .format(DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss"));
2265 
2266           String filename = format("%s-LARGEGRAD-%s.pdb",
2267               removeExtension(molecularAssembly.getFile().getName()), timeString);
2268           PotentialsFunctions ef = new PotentialsUtils();
2269           filename = ef.versionFile(filename);
2270 
2271           logger.warning(format(" Excessively large gradient detected; printing snapshot to file %s",
2272               filename));
2273           ef.saveAsPDB(molecularAssembly, new File(filename));
2274           molecularAssembly.setFile(origFile);
2275         }
2276       }
2277       return e;
2278     } catch (EnergyException ex) {
2279       if (printOnFailure) {
2280         printFailure();
2281       }
2282       if (ex.doCauseSevere()) {
2283         logger.info(Utilities.stackTraceToString(ex));
2284         logger.log(Level.SEVERE, " Error in calculating energies or gradients", ex);
2285       } else {
2286         logger.log(Level.INFO, format(" Exception in energy calculation:\n %s", ex));
2287       }
2288       throw ex;
2289     }
2290   }
2291 
2292   /**
2293    * Save coordinates when an EnergyException is caught.
2294    */
2295   private void printFailure() {
2296     String timeString = LocalDateTime.now()
2297         .format(DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss"));
2298     File file = molecularAssembly.getFile();
2299     String ext = "pdb";
2300     if (isXYZ(file)) {
2301       ext = "xyz";
2302     }
2303     String filename = format("%s-ERROR-%s.%s", removeExtension(file.getName()), timeString, ext);
2304     PotentialsFunctions ef = new PotentialsUtils();
2305     filename = ef.versionFile(filename);
2306     logger.info(format(" Writing on-error snapshot to file %s", filename));
2307     ef.save(molecularAssembly, new File(filename));
2308   }
2309 
2310   /**
2311    * {@inheritDoc}
2312    *
2313    * <p>Returns an array of acceleration values for active atoms.
2314    */
2315   @Override
2316   public double[] getAcceleration(double[] acceleration) {
2317     int n = getNumberOfVariables();
2318     if (acceleration == null || acceleration.length < n) {
2319       acceleration = new double[n];
2320     }
2321     int index = 0;
2322     double[] a = new double[3];
2323     for (int i = 0; i < nAtoms; i++) {
2324       if (atoms[i].isActive()) {
2325         atoms[i].getAcceleration(a);
2326         acceleration[index++] = a[0];
2327         acceleration[index++] = a[1];
2328         acceleration[index++] = a[2];
2329       }
2330     }
2331     return acceleration;
2332   }
2333 
2334   /**
2335    * Getter for the field <code>angleEnergy</code>.
2336    *
2337    * @return a double.
2338    */
2339   public double getAngleEnergy() {
2340     return angleEnergy;
2341   }
2342 
2343   /**
2344    * Getter for the field <code>angleTorsionEnergy</code>.
2345    *
2346    * @return a double.
2347    */
2348   public double getAngleTorsionEnergy() {
2349     return angleTorsionEnergy;
2350   }
2351 
2352   /**
2353    * Getter for the field <code>angleTorsions</code>.
2354    *
2355    * @return an array of {@link ffx.potential.bonded.AngleTorsion} objects.
2356    */
2357   public AngleTorsion[] getAngleTorsions() {
2358     return angleTorsions;
2359   }
2360 
2361   /**
2362    * Getter for the field <code>angles</code>. Both normal and in-plane angles are returned.
2363    *
2364    * @return an array of {@link ffx.potential.bonded.Angle} objects.
2365    */
2366   public Angle[] getAngles() {
2367     return angles;
2368   }
2369 
2370   public String getAngleEnergyString() {
2371     AngleType angleType = angles[0].angleType;
2372     String energy;
2373     if (angleType.angleFunction == AngleType.AngleFunction.SEXTIC) {
2374       energy = format("""
2375               k*(d^2 + %.15g*d^3 + %.15g*d^4 + %.15g*d^5 + %.15g*d^6);
2376               d=%.15g*theta-theta0;
2377               """,
2378           angleType.cubic, angleType.quartic, angleType.pentic, angleType.sextic, 180.0 / PI);
2379     } else {
2380       energy = format("""
2381               k*(d^2);
2382               d=%.15g*theta-theta0;
2383               """,
2384           180.0 / PI);
2385     }
2386     return energy;
2387   }
2388 
2389   public String getInPlaneAngleEnergyString() {
2390     AngleType angleType = angles[0].angleType;
2391     String energy = format("""
2392             k*(d^2 + %.15g*d^3 + %.15g*d^4 + %.15g*d^5 + %.15g*d^6);
2393             d=theta-theta0;
2394             theta = %.15g*pointangle(x1, y1, z1, projx, projy, projz, x3, y3, z3);
2395             projx = x2-nx*dot;
2396             projy = y2-ny*dot;
2397             projz = z2-nz*dot;
2398             dot = nx*(x2-x3) + ny*(y2-y3) + nz*(z2-z3);
2399             nx = px/norm;
2400             ny = py/norm;
2401             nz = pz/norm;
2402             norm = sqrt(px*px + py*py + pz*pz);
2403             px = (d1y*d2z-d1z*d2y);
2404             py = (d1z*d2x-d1x*d2z);
2405             pz = (d1x*d2y-d1y*d2x);
2406             d1x = x1-x4;
2407             d1y = y1-y4;
2408             d1z = z1-z4;
2409             d2x = x3-x4;
2410             d2y = y3-y4;
2411             d2z = z3-z4;
2412             """,
2413         angleType.cubic, angleType.quartic, angleType.pentic, angleType.sextic, 180.0 / PI);
2414     return energy;
2415   }
2416 
2417   /**
2418    * Getter for the field <code>angles</code> with only <code>AngleMode</code> angles.
2419    *
2420    * @param angleMode Only angles of this mode will be returned.
2421    * @return an array of {@link ffx.potential.bonded.Angle} objects.
2422    */
2423   public Angle[] getAngles(AngleMode angleMode) {
2424     if (angles == null || angles.length < 1) {
2425       return null;
2426     }
2427     int nAngles = angles.length;
2428     List<Angle> angleList = new ArrayList<>();
2429     // Sort all normal angles from in-plane angles
2430     for (int i = 0; i < nAngles; i++) {
2431       if (angles[i].getAngleMode() == angleMode) {
2432         angleList.add(angles[i]);
2433       }
2434     }
2435     nAngles = angleList.size();
2436     if (nAngles < 1) {
2437       return null;
2438     }
2439     return angleList.toArray(new Angle[0]);
2440   }
2441 
2442 
2443   /**
2444    * Getter for the field <code>nnEnergy</code>.
2445    *
2446    * @return a double.
2447    */
2448   public double getNeutralNetworkEnergy() {
2449     return nnEnergy;
2450   }
2451 
2452   /**
2453    * Getter for the field <code>bondEnergy</code>.
2454    *
2455    * @return a double.
2456    */
2457   public double getBondEnergy() {
2458     return bondEnergy;
2459   }
2460 
2461   /**
2462    * Getter for the field <code>bonds</code>.
2463    *
2464    * @return an array of {@link ffx.potential.bonded.Bond} objects.
2465    */
2466   public Bond[] getBonds() {
2467     return bonds;
2468   }
2469 
2470   public String getBondEnergyString() {
2471     BondType bondType = bonds[0].getBondType();
2472     String energy;
2473     if (bondType.bondFunction == BondType.BondFunction.QUARTIC) {
2474       energy = format("""
2475               k*(d^2 + %.15g*d^3 + %.15g*d^4);
2476               d=r-r0;
2477               """,
2478           bondType.cubic / OpenMM_NmPerAngstrom,
2479           bondType.quartic / (OpenMM_NmPerAngstrom * OpenMM_NmPerAngstrom));
2480     } else {
2481       energy = """
2482           k*(d^2);
2483           d=r-r0;
2484           """;
2485     }
2486     return energy;
2487   }
2488 
2489   /**
2490    * getCavitationEnergy.
2491    *
2492    * @return a double.
2493    */
2494   public double getCavitationEnergy() {
2495     return particleMeshEwald.getCavitationEnergy();
2496   }
2497 
2498   /**
2499    * Returns a copy of the list of constraints this ForceFieldEnergy has.
2500    *
2501    * @return Copied list of constraints.
2502    */
2503   @Override
2504   public List<Constraint> getConstraints() {
2505     return constraints.isEmpty() ? Collections.emptyList() : new ArrayList<>(constraints);
2506   }
2507 
2508   /**
2509    * Getter for the field <code>coordRestraints</code>.
2510    *
2511    * @return a {@link java.util.List} object.
2512    */
2513   public List<RestrainPosition> getRestrainPositions() {
2514     if (restrainPositions == null) {
2515       return Collections.emptyList();
2516     }
2517     return List.of(restrainPositions);
2518   }
2519 
2520   /**
2521    * {@inheritDoc}
2522    */
2523   @Override
2524   public double[] getCoordinates(double[] x) {
2525     int n = getNumberOfVariables();
2526     if (x == null || x.length < n) {
2527       x = new double[n];
2528     }
2529     int index = 0;
2530     for (int i = 0; i < nAtoms; i++) {
2531       Atom a = atoms[i];
2532       if (a.isActive()) {
2533         x[index++] = a.getX();
2534         x[index++] = a.getY();
2535         x[index++] = a.getZ();
2536       }
2537     }
2538     return x;
2539   }
2540 
2541   /**
2542    * {@inheritDoc}
2543    *
2544    * <p>Getter for the field <code>crystal</code>.
2545    */
2546   @Override
2547   public Crystal getCrystal() {
2548     return crystal;
2549   }
2550 
2551   /**
2552    * {@inheritDoc}
2553    *
2554    * <p>Set the boundary conditions for this calculation.
2555    */
2556   @Override
2557   public void setCrystal(Crystal crystal) {
2558     setCrystal(crystal, false);
2559   }
2560 
2561   /**
2562    * Getter for the field <code>cutoffPlusBuffer</code>.
2563    *
2564    * @return a double.
2565    */
2566   public double getCutoffPlusBuffer() {
2567     return cutoffPlusBuffer;
2568   }
2569 
2570   /**
2571    * getDispersionEnergy.
2572    *
2573    * @return a double.
2574    */
2575   public double getDispersionEnergy() {
2576     return particleMeshEwald.getDispersionEnergy();
2577   }
2578 
2579   /**
2580    * getElectrostaticEnergy.
2581    *
2582    * @return a double.
2583    */
2584   public double getElectrostaticEnergy() {
2585     return totalMultipoleEnergy;
2586   }
2587 
2588   /**
2589    * {@inheritDoc}
2590    */
2591   @Override
2592   public STATE getEnergyTermState() {
2593     return state;
2594   }
2595 
2596   /**
2597    * {@inheritDoc}
2598    *
2599    * <p>This method is for the RESPA integrator only.
2600    */
2601   @Override
2602   public void setEnergyTermState(STATE state) {
2603     this.state = state;
2604     switch (state) {
2605       case FAST:
2606         nnTerm = nnTermOrig;
2607         bondTerm = bondTermOrig;
2608         angleTerm = angleTermOrig;
2609         stretchBendTerm = stretchBendTermOrig;
2610         ureyBradleyTerm = ureyBradleyTermOrig;
2611         outOfPlaneBendTerm = outOfPlaneBendTermOrig;
2612         torsionTerm = torsionTermOrig;
2613         stretchTorsionTerm = stretchTorsionTermOrig;
2614         angleTorsionTerm = angleTorsionTermOrig;
2615         piOrbitalTorsionTerm = piOrbitalTorsionTermOrig;
2616         torsionTorsionTerm = torsionTorsionTermOrig;
2617         improperTorsionTerm = improperTorsionTermOrig;
2618         restrainDistanceTerm = restrainDistanceTermOrig;
2619         restrainTorsionTerm = restrainTorsionTermOrig;
2620         restrainPositionTerm = restrainPositionTermOrig;
2621         restrainGroupTerm = restrainGroupTermOrig;
2622         ncsTerm = ncsTermOrig;
2623         comTerm = comTermOrig;
2624         vanderWaalsTerm = false;
2625         multipoleTerm = false;
2626         polarizationTerm = false;
2627         generalizedKirkwoodTerm = false;
2628         esvTerm = false;
2629         break;
2630       case SLOW:
2631         vanderWaalsTerm = vanderWaalsTermOrig;
2632         multipoleTerm = multipoleTermOrig;
2633         polarizationTerm = polarizationTermOrig;
2634         generalizedKirkwoodTerm = generalizedKirkwoodTermOrig;
2635         esvTerm = esvTermOrig;
2636         nnTerm = false;
2637         bondTerm = false;
2638         angleTerm = false;
2639         stretchBendTerm = false;
2640         ureyBradleyTerm = false;
2641         outOfPlaneBendTerm = false;
2642         torsionTerm = false;
2643         stretchTorsionTerm = false;
2644         angleTorsionTerm = false;
2645         piOrbitalTorsionTerm = false;
2646         torsionTorsionTerm = false;
2647         improperTorsionTerm = false;
2648         restrainDistanceTerm = false;
2649         restrainPositionTerm = false;
2650         restrainGroupTerm = false;
2651         ncsTerm = false;
2652         comTerm = false;
2653         break;
2654       default:
2655         nnTerm = nnTermOrig;
2656         bondTerm = bondTermOrig;
2657         angleTerm = angleTermOrig;
2658         stretchBendTerm = stretchBendTermOrig;
2659         ureyBradleyTerm = ureyBradleyTermOrig;
2660         outOfPlaneBendTerm = outOfPlaneBendTermOrig;
2661         torsionTerm = torsionTermOrig;
2662         stretchTorsionTerm = stretchTorsionTermOrig;
2663         angleTorsionTerm = angleTorsionTermOrig;
2664         piOrbitalTorsionTerm = piOrbitalTorsionTermOrig;
2665         torsionTorsionTerm = torsionTorsionTermOrig;
2666         improperTorsionTerm = improperTorsionTermOrig;
2667         restrainDistanceTerm = restrainDistanceTermOrig;
2668         restrainTorsionTerm = restrainTorsionTermOrig;
2669         restrainPositionTerm = restrainPositionTermOrig;
2670         restrainGroupTerm = restrainGroupTermOrig;
2671         ncsTerm = ncsTermOrig;
2672         comTermOrig = comTerm;
2673         vanderWaalsTerm = vanderWaalsTermOrig;
2674         multipoleTerm = multipoleTermOrig;
2675         polarizationTerm = polarizationTermOrig;
2676         generalizedKirkwoodTerm = generalizedKirkwoodTermOrig;
2677     }
2678   }
2679 
2680   /**
2681    * getEsvBiasEnergy.
2682    *
2683    * @return a double.
2684    */
2685   public double getEsvBiasEnergy() {
2686     return esvBias;
2687   }
2688 
2689   /**
2690    * getExtendedSystem.
2691    *
2692    * @return a {@link ffx.potential.extended.ExtendedSystem} object.
2693    */
2694   public ExtendedSystem getExtendedSystem() {
2695     return esvSystem;
2696   }
2697 
2698   /**
2699    * getGK.
2700    *
2701    * @return a {@link ffx.potential.nonbonded.GeneralizedKirkwood} object.
2702    */
2703   public GeneralizedKirkwood getGK() {
2704     if (particleMeshEwald != null) {
2705       return particleMeshEwald.getGK();
2706     } else {
2707       return null;
2708     }
2709   }
2710 
2711   /**
2712    * getGKEnergy.
2713    *
2714    * @return a double.
2715    */
2716   public double getGKEnergy() {
2717     return particleMeshEwald.getGKEnergy();
2718   }
2719 
2720   /**
2721    * getGradient
2722    *
2723    * @param g an array of double.
2724    * @return an array of {@link double} objects.
2725    */
2726   public double[] getGradient(double[] g) {
2727     return fillGradient(g);
2728   }
2729 
2730   /**
2731    * Getter for the field <code>improperTorsionEnergy</code>.
2732    *
2733    * @return a double.
2734    */
2735   public double getImproperTorsionEnergy() {
2736     return improperTorsionEnergy;
2737   }
2738 
2739   /**
2740    * Getter for the field <code>improperTorsions</code>.
2741    *
2742    * @return an array of {@link ffx.potential.bonded.ImproperTorsion} objects.
2743    */
2744   public ImproperTorsion[] getImproperTorsions() {
2745     return improperTorsions;
2746   }
2747 
2748   /**
2749    * {@inheritDoc}
2750    */
2751   @Override
2752   public double getLambda() {
2753     return lambda;
2754   }
2755 
2756   /**
2757    * {@inheritDoc}
2758    */
2759   @Override
2760   public void setLambda(double lambda) {
2761     if (lambdaTerm) {
2762       if (lambda <= 1.0 && lambda >= 0.0) {
2763         this.lambda = lambda;
2764         if (vanderWaalsTerm) {
2765           vanderWaals.setLambda(lambda);
2766         }
2767         if (multipoleTerm) {
2768           particleMeshEwald.setLambda(lambda);
2769         }
2770 
2771         /*
2772         if (restrainPositionTerm && nRestrainPositions > 0) {
2773           for (RestrainPosition restrainPosition : restrainPositions) {
2774             restrainPosition.setLambda(lambda);
2775           }
2776         } */
2777 
2778         if (restrainDistanceTerm && nRestrainDistances > 0) {
2779           for (RestrainDistance restrainDistance : restrainDistances) {
2780             restrainDistance.setLambda(lambda);
2781           }
2782         }
2783         if (restrainTorsionTerm && nRestrainTorsions > 0) {
2784           for (RestraintTorsion restraintTorsion : restrainTorsions) {
2785             restraintTorsion.setLambda(lambda);
2786           }
2787         }
2788 
2789         if (ncsTerm && ncsRestraint != null) {
2790           ncsRestraint.setLambda(lambda);
2791         }
2792         if (comTerm && comRestraint != null) {
2793           comRestraint.setLambda(lambda);
2794         }
2795         if (lambdaTorsions) {
2796           for (int i = 0; i < nTorsions; i++) {
2797             torsions[i].setLambda(lambda);
2798           }
2799           for (int i = 0; i < nPiOrbitalTorsions; i++) {
2800             piOrbitalTorsions[i].setLambda(lambda);
2801           }
2802           for (int i = 0; i < nTorsionTorsions; i++) {
2803             torsionTorsions[i].setLambda(lambda);
2804           }
2805         }
2806       } else {
2807         String message = format("Lambda value %8.3f is not in the range [0..1].", lambda);
2808         logger.warning(message);
2809       }
2810     } else {
2811       logger.fine(" Attempting to set a lambda value on a ForceFieldEnergy with lambdaterm false.");
2812     }
2813   }
2814 
2815   /**
2816    * {@inheritDoc}
2817    */
2818   @Override
2819   public double[] getMass() {
2820     int n = getNumberOfVariables();
2821     double[] mass = new double[n];
2822     int index = 0;
2823     for (int i = 0; i < nAtoms; i++) {
2824       Atom a = atoms[i];
2825       if (a.isActive()) {
2826         double m = a.getMass();
2827         mass[index++] = m;
2828         mass[index++] = m;
2829         mass[index++] = m;
2830       }
2831     }
2832     return mass;
2833   }
2834 
2835   /**
2836    * {@inheritDoc}
2837    */
2838   @Override
2839   public int getNumberOfVariables() {
2840     int nActive = 0;
2841     for (int i = 0; i < nAtoms; i++) {
2842       if (atoms[i].isActive()) {
2843         nActive++;
2844       }
2845     }
2846     return nActive * 3;
2847   }
2848 
2849   /**
2850    * getNumberofAngleTorsions.
2851    *
2852    * @return a int.
2853    */
2854   public int getNumberofAngleTorsions() {
2855     return nAngleTorsions;
2856   }
2857 
2858   /**
2859    * getNumberofAngles.
2860    *
2861    * @return a int.
2862    */
2863   public int getNumberofAngles() {
2864     return nAngles;
2865   }
2866 
2867   /**
2868    * getNumberofBonds.
2869    *
2870    * @return a int.
2871    */
2872   public int getNumberofBonds() {
2873     return nBonds;
2874   }
2875 
2876   /**
2877    * getNumberofImproperTorsions.
2878    *
2879    * @return a int.
2880    */
2881   public int getNumberofImproperTorsions() {
2882     return nImproperTorsions;
2883   }
2884 
2885   /**
2886    * getNumberofOutOfPlaneBends.
2887    *
2888    * @return a int.
2889    */
2890   public int getNumberofOutOfPlaneBends() {
2891     return nOutOfPlaneBends;
2892   }
2893 
2894   /**
2895    * getNumberofPiOrbitalTorsions.
2896    *
2897    * @return a int.
2898    */
2899   public int getNumberofPiOrbitalTorsions() {
2900     return nPiOrbitalTorsions;
2901   }
2902 
2903   /**
2904    * getNumberofStretchBends.
2905    *
2906    * @return a int.
2907    */
2908   public int getNumberofStretchBends() {
2909     return nStretchBends;
2910   }
2911 
2912   /**
2913    * getNumberofStretchTorsions.
2914    *
2915    * @return a int.
2916    */
2917   public int getNumberofStretchTorsions() {
2918     return nStretchTorsions;
2919   }
2920 
2921   /**
2922    * getNumberofTorsionTorsions.
2923    *
2924    * @return a int.
2925    */
2926   public int getNumberofTorsionTorsions() {
2927     return nTorsionTorsions;
2928   }
2929 
2930   /**
2931    * getNumberofTorsions.
2932    *
2933    * @return a int.
2934    */
2935   public int getNumberofTorsions() {
2936     return nTorsions;
2937   }
2938 
2939   /**
2940    * getNumberofUreyBradleys.
2941    *
2942    * @return a int.
2943    */
2944   public int getNumberofUreyBradleys() {
2945     return nUreyBradleys;
2946   }
2947 
2948   /**
2949    * Getter for the field <code>outOfPlaneBendEnergy</code>.
2950    *
2951    * @return a double.
2952    */
2953   public double getOutOfPlaneBendEnergy() {
2954     return outOfPlaneBendEnergy;
2955   }
2956 
2957   /**
2958    * Getter for the field <code>outOfPlaneBends</code>.
2959    *
2960    * @return an array of {@link ffx.potential.bonded.OutOfPlaneBend} objects.
2961    */
2962   public OutOfPlaneBend[] getOutOfPlaneBends() {
2963     return outOfPlaneBends;
2964   }
2965 
2966   public String getOutOfPlaneEnergyString() {
2967     OutOfPlaneBendType outOfPlaneBendType = outOfPlaneBends[0].outOfPlaneBendType;
2968     String energy = format(""" 
2969             k*(theta^2 + %.15g*theta^3 + %.15g*theta^4 + %.15g*theta^5 + %.15g*theta^6);
2970             theta = %.15g*pointangle(x2, y2, z2, x4, y4, z4, projx, projy, projz);
2971             projx = x2-nx*dot;
2972             projy = y2-ny*dot;
2973             projz = z2-nz*dot;
2974             dot = nx*(x2-x3) + ny*(y2-y3) + nz*(z2-z3);
2975             nx = px/norm;
2976             ny = py/norm;
2977             nz = pz/norm;
2978             norm = sqrt(px*px + py*py + pz*pz);
2979             px = (d1y*d2z-d1z*d2y);
2980             py = (d1z*d2x-d1x*d2z);
2981             pz = (d1x*d2y-d1y*d2x);
2982             d1x = x1-x4;
2983             d1y = y1-y4;
2984             d1z = z1-z4;
2985             d2x = x3-x4;
2986             d2y = y3-y4;
2987             d2z = z3-z4
2988             """,
2989         outOfPlaneBendType.cubic, outOfPlaneBendType.quartic,
2990         outOfPlaneBendType.pentic, outOfPlaneBendType.sextic, 180.0 / PI);
2991     return energy;
2992   }
2993 
2994   /**
2995    * getPDBHeaderString
2996    *
2997    * @return a {@link java.lang.String} object.
2998    */
2999   public String getPDBHeaderString() {
3000     energy(false, false);
3001     StringBuilder sb = new StringBuilder();
3002     sb.append("REMARK   3  CALCULATED POTENTIAL ENERGY\n");
3003     if (nnTerm) {
3004       sb.append(
3005           format("REMARK   3   %s %g (%d)\n", "NEUTRAL NETWORK            : ", nnEnergy, nAtoms));
3006     }
3007     if (bondTerm) {
3008       sb.append(
3009           format("REMARK   3   %s %g (%d)\n", "BOND STRETCHING            : ", bondEnergy, nBonds));
3010       sb.append(format("REMARK   3   %s %g\n", "BOND RMSD                  : ", bondRMSD));
3011     }
3012     if (angleTerm) {
3013       sb.append(format("REMARK   3   %s %g (%d)\n", "ANGLE BENDING              : ", angleEnergy,
3014           nAngles));
3015       sb.append(format("REMARK   3   %s %g\n", "ANGLE RMSD                 : ", angleRMSD));
3016     }
3017     if (stretchBendTerm) {
3018       sb.append(
3019           format("REMARK   3   %s %g (%d)\n", "STRETCH-BEND               : ", stretchBendEnergy,
3020               nStretchBends));
3021     }
3022     if (ureyBradleyTerm) {
3023       sb.append(
3024           format("REMARK   3   %s %g (%d)\n", "UREY-BRADLEY               : ", ureyBradleyEnergy,
3025               nUreyBradleys));
3026     }
3027     if (outOfPlaneBendTerm) {
3028       sb.append(
3029           format("REMARK   3   %s %g (%d)\n", "OUT-OF-PLANE BEND          : ", outOfPlaneBendEnergy,
3030               nOutOfPlaneBends));
3031     }
3032     if (torsionTerm) {
3033       sb.append(format("REMARK   3   %s %g (%d)\n", "TORSIONAL ANGLE            : ", torsionEnergy,
3034           nTorsions));
3035     }
3036     if (piOrbitalTorsionTerm) {
3037       sb.append(format("REMARK   3   %s %g (%d)\n", "PI-ORBITAL TORSION         : ",
3038           piOrbitalTorsionEnergy, nPiOrbitalTorsions));
3039     }
3040     if (torsionTorsionTerm) {
3041       sb.append(
3042           format("REMARK   3   %s %g (%d)\n", "TORSION-TORSION            : ", torsionTorsionEnergy,
3043               nTorsionTorsions));
3044     }
3045     if (improperTorsionTerm) {
3046       sb.append(
3047           format("REMARK   3   %s %g (%d)\n", "IMPROPER TORSION           : ", improperTorsionEnergy,
3048               nImproperTorsions));
3049     }
3050     if (restrainDistanceTerm) {
3051       sb.append(format("REMARK   3   %s %g (%d)\n", "RESTRAINT BOND STRETCHING            : ",
3052           restrainDistanceEnergy, nRestrainDistances));
3053     }
3054 
3055     if (ncsTerm) {
3056       sb.append(
3057           format("REMARK   3   %s %g (%d)\n", "NCS RESTRAINT              : ", ncsEnergy, nAtoms));
3058     }
3059 
3060     if (restrainPositionTerm && nRestrainPositions > 0) {
3061       int nRests = 0;
3062       for (RestrainPosition restrainPosition : restrainPositions) {
3063         nRests += restrainPosition.getNumAtoms();
3064       }
3065       sb.append(format("REMARK   3   %s %g (%d)\n", "COORDINATE RESTRAINTS      : ", restrainPositionEnergy,
3066           nRests));
3067     }
3068 
3069     if (comTerm) {
3070       sb.append(
3071           format("REMARK   3   %s %g (%d)\n", "COM RESTRAINT              : ", comRestraintEnergy,
3072               nAtoms));
3073     }
3074 
3075     if (vanderWaalsTerm) {
3076       sb.append(
3077           format("REMARK   3   %s %g (%d)\n", "VAN DER WAALS              : ", vanDerWaalsEnergy,
3078               nVanDerWaalInteractions));
3079     }
3080     if (multipoleTerm) {
3081       sb.append(format("REMARK   3   %s %g (%d)\n", "ATOMIC MULTIPOLES          : ",
3082           permanentMultipoleEnergy, nPermanentInteractions));
3083     }
3084     if (polarizationTerm) {
3085       sb.append(format("REMARK   3   %s %g (%d)\n", "POLARIZATION               : ",
3086           polarizationEnergy, nPermanentInteractions));
3087     }
3088     sb.append(format("REMARK   3   %s %g\n", "TOTAL POTENTIAL (KCAL/MOL) : ", totalEnergy));
3089     int nsymm = crystal.getUnitCell().spaceGroup.getNumberOfSymOps();
3090     if (nsymm > 1) {
3091       sb.append(format("REMARK   3   %s %g\n", "UNIT CELL POTENTIAL        : ", totalEnergy * nsymm));
3092     }
3093     if (crystal.getUnitCell() != crystal) {
3094       nsymm = crystal.spaceGroup.getNumberOfSymOps();
3095       if (nsymm > 1) {
3096         sb.append(format("REMARK   3   %s %g\n", "REPLICATES CELL POTENTIAL  : ", totalEnergy * nsymm));
3097       }
3098     }
3099     sb.append("REMARK   3\n");
3100 
3101     return sb.toString();
3102   }
3103 
3104   /**
3105    * Getter for the field <code>parallelTeam</code>.
3106    *
3107    * @return a {@link edu.rit.pj.ParallelTeam} object.
3108    */
3109   public ParallelTeam getParallelTeam() {
3110     return parallelTeam;
3111   }
3112 
3113   /**
3114    * getPermanentInteractions.
3115    *
3116    * @return a int.
3117    */
3118   public int getPermanentInteractions() {
3119     return nPermanentInteractions;
3120   }
3121 
3122   /**
3123    * Getter for the field <code>permanentMultipoleEnergy</code>.
3124    *
3125    * @return a double.
3126    */
3127   public double getPermanentMultipoleEnergy() {
3128     return permanentMultipoleEnergy;
3129   }
3130 
3131   /**
3132    * Getter for the field <code>permanentRealSpaceEnergy</code>.
3133    *
3134    * @return a double.
3135    */
3136   public double getPermanentRealSpaceEnergy() {
3137     return permanentRealSpaceEnergy;
3138   }
3139 
3140   /**
3141    * getPermanentReciprocalMpoleEnergy.
3142    *
3143    * @return a double.
3144    */
3145   public double getPermanentReciprocalMpoleEnergy() {
3146     return particleMeshEwald.getPermRecipEnergy();
3147   }
3148 
3149   /**
3150    * getPermanentReciprocalSelfEnergy.
3151    *
3152    * @return a double.
3153    */
3154   public double getPermanentReciprocalSelfEnergy() {
3155     return particleMeshEwald.getPermSelfEnergy();
3156   }
3157 
3158   /**
3159    * Getter for the field <code>piOrbitalTorsionEnergy</code>.
3160    *
3161    * @return a double.
3162    */
3163   public double getPiOrbitalTorsionEnergy() {
3164     return piOrbitalTorsionEnergy;
3165   }
3166 
3167   /**
3168    * Getter for the field <code>piOrbitalTorsions</code>.
3169    *
3170    * @return an array of {@link ffx.potential.bonded.PiOrbitalTorsion} objects.
3171    */
3172   public PiOrbitalTorsion[] getPiOrbitalTorsions() {
3173     return piOrbitalTorsions;
3174   }
3175 
3176   public String getPiOrbitalTorsionEnergyString() {
3177     String energy = """
3178         2*k*sin(phi)^2;
3179         phi = pointdihedral(x3+c1x, y3+c1y, z3+c1z, x3, y3, z3, x4, y4, z4, x4+c2x, y4+c2y, z4+c2z);
3180         c1x = (d14y*d24z-d14z*d24y);
3181         c1y = (d14z*d24x-d14x*d24z);
3182         c1z = (d14x*d24y-d14y*d24x);
3183         c2x = (d53y*d63z-d53z*d63y);
3184         c2y = (d53z*d63x-d53x*d63z);
3185         c2z = (d53x*d63y-d53y*d63x);
3186         d14x = x1-x4;
3187         d14y = y1-y4;
3188         d14z = z1-z4;
3189         d24x = x2-x4;
3190         d24y = y2-y4;
3191         d24z = z2-z4;
3192         d53x = x5-x3;
3193         d53y = y5-y3;
3194         d53z = z5-z3;
3195         d63x = x6-x3;
3196         d63y = y6-y3;
3197         d63z = z6-z3;
3198         """;
3199     return energy;
3200   }
3201 
3202   /**
3203    * Gets the Platform associated with this force field energy. For the reference platform, always
3204    * returns FFX.
3205    *
3206    * @return A Platform.
3207    */
3208   public Platform getPlatform() {
3209     return platform;
3210   }
3211 
3212   /**
3213    * getPmeNode.
3214    *
3215    * @return a {@link ParticleMeshEwald} object.
3216    */
3217   public ParticleMeshEwald getPmeNode() {
3218     return particleMeshEwald;
3219   }
3220 
3221   /**
3222    * Getter for the field <code>polarizationEnergy</code>.
3223    *
3224    * @return a double.
3225    */
3226   public double getPolarizationEnergy() {
3227     return polarizationEnergy;
3228   }
3229 
3230 
3231   /**
3232    * {@inheritDoc}
3233    *
3234    * <p>Returns an array of previous acceleration values for active atoms.
3235    */
3236   @Override
3237   public double[] getPreviousAcceleration(double[] previousAcceleration) {
3238     int n = getNumberOfVariables();
3239     if (previousAcceleration == null || previousAcceleration.length < n) {
3240       previousAcceleration = new double[n];
3241     }
3242     int index = 0;
3243     double[] a = new double[3];
3244     for (int i = 0; i < nAtoms; i++) {
3245       if (atoms[i].isActive()) {
3246         atoms[i].getPreviousAcceleration(a);
3247         previousAcceleration[index++] = a[0];
3248         previousAcceleration[index++] = a[1];
3249         previousAcceleration[index++] = a[2];
3250       }
3251     }
3252     return previousAcceleration;
3253   }
3254 
3255   /**
3256    * Getter for the field <code>relativeSolvationEnergy</code>.
3257    *
3258    * @return a double.
3259    */
3260   public double getRelativeSolvationEnergy() {
3261     return relativeSolvationEnergy;
3262   }
3263 
3264   /**
3265    * {@inheritDoc}
3266    */
3267   @Override
3268   public double[] getScaling() {
3269     return optimizationScaling;
3270   }
3271 
3272   /**
3273    * {@inheritDoc}
3274    */
3275   @Override
3276   public void setScaling(double[] scaling) {
3277     optimizationScaling = scaling;
3278   }
3279 
3280   /**
3281    * Getter for the field <code>solvationEnergy</code>.
3282    *
3283    * @return a double.
3284    */
3285   public double getSolvationEnergy() {
3286     return solvationEnergy;
3287   }
3288 
3289   /**
3290    * getSolvationInteractions.
3291    *
3292    * @return a int.
3293    */
3294   public int getSolvationInteractions() {
3295     return nGKInteractions;
3296   }
3297 
3298   /**
3299    * getStrenchBendEnergy.
3300    *
3301    * @return a double.
3302    */
3303   public double getStrenchBendEnergy() {
3304     return stretchBendEnergy;
3305   }
3306 
3307   /**
3308    * Getter for the field <code>stretchBends</code>.
3309    *
3310    * @return an array of {@link ffx.potential.bonded.StretchBend} objects.
3311    */
3312   public StretchBend[] getStretchBends() {
3313     return stretchBends;
3314   }
3315 
3316   public String getStretchBendEnergyString() {
3317     String energy = format("(k1*(distance(p1,p2)-r12) + k2*(distance(p2,p3)-r23))*(%.15g*(angle(p1,p2,p3)-theta0))", 180.0 / PI);
3318     return energy;
3319   }
3320 
3321   /**
3322    * Getter for the field <code>stretchTorsionEnergy</code>.
3323    *
3324    * @return a double.
3325    */
3326   public double getStretchTorsionEnergy() {
3327     return stretchTorsionEnergy;
3328   }
3329 
3330   /**
3331    * Getter for the field <code>stretchTorsions</code>.
3332    *
3333    * @return an array of {@link ffx.potential.bonded.StretchTorsion} objects.
3334    */
3335   public StretchTorsion[] getStretchTorsions() {
3336     return stretchTorsions;
3337   }
3338 
3339   /**
3340    * Getter for the field <code>torsionEnergy</code>.
3341    *
3342    * @return a double.
3343    */
3344   public double getTorsionEnergy() {
3345     return torsionEnergy;
3346   }
3347 
3348   /**
3349    * Getter for the field <code>torsionTorsionEnergy</code>.
3350    *
3351    * @return a double.
3352    */
3353   public double getTorsionTorsionEnergy() {
3354     return torsionTorsionEnergy;
3355   }
3356 
3357   /**
3358    * Getter for the field <code>torsionTorsions</code>.
3359    *
3360    * @return an array of {@link ffx.potential.bonded.TorsionTorsion} objects.
3361    */
3362   public TorsionTorsion[] getTorsionTorsions() {
3363     return torsionTorsions;
3364   }
3365 
3366   /**
3367    * Getter for the field <code>torsions</code>.
3368    *
3369    * @return an array of {@link ffx.potential.bonded.Torsion} objects.
3370    */
3371   public Torsion[] getTorsions() {
3372     return torsions;
3373   }
3374 
3375   /**
3376    * getTotalElectrostaticEnergy.
3377    *
3378    * @return a double.
3379    */
3380   public double getTotalElectrostaticEnergy() {
3381     return totalMultipoleEnergy + solvationEnergy;
3382   }
3383 
3384   /**
3385    * {@inheritDoc}
3386    */
3387   @Override
3388   public double getTotalEnergy() {
3389     return totalEnergy;
3390   }
3391 
3392   /**
3393    * Getter for the field <code>ureyBradleyEnergy</code>.
3394    *
3395    * @return a double.
3396    */
3397   public double getUreyBradleyEnergy() {
3398     return ureyBradleyEnergy;
3399   }
3400 
3401   /**
3402    * Getter for the field <code>ureyBradleys</code>.
3403    *
3404    * @return an array of {@link ffx.potential.bonded.UreyBradley} objects.
3405    */
3406   public UreyBradley[] getUreyBradleys() {
3407     return ureyBradleys;
3408   }
3409 
3410   /**
3411    * Getter for the field <code>vanDerWaalsEnergy</code>.
3412    *
3413    * @return a double.
3414    */
3415   public double getVanDerWaalsEnergy() {
3416     return vanDerWaalsEnergy;
3417   }
3418 
3419   /**
3420    * getVanDerWaalsInteractions.
3421    *
3422    * @return a int.
3423    */
3424   public int getVanDerWaalsInteractions() {
3425     return nVanDerWaalInteractions;
3426   }
3427 
3428   /**
3429    * {@inheritDoc}
3430    *
3431    * <p>Return a reference to each variables type.
3432    */
3433   @Override
3434   public VARIABLE_TYPE[] getVariableTypes() {
3435     int n = getNumberOfVariables();
3436     VARIABLE_TYPE[] type = new VARIABLE_TYPE[n];
3437     int i = 0;
3438     for (int j = 0; j < nAtoms; j++) {
3439       if (atoms[j].isActive()) {
3440         type[i++] = VARIABLE_TYPE.X;
3441         type[i++] = VARIABLE_TYPE.Y;
3442         type[i++] = VARIABLE_TYPE.Z;
3443       }
3444     }
3445     return type;
3446   }
3447 
3448   /**
3449    * getVdwNode.
3450    *
3451    * @return a {@link ffx.potential.nonbonded.VanDerWaals} object.
3452    */
3453   public VanDerWaals getVdwNode() {
3454     return vanderWaals;
3455   }
3456 
3457   /**
3458    * {@inheritDoc}
3459    *
3460    * <p>Returns an array of velocity values for active atoms.
3461    */
3462   @Override
3463   public double[] getVelocity(double[] velocity) {
3464     int n = getNumberOfVariables();
3465     if (velocity == null || velocity.length < n) {
3466       velocity = new double[n];
3467     }
3468     int index = 0;
3469     double[] v = new double[3];
3470     for (int i = 0; i < nAtoms; i++) {
3471       Atom a = atoms[i];
3472       if (a.isActive()) {
3473         a.getVelocity(v);
3474         velocity[index++] = v[0];
3475         velocity[index++] = v[1];
3476         velocity[index++] = v[2];
3477       }
3478     }
3479     return velocity;
3480   }
3481 
3482   /**
3483    * {@inheritDoc}
3484    */
3485   @Override
3486   public double getd2EdL2() {
3487     double d2EdLambda2 = 0.0;
3488     if (!lambdaBondedTerms) {
3489       if (vanderWaalsTerm) {
3490         d2EdLambda2 = vanderWaals.getd2EdL2();
3491       }
3492       if (multipoleTerm) {
3493         d2EdLambda2 += particleMeshEwald.getd2EdL2();
3494       }
3495       /*
3496       if (restrainPositionTerm && nRestrainPositions > 0) {
3497         for (RestrainPosition restrainPosition : restrainPositions) {
3498           d2EdLambda2 += restrainPosition.getd2EdL2();
3499         }
3500       }
3501       */
3502       if (restrainDistanceTerm && nRestrainDistances > 0) {
3503         for (int i = 0; i < nRestrainDistances; i++) {
3504           d2EdLambda2 += restrainDistances[i].getd2EdL2();
3505         }
3506       }
3507       if (ncsTerm && ncsRestraint != null) {
3508         d2EdLambda2 += ncsRestraint.getd2EdL2();
3509       }
3510       if (comTerm && comRestraint != null) {
3511         d2EdLambda2 += comRestraint.getd2EdL2();
3512       }
3513       if (lambdaTorsions) {
3514         for (int i = 0; i < nTorsions; i++) {
3515           d2EdLambda2 += torsions[i].getd2EdL2();
3516         }
3517         for (int i = 0; i < nPiOrbitalTorsions; i++) {
3518           d2EdLambda2 += piOrbitalTorsions[i].getd2EdL2();
3519         }
3520         for (int i = 0; i < nTorsionTorsions; i++) {
3521           d2EdLambda2 += torsionTorsions[i].getd2EdL2();
3522         }
3523       }
3524     }
3525     return d2EdLambda2;
3526   }
3527 
3528   /**
3529    * {@inheritDoc}
3530    */
3531   @Override
3532   public double getdEdL() {
3533     double dEdLambda = 0.0;
3534     if (!lambdaBondedTerms) {
3535       if (vanderWaalsTerm) {
3536         dEdLambda = vanderWaals.getdEdL();
3537       }
3538       if (multipoleTerm) {
3539         dEdLambda += particleMeshEwald.getdEdL();
3540       }
3541       if (restrainDistanceTerm && nRestrainDistances > 0) {
3542         for (int i = 0; i < nRestrainDistances; i++) {
3543           dEdLambda += restrainDistances[i].getdEdL();
3544         }
3545       }
3546       /*
3547       if (restrainPositionTerm && nRestrainPositions > 0) {
3548         for (RestrainPosition restrainPosition : restrainPositions) {
3549           dEdLambda += restrainPosition.getdEdL();
3550         }
3551       }
3552       */
3553       if (ncsTerm && ncsRestraint != null) {
3554         dEdLambda += ncsRestraint.getdEdL();
3555       }
3556       if (comTerm && comRestraint != null) {
3557         dEdLambda += comRestraint.getdEdL();
3558       }
3559       if (lambdaTorsions) {
3560         for (int i = 0; i < nTorsions; i++) {
3561           dEdLambda += torsions[i].getdEdL();
3562         }
3563         for (int i = 0; i < nPiOrbitalTorsions; i++) {
3564           dEdLambda += piOrbitalTorsions[i].getdEdL();
3565         }
3566         for (int i = 0; i < nTorsionTorsions; i++) {
3567           dEdLambda += torsionTorsions[i].getdEdL();
3568         }
3569       }
3570     }
3571     return dEdLambda;
3572   }
3573 
3574   /**
3575    * {@inheritDoc}
3576    */
3577   @Override
3578   public void getdEdXdL(double[] gradients) {
3579     if (!lambdaBondedTerms) {
3580       if (vanderWaalsTerm) {
3581         vanderWaals.getdEdXdL(gradients);
3582       }
3583       if (multipoleTerm) {
3584         particleMeshEwald.getdEdXdL(gradients);
3585       }
3586       if (restrainDistanceTerm && nRestrainDistances > 0) {
3587         for (int i = 0; i < nRestrainDistances; i++) {
3588           restrainDistances[i].getdEdXdL(gradients);
3589         }
3590       }
3591       /*
3592       if (restrainPositionTerm && nRestrainPositions > 0) {
3593         for (RestrainPosition restrainPosition : restrainPositions) {
3594           restrainPosition.getdEdXdL(gradients);
3595         }
3596       }
3597       */
3598       if (ncsTerm && ncsRestraint != null) {
3599         ncsRestraint.getdEdXdL(gradients);
3600       }
3601       if (comTerm && comRestraint != null) {
3602         comRestraint.getdEdXdL(gradients);
3603       }
3604       if (lambdaTorsions) {
3605         double[] grad = new double[3];
3606         int index = 0;
3607         for (int i = 0; i < nAtoms; i++) {
3608           Atom a = atoms[i];
3609           if (a.isActive()) {
3610             a.getLambdaXYZGradient(grad);
3611             gradients[index++] += grad[0];
3612             gradients[index++] += grad[1];
3613             gradients[index++] += grad[2];
3614           }
3615         }
3616       }
3617     }
3618   }
3619 
3620   /**
3621    * {@inheritDoc}
3622    *
3623    * <p>The acceleration array should only contain acceleration data for active atoms.
3624    */
3625   @Override
3626   public void setAcceleration(double[] acceleration) {
3627     if (acceleration == null) {
3628       return;
3629     }
3630     int index = 0;
3631     double[] accel = new double[3];
3632     for (int i = 0; i < nAtoms; i++) {
3633       if (atoms[i].isActive()) {
3634         accel[0] = acceleration[index++];
3635         accel[1] = acceleration[index++];
3636         accel[2] = acceleration[index++];
3637         atoms[i].setAcceleration(accel);
3638       }
3639     }
3640   }
3641 
3642   /**
3643    * The coordinate array should only contain active atoms.
3644    *
3645    * @param coords an array of {@link double} objects.
3646    */
3647   public void setCoordinates(double[] coords) {
3648     if (coords == null) {
3649       return;
3650     }
3651     int index = 0;
3652     for (int i = 0; i < nAtoms; i++) {
3653       Atom a = atoms[i];
3654       if (a.isActive()) {
3655         double x = coords[index++];
3656         double y = coords[index++];
3657         double z = coords[index++];
3658         a.moveTo(x, y, z);
3659       }
3660     }
3661   }
3662 
3663   /**
3664    * Set the boundary conditions for this calculation.
3665    *
3666    * @param crystal             Crystal to set.
3667    * @param checkReplicatesCell Check if a replicates cell must be created.
3668    */
3669   public void setCrystal(Crystal crystal, boolean checkReplicatesCell) {
3670     if (checkReplicatesCell) {
3671       this.crystal = ReplicatesCrystal.replicatesCrystalFactory(crystal.getUnitCell(), cutOff2);
3672     } else {
3673       this.crystal = crystal;
3674     }
3675     /*
3676      Update VanDerWaals first, in case the NeighborList needs to be
3677      re-allocated to include a larger number of replicated cells.
3678     */
3679     if (vanderWaalsTerm) {
3680       vanderWaals.setCrystal(this.crystal);
3681     }
3682     if (multipoleTerm) {
3683       particleMeshEwald.setCrystal(this.crystal);
3684     }
3685   }
3686 
3687   /**
3688    * {@inheritDoc}
3689    *
3690    * <p>The previousAcceleration array should only contain previous acceleration data for active
3691    * atoms.
3692    */
3693   @Override
3694   public void setPreviousAcceleration(double[] previousAcceleration) {
3695     if (previousAcceleration == null) {
3696       return;
3697     }
3698     int index = 0;
3699     double[] prev = new double[3];
3700     for (int i = 0; i < nAtoms; i++) {
3701       if (atoms[i].isActive()) {
3702         prev[0] = previousAcceleration[index++];
3703         prev[1] = previousAcceleration[index++];
3704         prev[2] = previousAcceleration[index++];
3705         atoms[i].setPreviousAcceleration(prev);
3706       }
3707     }
3708   }
3709 
3710   /**
3711    * Sets the printOnFailure flag; if override is true, over-rides any existing property. Essentially
3712    * sets the default value of printOnFailure for an algorithm. For example, rotamer optimization
3713    * will generally run into force field issues in the normal course of execution as it tries
3714    * unphysical self and pair configurations, so the algorithm should not print out a large number of
3715    * error PDBs.
3716    *
3717    * @param onFail   To set
3718    * @param override Override properties
3719    */
3720   public void setPrintOnFailure(boolean onFail, boolean override) {
3721     if (override) {
3722       // Ignore any pre-existing value
3723       printOnFailure = onFail;
3724     } else {
3725       try {
3726         molecularAssembly.getForceField().getBoolean("PRINT_ON_FAILURE");
3727         /*
3728          If the call was successful, the property was explicitly set
3729          somewhere and should be kept. If an exception was thrown, the
3730          property was never set explicitly, so over-write.
3731         */
3732       } catch (Exception ex) {
3733         printOnFailure = onFail;
3734       }
3735     }
3736   }
3737 
3738   /**
3739    * {@inheritDoc}
3740    *
3741    * <p>The velocity array should only contain velocity data for active atoms.
3742    */
3743   @Override
3744   public void setVelocity(double[] velocity) {
3745     if (velocity == null) {
3746       return;
3747     }
3748     int index = 0;
3749     double[] vel = new double[3];
3750     for (int i = 0; i < nAtoms; i++) {
3751       if (atoms[i].isActive()) {
3752         vel[0] = velocity[index++];
3753         vel[1] = velocity[index++];
3754         vel[2] = velocity[index++];
3755         atoms[i].setVelocity(vel);
3756       }
3757     }
3758   }
3759 
3760   /**
3761    * {@inheritDoc}
3762    */
3763   @Override
3764   public String toString() {
3765     StringBuilder sb = new StringBuilder();
3766     if (bondTerm && nBonds > 0) {
3767       sb.append(format("  %s %20.8f %12d %12.3f (%8.5f)\n", "Bond Stretching   ", bondEnergy, nBonds,
3768           bondTime * toSeconds, bondRMSD));
3769     }
3770     if (angleTerm && nAngles > 0) {
3771       sb.append(
3772           format("  %s %20.8f %12d %12.3f (%8.5f)\n", "Angle Bending     ", angleEnergy, nAngles,
3773               angleTime * toSeconds, angleRMSD));
3774     }
3775     if (stretchBendTerm && nStretchBends > 0) {
3776       sb.append(
3777           format("  %s %20.8f %12d %12.3f\n", "Stretch-Bend      ", stretchBendEnergy, nStretchBends,
3778               stretchBendTime * toSeconds));
3779     }
3780     if (ureyBradleyTerm && nUreyBradleys > 0) {
3781       sb.append(
3782           format("  %s %20.8f %12d %12.3f\n", "Urey-Bradley      ", ureyBradleyEnergy, nUreyBradleys,
3783               ureyBradleyTime * toSeconds));
3784     }
3785     if (outOfPlaneBendTerm && nOutOfPlaneBends > 0) {
3786       sb.append(format("  %s %20.8f %12d %12.3f\n", "Out-of-Plane Bend ", outOfPlaneBendEnergy,
3787           nOutOfPlaneBends, outOfPlaneBendTime * toSeconds));
3788     }
3789     if (torsionTerm && nTorsions > 0) {
3790       sb.append(format("  %s %20.8f %12d %12.3f\n", "Torsional Angle   ", torsionEnergy, nTorsions,
3791           torsionTime * toSeconds));
3792     }
3793     if (piOrbitalTorsionTerm && nPiOrbitalTorsions > 0) {
3794       sb.append(format("  %s %20.8f %12d %12.3f\n", "Pi-Orbital Torsion", piOrbitalTorsionEnergy,
3795           nPiOrbitalTorsions, piOrbitalTorsionTime * toSeconds));
3796     }
3797     if (stretchTorsionTerm && nStretchTorsions > 0) {
3798       sb.append(format("  %s %20.8f %12d %12.3f\n", "Stretch-Torsion   ", stretchTorsionEnergy,
3799           nStretchTorsions, stretchTorsionTime * toSeconds));
3800     }
3801     if (angleTorsionTerm && nAngleTorsions > 0) {
3802       sb.append(format("  %s %20.8f %12d %12.3f\n", "Angle-Torsion     ", angleTorsionEnergy,
3803           nAngleTorsions, angleTorsionTime * toSeconds));
3804     }
3805     if (torsionTorsionTerm && nTorsionTorsions > 0) {
3806       sb.append(format("  %s %20.8f %12d %12.3f\n", "Torsion-Torsion   ", torsionTorsionEnergy,
3807           nTorsionTorsions, torsionTorsionTime * toSeconds));
3808     }
3809     if (improperTorsionTerm && nImproperTorsions > 0) {
3810       sb.append(format("  %s %20.8f %12d %12.3f\n", "Improper Torsion  ", improperTorsionEnergy,
3811           nImproperTorsions, improperTorsionTime * toSeconds));
3812     }
3813     if (restrainDistanceTerm && nRestrainDistances > 0) {
3814       sb.append(format("  %s %20.8f %12d %12.3f\n", "Restrain Distance ", restrainDistanceEnergy,
3815           nRestrainDistances, restrainDistanceTime * toSeconds));
3816     }
3817     if (restrainTorsionTerm && nRestrainTorsions > 0) {
3818       sb.append(format("  %s %20.8f %12d %12.3f\n", "Restrain Torsion  ", restrainTorsionEnergy, nRestrainTorsions,
3819           restrainTorsionTime * toSeconds));
3820     }
3821     if (restrainPositionTerm && nRestrainPositions > 0) {
3822       int nRestrainPositionAtoms = 0;
3823       for (RestrainPosition restrainPosition : restrainPositions) {
3824         nRestrainPositionAtoms += restrainPosition.getNumAtoms();
3825       }
3826       sb.append(format("  %s %20.8f %12d %12.3f\n", "Restrain Position ", restrainPositionEnergy, nRestrainPositionAtoms,
3827           restrainPositionTime * toSeconds));
3828     }
3829     if (restrainGroupTerm && nRestrainGroups > 0) {
3830       sb.append(format("  %s %20.8f %12d %12.3f\n", "Restrain Groups   ", restrainGroupEnergy,
3831           nRestrainGroups, restrainGroupTime * toSeconds));
3832     }
3833     if (ncsTerm) {
3834       sb.append(format("  %s %20.8f %12d %12.3f\n", "NCS Restraint     ", ncsEnergy, nAtoms,
3835           ncsTime * toSeconds));
3836     }
3837     if (comTerm) {
3838       sb.append(format("  %s %20.8f %12d %12.3f\n", "COM Restraint     ", comRestraintEnergy, nAtoms,
3839           comRestraintTime * toSeconds));
3840     }
3841     if (vanderWaalsTerm && nVanDerWaalInteractions > 0) {
3842       sb.append(format("  %s %20.8f %12d %12.3f\n", "Van der Waals     ", vanDerWaalsEnergy,
3843           nVanDerWaalInteractions, vanDerWaalsTime * toSeconds));
3844     }
3845     if (multipoleTerm && nPermanentInteractions > 0) {
3846       if (polarizationTerm) {
3847         sb.append(format("  %s %20.8f %12d\n", "Atomic Multipoles ", permanentMultipoleEnergy,
3848             nPermanentInteractions));
3849       } else {
3850         if (elecForm == ELEC_FORM.FIXED_CHARGE) {
3851           sb.append(format("  %s %20.8f %12d %12.3f\n", "Atomic Charges    ", permanentMultipoleEnergy,
3852               nPermanentInteractions, electrostaticTime * toSeconds));
3853         } else
3854           sb.append(format("  %s %20.8f %12d %12.3f\n", "Atomic Multipoles ", permanentMultipoleEnergy,
3855               nPermanentInteractions, electrostaticTime * toSeconds));
3856       }
3857     }
3858     if (polarizationTerm && nPermanentInteractions > 0) {
3859       sb.append(format("  %s %20.8f %12d %12.3f\n", "Polarization      ", polarizationEnergy,
3860           nPermanentInteractions, electrostaticTime * toSeconds));
3861     }
3862     if (generalizedKirkwoodTerm && nGKInteractions > 0) {
3863       sb.append(
3864           format("  %s %20.8f %12d\n", "Solvation         ", solvationEnergy, nGKInteractions));
3865     }
3866     if (relativeSolvationTerm) {
3867       sb.append(format("  %s %20.8f %12d\n", "Relative Solvation", relativeSolvationEnergy,
3868           nRelativeSolvations));
3869     }
3870     if (esvTerm) {
3871       sb.append(
3872           format("  %s %20.8f  %s\n", "ExtendedSystemBias", esvBias, esvSystem.getLambdaList()));
3873       sb.append(esvSystem.getBiasDecomposition());
3874     }
3875     if (nnTerm) {
3876       sb.append(format("  %s %20.8f %12d %12.3f\n", "Neural Network    ", nnEnergy, nAtoms,
3877           nnTime * toSeconds));
3878     }
3879     sb.append(
3880         format("  %s %20.8f  %s %12.3f (sec)", "Total Potential   ", totalEnergy, "(Kcal/mole)",
3881             totalTime * toSeconds));
3882     int nsymm = crystal.getUnitCell().spaceGroup.getNumberOfSymOps();
3883     if (nsymm > 1) {
3884       sb.append(format("\n  %s %20.8f", "Unit Cell         ", totalEnergy * nsymm));
3885     }
3886     if (crystal.getUnitCell() != crystal) {
3887       nsymm = crystal.spaceGroup.getNumberOfSymOps();
3888       sb.append(format("\n  %s %20.8f", "Replicates Cell   ", totalEnergy * nsymm));
3889     }
3890 
3891     return sb.toString();
3892   }
3893 
3894 
3895   /**
3896    * Log out all bonded energy terms.
3897    */
3898   public void logBondedTerms() {
3899     if (bondTerm && nBonds > 0) {
3900       logger.info("\n Bond Stretching Interactions:");
3901       Bond[] bonds = getBonds();
3902       for (Bond bond : bonds) {
3903         logger.info(" Bond \t" + bond.toString());
3904       }
3905     }
3906     if (angleTerm && nAngles > 0) {
3907       logger.info("\n Angle Bending Interactions:");
3908       Angle[] angles = getAngles();
3909       for (Angle angle : angles) {
3910         logger.info(" Angle \t" + angle.toString());
3911       }
3912     }
3913     if (stretchBendTerm && nStretchBends > 0) {
3914       logger.info("\n Stretch-Bend Interactions:");
3915       StretchBend[] stretchBends = getStretchBends();
3916       for (StretchBend stretchBend : stretchBends) {
3917         logger.info(" Stretch-Bend \t" + stretchBend.toString());
3918       }
3919     }
3920     if (ureyBradleyTerm && nUreyBradleys > 0) {
3921       logger.info("\n Urey-Bradley Interactions:");
3922       UreyBradley[] ureyBradleys = getUreyBradleys();
3923       for (UreyBradley ureyBradley : ureyBradleys) {
3924         logger.info("Urey-Bradley \t" + ureyBradley.toString());
3925       }
3926     }
3927     if (outOfPlaneBendTerm && nOutOfPlaneBends > 0) {
3928       logger.info("\n Out-of-Plane Bend Interactions:");
3929       OutOfPlaneBend[] outOfPlaneBends = getOutOfPlaneBends();
3930       for (OutOfPlaneBend outOfPlaneBend : outOfPlaneBends) {
3931         logger.info(" Out-of-Plane Bend \t" + outOfPlaneBend.toString());
3932       }
3933     }
3934     if (torsionTerm && nTorsions > 0) {
3935       logger.info("\n Torsion Angle Interactions:");
3936       Torsion[] torsions = getTorsions();
3937       for (Torsion torsion : torsions) {
3938         logger.info(" Torsion \t" + torsion.toString());
3939       }
3940     }
3941     if (piOrbitalTorsionTerm && nPiOrbitalTorsions > 0) {
3942       logger.info("\n Pi-Orbital Torsion Interactions:");
3943       PiOrbitalTorsion[] piOrbitalTorsions = getPiOrbitalTorsions();
3944       for (PiOrbitalTorsion piOrbitalTorsion : piOrbitalTorsions) {
3945         logger.info(" Pi-Torsion \t" + piOrbitalTorsion.toString());
3946       }
3947     }
3948     if (stretchTorsionTerm && nStretchTorsions > 0) {
3949       logger.info("\n Stretch-Torsion Interactions:");
3950       StretchTorsion[] stretchTorsions = getStretchTorsions();
3951       for (StretchTorsion stretchTorsion : stretchTorsions) {
3952         logger.info(" Stretch-Torsion \t" + stretchTorsion.toString());
3953       }
3954     }
3955     if (angleTorsionTerm && nAngleTorsions > 0) {
3956       logger.info("\n Angle-Torsion Interactions:");
3957       AngleTorsion[] angleTorsions = getAngleTorsions();
3958       for (AngleTorsion angleTorsion : angleTorsions) {
3959         logger.info(" Angle-Torsion \t" + angleTorsion.toString());
3960       }
3961     }
3962     if (torsionTorsionTerm && nTorsionTorsions > 0) {
3963       logger.info("\n Torsion-Torsion Interactions:");
3964       TorsionTorsion[] torsionTorsions = getTorsionTorsions();
3965       for (TorsionTorsion torsionTorsion : torsionTorsions) {
3966         logger.info(" Torsion-Torsion \t" + torsionTorsion.toString());
3967       }
3968     }
3969     if (improperTorsionTerm && nImproperTorsions > 0) {
3970       logger.info("\n Improper Interactions:");
3971       ImproperTorsion[] improperTorsions = getImproperTorsions();
3972       for (ImproperTorsion improperTorsion : improperTorsions) {
3973         logger.info(" Improper \t" + improperTorsion.toString());
3974       }
3975     }
3976     if (restrainDistanceTerm && nRestrainDistances > 0) {
3977       logger.info("\n Restrain Distance Interactions:");
3978       List<RestrainDistance> restrainDistances = getRestrainDistances(null);
3979       for (RestrainDistance restrainDistance : restrainDistances) {
3980         logger.info(" Restrain Distance \t" + restrainDistance.toString());
3981       }
3982     }
3983     if (restrainTorsionTerm && nRestrainTorsions > 0) {
3984       logger.info("\n Restrain Torsion Interactions:");
3985       for (RestraintTorsion restraintTorsion : restrainTorsions) {
3986         logger.info(" Restrain Torsion \t" + restraintTorsion.toString());
3987       }
3988     }
3989     if (restrainPositionTerm && nRestrainPositions > 0) {
3990       logger.info("\n Restrain Position Interactions:");
3991       for (RestrainPosition restrainPosition : restrainPositions) {
3992         logger.info(" Restrain Position \t" + restrainPosition.toString());
3993       }
3994     }
3995     if (restrainGroupTerm && nRestrainGroups > 0) {
3996       logger.info("\n Restrain Group Interactions:");
3997       logger.info(restrainGroups.toString());
3998     }
3999   }
4000 
4001   /**
4002    * Setter for the field <code>lambdaBondedTerms</code>.
4003    *
4004    * @param lambdaBondedTerms    a boolean.
4005    * @param lambdaAllBondedTerms a boolean.
4006    */
4007   void setLambdaBondedTerms(boolean lambdaBondedTerms, boolean lambdaAllBondedTerms) {
4008     this.lambdaBondedTerms = lambdaBondedTerms;
4009     this.lambdaAllBondedTerms = lambdaAllBondedTerms;
4010   }
4011 
4012   /**
4013    * Return the non-bonded components of energy (vdW, electrostatics).
4014    *
4015    * @param includeSolv Include solvation energy
4016    * @return Nonbonded energy
4017    */
4018   private double getNonbondedEnergy(boolean includeSolv) {
4019     return (includeSolv ? (totalNonBondedEnergy + solvationEnergy) : totalNonBondedEnergy);
4020   }
4021 
4022   /**
4023    * Private method for internal use, so we don't have subclasses calling super.energy, and this
4024    * class delegating to the subclass's getGradient method.
4025    *
4026    * @param g Gradient array to fill.
4027    * @return Gradient array.
4028    */
4029   private double[] fillGradient(double[] g) {
4030     assert (g != null);
4031     double[] grad = new double[3];
4032     int n = getNumberOfVariables();
4033     if (g == null || g.length < n) {
4034       g = new double[n];
4035     }
4036     int index = 0;
4037     for (int i = 0; i < nAtoms; i++) {
4038       Atom a = atoms[i];
4039       if (a.isActive()) {
4040         a.getXYZGradient(grad);
4041         double gx = grad[0];
4042         double gy = grad[1];
4043         double gz = grad[2];
4044         if (isNaN(gx) || isInfinite(gx) || isNaN(gy) || isInfinite(gy) || isNaN(gz) || isInfinite(
4045             gz)) {
4046           StringBuilder sb = new StringBuilder(
4047               format("The gradient of atom %s is (%8.3f,%8.3f,%8.3f).", a, gx, gy, gz));
4048           double[] vals = new double[3];
4049           a.getVelocity(vals);
4050           sb.append(format("\n Velocities: %8.3g %8.3g %8.3g", vals[0], vals[1], vals[2]));
4051           a.getAcceleration(vals);
4052           sb.append(format("\n Accelerations: %8.3g %8.3g %8.3g", vals[0], vals[1], vals[2]));
4053           a.getPreviousAcceleration(vals);
4054           sb.append(
4055               format("\n Previous accelerations: %8.3g %8.3g %8.3g", vals[0], vals[1], vals[2]));
4056 
4057           throw new EnergyException(sb.toString());
4058         }
4059         g[index++] = gx;
4060         g[index++] = gy;
4061         g[index++] = gz;
4062       }
4063     }
4064     return g;
4065   }
4066 
4067   /**
4068    * setRestrainDistance
4069    *
4070    * @param a1                a {@link ffx.potential.bonded.Atom} object.
4071    * @param a2                a {@link ffx.potential.bonded.Atom} object.
4072    * @param distance          a double.
4073    * @param forceConstant     the force constant in kcal/mole.
4074    * @param flatBottom        Radius of a flat-bottom potential in Angstroms.
4075    * @param lamStart          At what lambda does the restraint begin to take effect?
4076    * @param lamEnd            At what lambda does the restraint hit full strength?
4077    * @param switchingFunction Switching function to use as a lambda dependence.
4078    */
4079   private void setRestrainDistance(Atom a1, Atom a2, double distance, double forceConstant,
4080                                    double flatBottom, double lamStart, double lamEnd,
4081                                    UnivariateSwitchingFunction switchingFunction) {
4082     restrainDistanceTerm = true;
4083     boolean rbLambda = !(switchingFunction instanceof ConstantSwitch) && lambdaTerm;
4084     RestrainDistance restrainDistance = new RestrainDistance(a1, a2, crystal, rbLambda, lamStart, lamEnd, switchingFunction);
4085     int[] classes = {a1.getAtomType().atomClass, a2.getAtomType().atomClass};
4086     if (flatBottom != 0) {
4087       BondType bondType = new BondType(classes, forceConstant, distance,
4088           BondType.BondFunction.FLAT_BOTTOM_HARMONIC, flatBottom);
4089       restrainDistance.setBondType(bondType);
4090     } else {
4091       BondType bondType = new BondType(classes, forceConstant, distance,
4092           BondType.BondFunction.HARMONIC);
4093       restrainDistance.setBondType(bondType);
4094     }
4095 
4096     if (restrainDistances == null) {
4097       nRestrainDistances = 0;
4098     }
4099 
4100     RestrainDistance[] restrainDistances1 = new RestrainDistance[++nRestrainDistances];
4101     if (restrainDistances != null && restrainDistances.length != 0) {
4102       arraycopy(restrainDistances, 0, restrainDistances1, 0, (nRestrainDistances - 1));
4103     }
4104     restrainDistances1[nRestrainDistances - 1] = restrainDistance;
4105     restrainDistances = restrainDistances1;
4106     restrainDistance.energy(false);
4107     restrainDistance.log();
4108   }
4109 
4110   private Crystal configureNCS(ForceField forceField, Crystal unitCell) {
4111     // MTRIXn to be permuted with standard space group in NCSCrystal.java for experimental
4112     // refinement.
4113     if (forceField.getProperties().containsKey("MTRIX1")
4114         && forceField.getProperties().containsKey("MTRIX2") && forceField.getProperties()
4115         .containsKey("MTRIX3")) {
4116       Crystal unitCell2 = new Crystal(unitCell.a, unitCell.b, unitCell.c, unitCell.alpha,
4117           unitCell.beta, unitCell.gamma, unitCell.spaceGroup.pdbName);
4118       SpaceGroup spaceGroup = unitCell2.spaceGroup;
4119       // Separate string list MTRIXn into Double matricies then pass into symops
4120       CompositeConfiguration properties = forceField.getProperties();
4121       String[] MTRX1List = properties.getStringArray("MTRIX1");
4122       String[] MTRX2List = properties.getStringArray("MTRIX2");
4123       String[] MTRX3List = properties.getStringArray("MTRIX3");
4124       spaceGroup.symOps.clear();
4125       double number1;
4126       double number2;
4127       double number3;
4128       for (int i = 0; i < MTRX1List.length; i++) {
4129         double[][] Rot_MTRX = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
4130         double[] Tr_MTRX = {0, 0, 0};
4131         String[] tokens1 = MTRX1List[i].trim().split(" +"); // 4 items: rot [0][1-3] * trans[0]
4132         String[] tokens2 = MTRX2List[i].trim().split(" +"); // 4 items: rot [1][1-3] * trans[1]
4133         String[] tokens3 = MTRX3List[i].trim().split(" +"); // 4 items: rot [2][1-3] * trans[2]
4134         for (int k = 0; k < 4; k++) {
4135           number1 = Double.parseDouble(tokens1[k]);
4136           number2 = Double.parseDouble(tokens2[k]);
4137           number3 = Double.parseDouble(tokens3[k]);
4138           if (k != 3) {
4139             Rot_MTRX[0][k] = number1;
4140             Rot_MTRX[1][k] = number2;
4141             Rot_MTRX[2][k] = number3;
4142           } else {
4143             Tr_MTRX[0] = number1;
4144             Tr_MTRX[1] = number2;
4145             Tr_MTRX[2] = number3;
4146           }
4147         }
4148         SymOp symOp = new SymOp(Rot_MTRX, Tr_MTRX);
4149         if (logger.isLoggable(Level.FINEST)) {
4150           logger.info(
4151               format(" MTRIXn SymOp: %d of %d\n" + symOp, i + 1, MTRX1List.length));
4152         }
4153         spaceGroup.symOps.add(symOp);
4154       }
4155       unitCell = NCSCrystal.NCSCrystalFactory(unitCell, spaceGroup.symOps);
4156       unitCell.updateCrystal();
4157     }
4158     return unitCell;
4159   }
4160 
4161   /**
4162    * Getter for the field <code>restrainDistances</code>.
4163    *
4164    * @param bondFunction the type of bond function.
4165    * @return a {@link java.util.List} object.
4166    */
4167   public List<RestrainDistance> getRestrainDistances(@Nullable BondType.BondFunction bondFunction) {
4168     List<RestrainDistance> list = new ArrayList<>();
4169     if (restrainDistances != null && restrainDistances.length > 0) {
4170       // If the bondFunction is null, return all restrained bonds.
4171       if (bondFunction == null) {
4172         return Arrays.asList(restrainDistances);
4173       }
4174       // Otherwise, return only the restraint bonds with the specified bond function.
4175       for (RestrainDistance restrainDistance : restrainDistances) {
4176         if (restrainDistance.getBondType().bondFunction == bondFunction) {
4177           list.add(restrainDistance);
4178         }
4179       }
4180       if (!list.isEmpty()) {
4181         return list;
4182       }
4183     }
4184     return null;
4185   }
4186 
4187   /**
4188    * Getter for the field <code>restraintBonds</code>.
4189    *
4190    * @return a {@link java.util.List} object.
4191    */
4192   public List<RestraintTorsion> getRestrainTorsions() {
4193     if (restrainTorsions != null && restrainTorsions.length > 0) {
4194       return Arrays.asList(restrainTorsions);
4195     } else {
4196       return null;
4197     }
4198   }
4199 
4200   /**
4201    * Getter for the RestrainGroup field.
4202    *
4203    * @return Returns RestrainGroup.
4204    */
4205   public RestrainGroups getRestrainGroups() {
4206     return restrainGroups;
4207   }
4208 
4209   private class BondedRegion extends ParallelRegion {
4210 
4211     // Shared RMSD variables.
4212     private final SharedDouble sharedBondRMSD;
4213     private final SharedDouble sharedAngleRMSD;
4214     // Shared force field bonded energy accumulation variables.
4215     private final SharedDouble sharedBondEnergy;
4216     private final SharedDouble sharedAngleEnergy;
4217     private final SharedDouble sharedOutOfPlaneBendEnergy;
4218     private final SharedDouble sharedPiOrbitalTorsionEnergy;
4219     private final SharedDouble sharedStretchBendEnergy;
4220     private final SharedDouble sharedUreyBradleyEnergy;
4221     private final SharedDouble sharedImproperTorsionEnergy;
4222     private final SharedDouble sharedTorsionEnergy;
4223     private final SharedDouble sharedStretchTorsionEnergy;
4224     private final SharedDouble sharedAngleTorsionEnergy;
4225     private final SharedDouble sharedTorsionTorsionEnergy;
4226     private final SharedDouble sharedRestrainPositionEnergy;
4227     private final SharedDouble sharedRestrainDistanceEnergy;
4228     private final SharedDouble sharedRestrainTorsionEnergy;
4229     // Number of threads.
4230     private final int nThreads;
4231     // Gradient loops.
4232     private final GradInitLoop[] gradInitLoops;
4233     private final GradReduceLoop[] gradReduceLoops;
4234     // Force field bonded energy parallel loops.
4235     private final BondedTermLoop[] bondLoops;
4236     private final BondedTermLoop[] angleLoops;
4237     private final BondedTermLoop[] outOfPlaneBendLoops;
4238     private final BondedTermLoop[] improperTorsionLoops;
4239     private final BondedTermLoop[] piOrbitalTorsionLoops;
4240     private final BondedTermLoop[] stretchBendLoops;
4241     private final BondedTermLoop[] torsionLoops;
4242     private final BondedTermLoop[] stretchTorsionLoops;
4243     private final BondedTermLoop[] angleTorsionLoops;
4244     private final BondedTermLoop[] torsionTorsionLoops;
4245     private final BondedTermLoop[] ureyBradleyLoops;
4246 
4247     private final BondedTermLoop[] restrainPositionLoops;
4248     private final BondedTermLoop[] restrainDistanceLoops;
4249     private final BondedTermLoop[] restrainTorsionLoops;
4250     private final AtomicDoubleArray3D grad;
4251     // Flag to indicate gradient computation.
4252     private boolean gradient = false;
4253     private AtomicDoubleArrayImpl atomicDoubleArrayImpl;
4254     private AtomicDoubleArray3D lambdaGrad;
4255 
4256     BondedRegion() {
4257 
4258       // Allocate shared RMSD variables.
4259       sharedAngleRMSD = new SharedDouble();
4260       sharedBondRMSD = new SharedDouble();
4261 
4262       // Allocate shared force field bonded energy variables.
4263       sharedBondEnergy = new SharedDouble();
4264       sharedAngleEnergy = new SharedDouble();
4265       sharedImproperTorsionEnergy = new SharedDouble();
4266       sharedOutOfPlaneBendEnergy = new SharedDouble();
4267       sharedPiOrbitalTorsionEnergy = new SharedDouble();
4268       sharedStretchBendEnergy = new SharedDouble();
4269       sharedTorsionEnergy = new SharedDouble();
4270       sharedStretchTorsionEnergy = new SharedDouble();
4271       sharedAngleTorsionEnergy = new SharedDouble();
4272       sharedTorsionTorsionEnergy = new SharedDouble();
4273       sharedUreyBradleyEnergy = new SharedDouble();
4274 
4275       // Allocate Restrain energy variables.
4276       sharedRestrainPositionEnergy = new SharedDouble();
4277       sharedRestrainDistanceEnergy = new SharedDouble();
4278       sharedRestrainTorsionEnergy = new SharedDouble();
4279 
4280       nThreads = parallelTeam.getThreadCount();
4281 
4282       // Gradient initialization loops.
4283       gradInitLoops = new GradInitLoop[nThreads];
4284       gradReduceLoops = new GradReduceLoop[nThreads];
4285 
4286       // Allocate memory for force field bonded energy loop arrays.
4287       angleLoops = new BondedTermLoop[nThreads];
4288       bondLoops = new BondedTermLoop[nThreads];
4289       improperTorsionLoops = new BondedTermLoop[nThreads];
4290       outOfPlaneBendLoops = new BondedTermLoop[nThreads];
4291       piOrbitalTorsionLoops = new BondedTermLoop[nThreads];
4292       stretchBendLoops = new BondedTermLoop[nThreads];
4293       torsionLoops = new BondedTermLoop[nThreads];
4294       stretchTorsionLoops = new BondedTermLoop[nThreads];
4295       angleTorsionLoops = new BondedTermLoop[nThreads];
4296       torsionTorsionLoops = new BondedTermLoop[nThreads];
4297       ureyBradleyLoops = new BondedTermLoop[nThreads];
4298       restrainPositionLoops = new BondedTermLoop[nThreads];
4299       restrainTorsionLoops = new BondedTermLoop[nThreads];
4300       restrainDistanceLoops = new BondedTermLoop[nThreads];
4301 
4302       // Define how the gradient will be accumulated.
4303       atomicDoubleArrayImpl = AtomicDoubleArrayImpl.MULTI;
4304       ForceField forceField = molecularAssembly.getForceField();
4305 
4306       String value = forceField.getString("ARRAY_REDUCTION", "MULTI");
4307       try {
4308         atomicDoubleArrayImpl = AtomicDoubleArrayImpl.valueOf(toEnumForm(value));
4309       } catch (Exception e) {
4310         logger.info(format(" Unrecognized ARRAY-REDUCTION %s; defaulting to %s", value,
4311             atomicDoubleArrayImpl));
4312       }
4313       logger.fine(format("  Bonded using %s arrays.", atomicDoubleArrayImpl.toString()));
4314 
4315       grad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, nThreads);
4316       if (lambdaTerm) {
4317         lambdaGrad = new AtomicDoubleArray3D(atomicDoubleArrayImpl, nAtoms, nThreads);
4318       }
4319     }
4320 
4321     @Override
4322     public void finish() {
4323       // Finalize bond and angle RMSD values.
4324       if (bondTerm) {
4325         bondRMSD = sqrt(sharedBondRMSD.get() / bonds.length);
4326       }
4327       if (angleTerm) {
4328         angleRMSD = sqrt(sharedAngleRMSD.get() / angles.length);
4329       }
4330 
4331       // Load shared energy values into their respective fields.
4332       angleEnergy = sharedAngleEnergy.get();
4333       bondEnergy = sharedBondEnergy.get();
4334       improperTorsionEnergy = sharedImproperTorsionEnergy.get();
4335       outOfPlaneBendEnergy = sharedOutOfPlaneBendEnergy.get();
4336       piOrbitalTorsionEnergy = sharedPiOrbitalTorsionEnergy.get();
4337       stretchBendEnergy = sharedStretchBendEnergy.get();
4338       ureyBradleyEnergy = sharedUreyBradleyEnergy.get();
4339       torsionEnergy = sharedTorsionEnergy.get();
4340       stretchTorsionEnergy = sharedStretchTorsionEnergy.get();
4341       angleTorsionEnergy = sharedAngleTorsionEnergy.get();
4342       torsionTorsionEnergy = sharedTorsionTorsionEnergy.get();
4343       ureyBradleyEnergy = sharedUreyBradleyEnergy.get();
4344 
4345       // Load shared restrain energy values.
4346       restrainPositionEnergy = sharedRestrainPositionEnergy.get();
4347       restrainDistanceEnergy = sharedRestrainDistanceEnergy.get();
4348       restrainTorsionEnergy = sharedRestrainTorsionEnergy.get();
4349     }
4350 
4351     @Override
4352     public void run() throws Exception {
4353       int threadID = getThreadIndex();
4354 
4355       // Initialize the Gradient to zero.
4356       if (gradient) {
4357         if (gradInitLoops[threadID] == null) {
4358           gradInitLoops[threadID] = new GradInitLoop();
4359         }
4360         execute(0, nAtoms - 1, gradInitLoops[threadID]);
4361       }
4362 
4363       // Evaluate force field bonded energy terms in parallel.
4364       if (angleTerm) {
4365         if (angleLoops[threadID] == null) {
4366           angleLoops[threadID] = new BondedTermLoop(angles, sharedAngleEnergy, sharedAngleRMSD);
4367         }
4368         if (threadID == 0) {
4369           angleTime = -System.nanoTime();
4370         }
4371         execute(0, nAngles - 1, angleLoops[threadID]);
4372         if (threadID == 0) {
4373           angleTime += System.nanoTime();
4374         }
4375       }
4376 
4377       if (bondTerm) {
4378         if (bondLoops[threadID] == null) {
4379           bondLoops[threadID] = new BondedTermLoop(bonds, sharedBondEnergy, sharedBondRMSD);
4380         }
4381         if (threadID == 0) {
4382           bondTime = -System.nanoTime();
4383         }
4384         execute(0, nBonds - 1, bondLoops[threadID]);
4385         if (threadID == 0) {
4386           bondTime += System.nanoTime();
4387         }
4388       }
4389 
4390       if (improperTorsionTerm) {
4391         if (improperTorsionLoops[threadID] == null) {
4392           improperTorsionLoops[threadID] = new BondedTermLoop(improperTorsions,
4393               sharedImproperTorsionEnergy);
4394         }
4395         if (threadID == 0) {
4396           improperTorsionTime = -System.nanoTime();
4397         }
4398         execute(0, nImproperTorsions - 1, improperTorsionLoops[threadID]);
4399         if (threadID == 0) {
4400           improperTorsionTime += System.nanoTime();
4401         }
4402       }
4403 
4404       if (outOfPlaneBendTerm) {
4405         if (outOfPlaneBendLoops[threadID] == null) {
4406           outOfPlaneBendLoops[threadID] = new BondedTermLoop(outOfPlaneBends,
4407               sharedOutOfPlaneBendEnergy);
4408         }
4409         if (threadID == 0) {
4410           outOfPlaneBendTime = -System.nanoTime();
4411         }
4412         execute(0, nOutOfPlaneBends - 1, outOfPlaneBendLoops[threadID]);
4413         if (threadID == 0) {
4414           outOfPlaneBendTime += System.nanoTime();
4415         }
4416       }
4417 
4418       if (piOrbitalTorsionTerm) {
4419         if (piOrbitalTorsionLoops[threadID] == null) {
4420           piOrbitalTorsionLoops[threadID] = new BondedTermLoop(piOrbitalTorsions,
4421               sharedPiOrbitalTorsionEnergy);
4422         }
4423         if (threadID == 0) {
4424           piOrbitalTorsionTime = -System.nanoTime();
4425         }
4426         execute(0, nPiOrbitalTorsions - 1, piOrbitalTorsionLoops[threadID]);
4427         if (threadID == 0) {
4428           piOrbitalTorsionTime += System.nanoTime();
4429         }
4430       }
4431 
4432       if (stretchBendTerm) {
4433         if (stretchBendLoops[threadID] == null) {
4434           stretchBendLoops[threadID] = new BondedTermLoop(stretchBends, sharedStretchBendEnergy);
4435         }
4436         if (threadID == 0) {
4437           stretchBendTime = -System.nanoTime();
4438         }
4439         execute(0, nStretchBends - 1, stretchBendLoops[threadID]);
4440         if (threadID == 0) {
4441           stretchBendTime += System.nanoTime();
4442         }
4443       }
4444 
4445       if (torsionTerm) {
4446         if (torsionLoops[threadID] == null) {
4447           torsionLoops[threadID] = new BondedTermLoop(torsions, sharedTorsionEnergy);
4448         }
4449         if (threadID == 0) {
4450           torsionTime = -System.nanoTime();
4451         }
4452         execute(0, nTorsions - 1, torsionLoops[threadID]);
4453         if (threadID == 0) {
4454           torsionTime += System.nanoTime();
4455         }
4456       }
4457 
4458       if (stretchTorsionTerm) {
4459         if (stretchTorsionLoops[threadID] == null) {
4460           stretchTorsionLoops[threadID] = new BondedTermLoop(stretchTorsions,
4461               sharedStretchTorsionEnergy);
4462         }
4463         if (threadID == 0) {
4464           stretchTorsionTime = -System.nanoTime();
4465         }
4466         execute(0, nStretchTorsions - 1, stretchTorsionLoops[threadID]);
4467         if (threadID == 0) {
4468           stretchTorsionTime += System.nanoTime();
4469         }
4470       }
4471 
4472       if (angleTorsionTerm) {
4473         if (angleTorsionLoops[threadID] == null) {
4474           angleTorsionLoops[threadID] = new BondedTermLoop(angleTorsions, sharedAngleTorsionEnergy);
4475         }
4476         if (threadID == 0) {
4477           angleTorsionTime = -System.nanoTime();
4478         }
4479         execute(0, nAngleTorsions - 1, angleTorsionLoops[threadID]);
4480         if (threadID == 0) {
4481           angleTorsionTime += System.nanoTime();
4482         }
4483       }
4484 
4485       if (torsionTorsionTerm) {
4486         if (torsionTorsionLoops[threadID] == null) {
4487           torsionTorsionLoops[threadID] = new BondedTermLoop(torsionTorsions,
4488               sharedTorsionTorsionEnergy);
4489         }
4490         if (threadID == 0) {
4491           torsionTorsionTime = -System.nanoTime();
4492         }
4493         execute(0, nTorsionTorsions - 1, torsionTorsionLoops[threadID]);
4494         if (threadID == 0) {
4495           torsionTorsionTime += System.nanoTime();
4496         }
4497       }
4498 
4499       if (ureyBradleyTerm) {
4500         if (ureyBradleyLoops[threadID] == null) {
4501           ureyBradleyLoops[threadID] = new BondedTermLoop(ureyBradleys, sharedUreyBradleyEnergy);
4502         }
4503         if (threadID == 0) {
4504           ureyBradleyTime = -System.nanoTime();
4505         }
4506         execute(0, nUreyBradleys - 1, ureyBradleyLoops[threadID]);
4507         if (threadID == 0) {
4508           ureyBradleyTime += System.nanoTime();
4509         }
4510       }
4511 
4512       // Evaluate RestraintPosition terms in parallel.
4513       if (restrainPositionTerm && nRestrainPositions > 0) {
4514         if (restrainPositionLoops[threadID] == null) {
4515           restrainPositionLoops[threadID] = new BondedTermLoop(restrainPositions, sharedRestrainPositionEnergy);
4516         }
4517         if (threadID == 0) {
4518           restrainPositionTime = -System.nanoTime();
4519         }
4520         execute(0, nRestrainPositions - 1, restrainPositionLoops[threadID]);
4521         if (threadID == 0) {
4522           restrainPositionTime += System.nanoTime();
4523         }
4524       }
4525 
4526       // Evaluate RestrainDistance terms in parallel.
4527       if (restrainDistanceTerm && nRestrainDistances > 0) {
4528         if (restrainDistanceLoops[threadID] == null) {
4529           restrainDistanceLoops[threadID] = new BondedTermLoop(restrainDistances, sharedRestrainDistanceEnergy);
4530         }
4531         if (threadID == 0) {
4532           restrainDistanceTime = -System.nanoTime();
4533         }
4534         execute(0, nRestrainDistances - 1, restrainDistanceLoops[threadID]);
4535         if (threadID == 0) {
4536           restrainDistanceTime += System.nanoTime();
4537         }
4538       }
4539 
4540       // Evaluate RestrainTorsion terms in parallel.
4541       if (restrainTorsionTerm && nRestrainTorsions > 0) {
4542         if (restrainTorsionLoops[threadID] == null) {
4543           restrainTorsionLoops[threadID] = new BondedTermLoop(restrainTorsions, sharedRestrainTorsionEnergy);
4544         }
4545         if (threadID == 0) {
4546           restrainTorsionTime = -System.nanoTime();
4547         }
4548         execute(0, nRestrainTorsions - 1, restrainTorsionLoops[threadID]);
4549         if (threadID == 0) {
4550           restrainTorsionTime += System.nanoTime();
4551         }
4552       }
4553 
4554       // Reduce the Gradient and load it into Atom instances.
4555       if (gradient) {
4556         if (gradReduceLoops[threadID] == null) {
4557           gradReduceLoops[threadID] = new GradReduceLoop();
4558         }
4559         execute(0, nAtoms - 1, gradReduceLoops[threadID]);
4560       }
4561     }
4562 
4563     public void setGradient(boolean gradient) {
4564       this.gradient = gradient;
4565     }
4566 
4567     @Override
4568     public void start() {
4569       // Zero out shared RMSD values.
4570       sharedAngleRMSD.set(0.0);
4571       sharedBondRMSD.set(0.0);
4572 
4573       // Zero out shared energy values.
4574       sharedAngleEnergy.set(0.0);
4575       sharedBondEnergy.set(0.0);
4576       sharedImproperTorsionEnergy.set(0.0);
4577       sharedOutOfPlaneBendEnergy.set(0.0);
4578       sharedPiOrbitalTorsionEnergy.set(0.0);
4579       sharedStretchBendEnergy.set(0.0);
4580       sharedTorsionEnergy.set(0.0);
4581       sharedStretchTorsionEnergy.set(0.0);
4582       sharedAngleTorsionEnergy.set(0.0);
4583       sharedTorsionTorsionEnergy.set(0.0);
4584       sharedUreyBradleyEnergy.set(0.0);
4585 
4586       // Zero out shared restraint energy values.
4587       sharedRestrainPositionEnergy.set(0.0);
4588       sharedRestrainDistanceEnergy.set(0.0);
4589       sharedRestrainTorsionEnergy.set(0.0);
4590 
4591       // Assure capacity of the gradient arrays.
4592       if (gradient) {
4593         grad.alloc(nAtoms);
4594       }
4595       if (lambdaTerm) {
4596         lambdaGrad.alloc(nAtoms);
4597       }
4598     }
4599 
4600     private class GradInitLoop extends IntegerForLoop {
4601 
4602       @Override
4603       public void run(int first, int last) {
4604         int threadID = getThreadIndex();
4605         if (gradient) {
4606           grad.reset(threadID, first, last);
4607           for (int i = first; i <= last; i++) {
4608             atoms[i].setXYZGradient(0.0, 0.0, 0.0);
4609           }
4610         }
4611         if (lambdaTerm) {
4612           lambdaGrad.reset(threadID, first, last);
4613           for (int i = first; i <= last; i++) {
4614             atoms[i].setLambdaXYZGradient(0.0, 0.0, 0.0);
4615           }
4616         }
4617       }
4618 
4619       @Override
4620       public IntegerSchedule schedule() {
4621         return IntegerSchedule.fixed();
4622       }
4623     }
4624 
4625     private class GradReduceLoop extends IntegerForLoop {
4626 
4627       @Override
4628       public void run(int first, int last) {
4629         if (gradient) {
4630           grad.reduce(first, last);
4631           for (int i = first; i <= last; i++) {
4632             Atom a = atoms[i];
4633             a.setXYZGradient(grad.getX(i), grad.getY(i), grad.getZ(i));
4634           }
4635         }
4636         if (lambdaTerm) {
4637           lambdaGrad.reduce(first, last);
4638           for (int i = first; i <= last; i++) {
4639             Atom a = atoms[i];
4640             a.setLambdaXYZGradient(lambdaGrad.getX(i), lambdaGrad.getY(i), lambdaGrad.getZ(i));
4641           }
4642         }
4643       }
4644 
4645       @Override
4646       public IntegerSchedule schedule() {
4647         return IntegerSchedule.fixed();
4648       }
4649     }
4650 
4651     private class BondedTermLoop extends IntegerForLoop {
4652 
4653       private final BondedTerm[] terms;
4654       private final SharedDouble sharedEnergy;
4655       private final SharedDouble sharedRMSD;
4656       private final boolean computeRMSD;
4657       private double localEnergy;
4658       private double localRMSD;
4659       private int threadID;
4660 
4661       BondedTermLoop(BondedTerm[] terms, SharedDouble sharedEnergy) {
4662         this(terms, sharedEnergy, null);
4663       }
4664 
4665       BondedTermLoop(BondedTerm[] terms, SharedDouble sharedEnergy, SharedDouble sharedRMSD) {
4666         this.terms = terms;
4667         this.sharedEnergy = sharedEnergy;
4668         this.sharedRMSD = sharedRMSD;
4669         computeRMSD = (sharedRMSD != null);
4670       }
4671 
4672       @Override
4673       public void finish() {
4674         sharedEnergy.addAndGet(localEnergy);
4675         if (computeRMSD) {
4676           sharedRMSD.addAndGet(localRMSD);
4677         }
4678       }
4679 
4680       @Override
4681       public void run(int first, int last) {
4682         for (int i = first; i <= last; i++) {
4683           BondedTerm term = terms[i];
4684           /*
4685            * The logic here deals with how DualTopologyEnergy (DTE) works.
4686            * If this ForceFieldEnergy (FFE) is not part of a DTE, lambdaBondedTerms is always false, and the term is always evaluated.
4687            *
4688            * Most bonded terms should be at full-strength, regardless of lambda, "complemented" to 1.0*strength.
4689            * Outside FFE, DTE scales the initial, !lambdaBondedTerms evaluation by f(lambda).
4690            *
4691            * In the case of a bonded term lacking any softcore atoms, this will be externally complemented by the other FFE topology.
4692            * If there is internal lambda-scaling, that will separately apply at both ends.
4693            *
4694            * In the case of a bonded term with a softcore atom, it's a bit trickier.
4695            * If it's unscaled by lambda, it needs to be internally complemented; we re-evaluate it with lambdaBondedTerms true.
4696            * This second evaluation is scaled by DTE by a factor of f(1-lambda), and becomes "restraintEnergy".
4697            * If it is scaled internally by lambda, we assume that the energy term is not meant to be internally complemented.
4698            * In that case, we skip evaluation into restraintEnergy.
4699            */
4700           boolean used = !lambdaBondedTerms || lambdaAllBondedTerms || (term.applyLambda() && !term.isLambdaScaled());
4701           if (used) {
4702             localEnergy += term.energy(gradient, threadID, grad, lambdaGrad);
4703             if (computeRMSD) {
4704               double value = term.getValue();
4705               localRMSD += value * value;
4706             }
4707           }
4708         }
4709       }
4710 
4711       @Override
4712       public void start() {
4713         localEnergy = 0.0;
4714         localRMSD = 0.0;
4715         threadID = getThreadIndex();
4716       }
4717     }
4718   }
4719 }