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