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.ParallelRegion;
41  import edu.rit.pj.ParallelSection;
42  import edu.rit.pj.ParallelTeam;
43  import ffx.crystal.Crystal;
44  import ffx.crystal.CrystalPotential;
45  import ffx.crystal.SymOp;
46  import ffx.numerics.Potential;
47  import ffx.numerics.switching.UnivariateSwitchingFunction;
48  import ffx.potential.bonded.Atom;
49  import ffx.potential.bonded.LambdaInterface;
50  import ffx.potential.openmm.OpenMMDualTopologyEnergy;
51  import ffx.potential.openmm.OpenMMEnergy;
52  import ffx.potential.parameters.ForceField;
53  import ffx.potential.utils.EnergyException;
54  
55  import javax.annotation.Nullable;
56  import java.util.ArrayList;
57  import java.util.Arrays;
58  import java.util.List;
59  import java.util.logging.Level;
60  import java.util.logging.Logger;
61  
62  import static ffx.crystal.SymOp.applyCartesianSymOp;
63  import static ffx.crystal.SymOp.applyCartesianSymRot;
64  import static ffx.crystal.SymOp.invertSymOp;
65  import static ffx.potential.parameters.ForceField.toEnumForm;
66  import static ffx.potential.utils.Superpose.rmsd;
67  import static ffx.utilities.StringUtils.parseAtomRanges;
68  import static ffx.utilities.StringUtils.writeAtomRanges;
69  import static java.lang.Double.parseDouble;
70  import static java.lang.String.format;
71  import static java.util.Arrays.fill;
72  import static org.apache.commons.math3.util.FastMath.sqrt;
73  
74  /**
75   * Compute the potential energy and derivatives for a dual-topology system.
76   *
77   * @author Michael J. Schnieders
78   * @since 1.0
79   */
80  public class DualTopologyEnergy implements CrystalPotential, LambdaInterface {
81  
82    /**
83     * Logger for the DualTopologyEnergy class.
84     */
85    private static final Logger logger = Logger.getLogger(DualTopologyEnergy.class.getName());
86    /**
87     * Shared atoms between topologies 1 and 2.
88     */
89    private final int nShared;
90    /**
91     * Total number of variables
92     */
93    private final int nVariables;
94    /**
95     * Region for computing energy/gradient in parallel.
96     */
97    private final EnergyRegion energyRegion;
98    /**
99     * Mass array for shared and softcore atoms.
100    */
101   private final double[] mass;
102   /**
103    * VARIABLE_TYPE array for shared and softcore atoms.
104    */
105   private final VARIABLE_TYPE[] variableTypes;
106   /**
107    * Coordinates for topology 1.
108    */
109   private final double[] x1;
110   /**
111    * Coordinates for topology 2.
112    */
113   private final double[] x2;
114   /**
115    * Topology 1 coordinate gradient.
116    */
117   private final double[] g1;
118   /**
119    * Topology 2 coordinate gradient.
120    */
121   private final double[] g2;
122   /**
123    * Topology 1 restraint gradient for end state bonded terms.
124    */
125   private final double[] rg1;
126   /**
127    * Topology 2 restraint gradient for end state bonded terms.
128    */
129   private final double[] rg2;
130   /**
131    * Topology 1 derivative of the coordinate gradient with respect to lambda.
132    */
133   private final double[] gl1;
134   /**
135    * Topology 2 derivative of the coordinate gradient with respect to lambda.
136    */
137   private final double[] gl2;
138   /**
139    * Topology 1 derivative of the coordinate gradient with respect to lambda for end state bonded
140    * terms
141    */
142   private final double[] rgl1;
143   /**
144    * Topology 2 derivative of the coordinate gradient with respect to lambda for end state bonded
145    * terms
146    */
147   private final double[] rgl2;
148   /**
149    * Topology 1 Potential.
150    */
151   private final CrystalPotential potential1;
152   /**
153    * Topology 2 Potential.
154    */
155   private final CrystalPotential potential2;
156   /**
157    * Topology 1 LambdaInterface.
158    */
159   private final LambdaInterface lambdaInterface1;
160   /**
161    * Topology 2 LambdaInterface.
162    */
163   private final LambdaInterface lambdaInterface2;
164   /**
165    * Topology 1 ForceFieldEnergy.
166    */
167   private final ForceFieldEnergy forceFieldEnergy1;
168   /**
169    * Topology 2 ForceFieldEnergy.
170    */
171   private final ForceFieldEnergy forceFieldEnergy2;
172   /**
173    * Number of active atoms in Topology 1.
174    */
175   private final int nActive1;
176   /**
177    * Number of active atoms in Topology 2.
178    */
179   private final int nActive2;
180   /**
181    * Array of active atoms in Topology 1.
182    */
183   private final Atom[] activeAtoms1;
184   /**
185    * Array of active atoms in Topology 2.
186    */
187   private final Atom[] activeAtoms2;
188   /**
189    * Flag is true if the atom is shared (not alchemical) for Topology 1.
190    */
191   private final boolean[] sharedAtoms1;
192   /**
193    * Flag is true if the atom is shared (not alchemical) for Topology 2.
194    */
195   private final boolean[] sharedAtoms2;
196   /**
197    * Index for each atom in topology 1 into the overall dual-topology atom array.
198    * This includes all atoms, including (in)active, shared atoms and alchemical atoms.
199    */
200   private final int[] system1AtomIndex;
201   /**
202    * Index for each atom in topology 2 into the overall dual-topology atom array.
203    * This includes all atoms, including (in)active, shared atoms and alchemical atoms.
204    */
205   private final int[] system2AtomIndex;
206   /**
207    * An array of shared and alchemical atoms for the dual topology system.
208    * <p>
209    * The shared atoms from Topology 1.
210    */
211   private final Atom[] dualTopologyAtoms;
212   /**
213    * An array of shared and alchemical atoms for the dual topology system.
214    * <p>
215    * The shared atoms from Topology 2.
216    */
217   private final Atom[] dualTopologyAtoms2;
218   /**
219    * Will default to a power-1 PowerSwitch function.
220    */
221   private final UnivariateSwitchingFunction switchFunction;
222   /**
223    * Current potential energy of topology 1 (kcal/mol).
224    */
225   private double energy1 = 0;
226   /**
227    * Current potential energy of topology 2 (kcal/mol).
228    */
229   private double energy2 = 0;
230   /**
231    * ParallelTeam to execute the EnergyRegion.
232    */
233   private ParallelTeam parallelTeam;
234   /**
235    * Include a valence restraint energy for atoms being "disappeared."
236    */
237   private final boolean doValenceRestraint1;
238   /**
239    * Include a valence restraint energy for atoms being "disappeared."
240    */
241   private final boolean doValenceRestraint2;
242   /**
243    * Use System 1 Bonded Energy.
244    */
245   private final boolean useFirstSystemBondedEnergy;
246   /**
247    * End-state restraint energy of topology 1 (kcal/mol).
248    */
249   private double restraintEnergy1 = 0;
250   /**
251    * End-state restraint energy of topology 2 (kcal/mol).
252    */
253   private double restraintEnergy2 = 0;
254   /**
255    * dEdL of topology 1 (kcal/mol).
256    */
257   private double dEdL_1 = 0;
258   /**
259    * dEdL of topology 2 (kcal/mol).
260    */
261   private double dEdL_2 = 0;
262   /**
263    * End-state restraint dEdL of topology 1 (kcal/mol).
264    */
265   private double restraintdEdL_1 = 0;
266   /**
267    * End-state restraint dEdL of topology 2 (kcal/mol).
268    */
269   private double restraintdEdL_2 = 0;
270   /**
271    * d2EdL2 of topology 1 (kcal/mol).
272    */
273   private double d2EdL2_1 = 0;
274   /**
275    * d2EdL2 of topology 2 (kcal/mol).
276    */
277   private double d2EdL2_2 = 0;
278   /**
279    * End-state restraint d2EdL2 of topology 1 (kcal/mol).
280    */
281   private double restraintd2EdL2_1 = 0;
282   /**
283    * End-state restraint d2EdL2 of topology 2 (kcal/mol).
284    */
285   private double restraintd2EdL2_2 = 0;
286   /**
287    * Total energy of the dual topology, including lambda scaling.
288    */
289   private double totalEnergy = 0;
290   /**
291    * Current lambda value.
292    */
293   private double lambda = 1.0;
294   /**
295    * Value of the switching function at lambda.
296    */
297   private double f1L = 1.0;
298   /**
299    * Value of the switching function at (1-lambda).
300    */
301   private double f2L = 0.0;
302   /**
303    * First derivative with respect to lambda of the switching function.
304    */
305   private double dF1dL = 0.0;
306   /**
307    * First derivative with respect to (1-lambda) of the switching function.
308    */
309   private double dF2dL = 0.0;
310   /**
311    * Second derivative with respect to lambda of the switching function.
312    */
313   private double d2F1dL2 = 0.0;
314   /**
315    * Second derivative with respect to (1-lambda) of the switching function.
316    */
317   private double d2F2dL2 = 0.0;
318   /**
319    * Scaling array for shared and softcore atoms.
320    */
321   private double[] scaling = null;
322   /**
323    * State for calculating energies.
324    */
325   private STATE state = STATE.BOTH;
326   /**
327    * Symmetry operator to superimpose system 2 onto system 1
328    */
329   private SymOp[] symOp;
330   /**
331    * Symmetry operator to move system 2 back to original frame
332    */
333   private SymOp[] inverse;
334   /**
335    * Atom selection for each symmetry operator.
336    */
337   private final ArrayList<List<Integer>> symOpAtoms = new ArrayList<>();
338   /**
339    * Mask to determine which atoms a sym op will be applied.
340    */
341   private boolean[][] mask = null;
342   /**
343    * Utilize a provided SymOp
344    */
345   private boolean useSymOp = false;
346 
347   /**
348    * Constructor for DualTopologyEnergy.
349    *
350    * @param topology1      a {@link ffx.potential.MolecularAssembly} object.
351    * @param topology2      a {@link ffx.potential.MolecularAssembly} object.
352    * @param switchFunction a {@link UnivariateSwitchingFunction} object.
353    */
354   public DualTopologyEnergy(
355       MolecularAssembly topology1,
356       MolecularAssembly topology2,
357       UnivariateSwitchingFunction switchFunction) {
358     forceFieldEnergy1 = topology1.getPotentialEnergy();
359     forceFieldEnergy2 = topology2.getPotentialEnergy();
360     potential1 = forceFieldEnergy1;
361     potential2 = forceFieldEnergy2;
362     lambdaInterface1 = forceFieldEnergy1;
363     lambdaInterface2 = forceFieldEnergy2;
364 
365     /* Atom array for topology 1. */
366     Atom[] atoms1 = topology1.getAtomArray();
367     /* Atom array for topology 2. */
368     Atom[] atoms2 = topology2.getAtomArray();
369 
370     ForceField forceField1 = topology1.getForceField();
371     doValenceRestraint1 = forceField1.getBoolean("LAMBDA_VALENCE_RESTRAINTS", true);
372     ForceField forceField2 = topology2.getForceField();
373     doValenceRestraint2 = forceField2.getBoolean("LAMBDA_VALENCE_RESTRAINTS", true);
374 
375     useFirstSystemBondedEnergy = forceField2.getBoolean("USE_FIRST_SYSTEM_BONDED_ENERGY", false);
376 
377     // Check that all atoms that are not undergoing alchemy are common to both topologies.
378     int shared1 = 0;
379     int shared2 = 0;
380     int activeCount1 = 0;
381     int activeCount2 = 0;
382     for (Atom a1 : atoms1) {
383       if (a1.isActive()) {
384         activeCount1++;
385         if (!a1.applyLambda()) {
386           shared1++;
387         }
388       }
389     }
390     for (Atom a2 : atoms2) {
391       if (a2.isActive()) {
392         activeCount2++;
393         if (!a2.applyLambda()) {
394           shared2++;
395         }
396       }
397     }
398 
399     if (shared1 != shared2) {
400       logger.severe(" Shared active atoms are not equal between topologies:\n"
401           + " Topology 1 has " + shared1 + " shared atoms.\n"
402           + " Topology 2 has " + shared2 + " shared atoms.\n"
403           + " Please check the input files or force field parameters.");
404     }
405 
406     assert (shared1 == shared2);
407     nActive1 = activeCount1;
408     nActive2 = activeCount2;
409     activeAtoms1 = new Atom[nActive1];
410     activeAtoms2 = new Atom[nActive2];
411     sharedAtoms1 = new boolean[nActive1];
412     sharedAtoms2 = new boolean[nActive2];
413     Arrays.fill(sharedAtoms1, true);
414     Arrays.fill(sharedAtoms2, true);
415 
416     // Create a dual topology list that includes all atoms from both topologies (even inactive atoms).
417     int nAlchemical1 = 0;
418     int nShared1 = 0;
419     int nAlchemical2 = 0;
420     int nShared2 = 0;
421     for (Atom a : atoms1) {
422       if (a.applyLambda()) {
423         nAlchemical1++;
424       } else {
425         nShared1++;
426       }
427     }
428     for (Atom a : atoms2) {
429       if (a.applyLambda()) {
430         nAlchemical2++;
431       } else {
432         nShared2++;
433       }
434     }
435     if (nShared1 != nShared2) {
436       logger.severe(" Shared atoms are not equal between topologies:\n"
437           + " Topology 1 has " + nShared1 + " shared atoms.\n"
438           + " Topology 2 has " + nShared2 + " shared atoms.\n"
439           + " Please check the input files or force field parameters.");
440     }
441     dualTopologyAtoms = new Atom[nShared1 + nAlchemical1 + nAlchemical2];
442     dualTopologyAtoms2 = new Atom[nShared1 + nAlchemical1 + nAlchemical2];
443     int indexShared = 0;
444     int indexAlchemical = nShared1;
445     int index1 = 0;
446     system1AtomIndex = new int[atoms1.length];
447     for (Atom a : atoms1) {
448       a.setTopologyIndex(0);
449       if (a.applyLambda()) {
450         system1AtomIndex[index1] = indexAlchemical;
451         a.setTopologyAtomIndex(indexAlchemical);
452         dualTopologyAtoms[indexAlchemical] = a;
453         dualTopologyAtoms2[indexAlchemical] = a;
454         indexAlchemical++;
455       } else {
456         system1AtomIndex[index1] = indexShared;
457         a.setTopologyAtomIndex(indexShared);
458         dualTopologyAtoms[indexShared] = a;
459         indexShared++;
460       }
461       index1++;
462     }
463     // Reset the shared index.
464     indexShared = 0;
465     int index2 = 0;
466     system2AtomIndex = new int[atoms2.length];
467     for (Atom a : atoms2) {
468       a.setTopologyIndex(1);
469       if (a.applyLambda()) {
470         system2AtomIndex[index2] = indexAlchemical;
471         a.setTopologyAtomIndex(indexAlchemical);
472         dualTopologyAtoms[indexAlchemical] = a;
473         dualTopologyAtoms2[indexAlchemical] = a;
474         indexAlchemical++;
475       } else {
476         system2AtomIndex[index2] = indexShared;
477         a.setTopologyAtomIndex(indexShared);
478         dualTopologyAtoms2[indexShared] = a;
479         indexShared++;
480       }
481       index2++;
482     }
483 
484     // Fill the active atom arrays and shared atom flags.
485     int index = 0;
486     for (Atom a1 : atoms1) {
487       if (a1.isActive()) {
488         activeAtoms1[index] = a1;
489         if (a1.applyLambda()) {
490           // This atom is softcore with independent coordinates.
491           sharedAtoms1[index] = false;
492         }
493         index++;
494       }
495     }
496     index = 0;
497     for (Atom a2 : atoms2) {
498       if (a2.isActive()) {
499         activeAtoms2[index] = a2;
500         if (a2.applyLambda()) {
501           // This atom is softcore with independent coordinates.
502           sharedAtoms2[index] = false;
503         }
504         index++;
505       }
506     }
507 
508     nShared = shared1;
509     /* Topology 1 number of softcore atoms. */
510     int nSoftCore1 = nActive1 - nShared;
511     /* Topology 2 number of softcore atoms. */
512     int nSoftCore2 = nActive2 - nShared;
513     /* Total number of softcore and shared atoms: nTotal = nShared + nSoftcore1 + nSoftcore2 */
514     int nTotal = nShared + nSoftCore1 + nSoftCore2;
515     nVariables = 3 * nTotal;
516 
517     // Allocate memory for coordinates and derivatives.
518     x1 = new double[nActive1 * 3];
519     x2 = new double[nActive2 * 3];
520     g1 = new double[nActive1 * 3];
521     g2 = new double[nActive2 * 3];
522     rg1 = new double[nActive1 * 3];
523     rg2 = new double[nActive2 * 3];
524     gl1 = new double[nActive1 * 3];
525     gl2 = new double[nActive2 * 3];
526     rgl1 = new double[nActive1 * 3];
527     rgl2 = new double[nActive2 * 3];
528 
529     // All variables are coordinates.
530     index = 0;
531     variableTypes = new VARIABLE_TYPE[nVariables];
532     for (int i = 0; i < nTotal; i++) {
533       variableTypes[index++] = VARIABLE_TYPE.X;
534       variableTypes[index++] = VARIABLE_TYPE.Y;
535       variableTypes[index++] = VARIABLE_TYPE.Z;
536     }
537 
538     // Fill the mass array.
539     int commonIndex = 0;
540     int softcoreIndex = 3 * nShared;
541     mass = new double[nVariables];
542     for (int i = 0; i < nActive1; i++) {
543       Atom a = activeAtoms1[i];
544       double m = a.getMass();
545       if (sharedAtoms1[i]) {
546         mass[commonIndex++] = m;
547         mass[commonIndex++] = m;
548         mass[commonIndex++] = m;
549       } else {
550         mass[softcoreIndex++] = m;
551         mass[softcoreIndex++] = m;
552         mass[softcoreIndex++] = m;
553       }
554     }
555     commonIndex = 0;
556     for (int i = 0; i < nActive2; i++) {
557       Atom a = activeAtoms2[i];
558       double m = a.getMass();
559       if (sharedAtoms2[i]) {
560         for (int j = 0; j < 3; j++) {
561           double massCommon = mass[commonIndex];
562           massCommon = Math.max(massCommon, m);
563           mass[commonIndex++] = massCommon;
564         }
565       } else {
566         mass[softcoreIndex++] = m;
567         mass[softcoreIndex++] = m;
568         mass[softcoreIndex++] = m;
569       }
570     }
571     energyRegion = new EnergyRegion();
572     parallelTeam = new ParallelTeam(1);
573 
574     this.switchFunction = switchFunction;
575     logger.info(format("\n Dual topology using switching function:\n  %s", switchFunction));
576     logger.info(format(" Shared atoms: %d (1: %d of %d; 2: %d of %d)\n ", nShared, shared1, nActive1, shared2, nActive2));
577     String symOpString = forceField2.getString("symop", null);
578     if (symOpString != null) {
579       readSymOp(symOpString);
580     } else {
581       // Both end-states will use the same crystal object.
582       potential2.setCrystal(potential1.getCrystal());
583       useSymOp = false;
584       symOp = null;
585       inverse = null;
586     }
587 
588     // Check that all Dual-Topology atoms start with identical coordinates.
589     int i1 = 0;
590     int i2 = 0;
591     // Not true if mapping atoms via symmetry operator.
592     if (!useSymOp) {
593       for (int i = 0; i < nShared; i++) {
594         Atom a1 = atoms1[i1++];
595         while (a1.applyLambda()) {
596           a1 = atoms1[i1++];
597         }
598         Atom a2 = atoms2[i2++];
599         while (a2.applyLambda()) {
600           a2 = atoms2[i2++];
601         }
602         assert (a1.getX() == a2.getX());
603         assert (a1.getY() == a2.getY());
604         assert (a1.getZ() == a2.getZ());
605         reconcileAtoms(a1, a2, Level.INFO);
606       }
607     }
608   }
609 
610   /**
611    * Static factory method to create a DualTopologyEnergy, possibly via FFX or OpenMM implementations.
612    *
613    * @param molecularAssembly1 MolecularAssembly for topology 1.
614    * @param molecularAssembly2 MolecularAssembly for topology 2.
615    * @param switchFunction     The UnivariateSwitchingFunction to use for the dual topology energy.
616    * @return A DualTopologyEnergy on some Platform
617    */
618   @SuppressWarnings("fallthrough")
619   public static DualTopologyEnergy energyFactory(MolecularAssembly molecularAssembly1,
620                                                  MolecularAssembly molecularAssembly2,
621                                                  UnivariateSwitchingFunction switchFunction) {
622     ForceField forceField = molecularAssembly1.getForceField();
623     String platformString = toEnumForm(forceField.getString("PLATFORM-DT", "FFX"));
624     try {
625       Platform platform = Platform.valueOf(platformString);
626       switch (platform) {
627         case OMM, OMM_REF, OMM_CUDA, OMM_OPENCL:
628           try {
629             return new OpenMMDualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction, platform);
630           } catch (Exception ex) {
631             logger.warning(format(" Exception creating OpenMMDualTopologyEnergy: %s", ex));
632             return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
633           }
634         case OMM_CPU:
635           logger.warning(format(" Platform %s not supported; defaulting to FFX", platform));
636         default:
637           return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
638       }
639     } catch (IllegalArgumentException | NullPointerException ex) {
640       logger.warning(
641           format(" String %s did not match a known energy implementation", platformString));
642       return new DualTopologyEnergy(molecularAssembly1, molecularAssembly2, switchFunction);
643     }
644   }
645 
646 
647   /**
648    * Get the number of dual topology atoms.
649    *
650    * @return The number of dual topology atoms.
651    */
652   public int getNumberOfAtoms() {
653     return dualTopologyAtoms.length;
654   }
655 
656   /**
657    * Get the dual topology atoms for the specified topology.
658    *
659    * @param topology The topology index (0 for topology 1, 1 for topology 2).
660    * @return An array of Atoms for dual-topology with shared atoms from the specified topology.
661    */
662   public Atom[] getDualTopologyAtoms(int topology) {
663     if (topology == 0) {
664       return dualTopologyAtoms;
665     } else if (topology == 1) {
666       return dualTopologyAtoms2;
667     } else {
668       throw new IllegalArgumentException(" Invalid topology index: " + topology);
669     }
670   }
671 
672   /**
673    * Get the atom from the dual-topology atom array corresponding to the specified topology and index.
674    *
675    * @param topology The topology index (0 for topology 1, 1 for topology 2).
676    * @param index    The index of the atom in the dual topology atom array.
677    * @return The Atom from the dual-topology atom array corresponding to the specified topology and index.
678    */
679   public Atom getDualTopologyAtom(int topology, int index) {
680     if (topology == 0) {
681       return dualTopologyAtoms[index];
682     } else if (topology == 1) {
683       return dualTopologyAtoms2[index];
684     } else {
685       throw new IllegalArgumentException(" Invalid topology index: " + topology);
686     }
687   }
688 
689   /**
690    * Map an atomic index from a single topology to the overall dual-topology atom array.
691    *
692    * @param topology The topology index (0 for topology 1, 1 for topology 2).
693    * @param index    The index of the atom in the specified topology.
694    * @return The index of the atom in the overall dual-topology atom array.
695    */
696   public int mapToDualTopologyIndex(int topology, int index) {
697     if (topology == 0) {
698       return system1AtomIndex[index];
699     } else if (topology == 1) {
700       return system2AtomIndex[index];
701     } else {
702       throw new IllegalArgumentException(" Invalid topology index: " + topology);
703     }
704   }
705 
706   /**
707    * Get the ForceFieldEnergy for topology 1.
708    *
709    * @return The ForceFieldEnergy for topology 1.
710    */
711   public ForceFieldEnergy getForceFieldEnergy1() {
712     return forceFieldEnergy1;
713   }
714 
715   /**
716    * Get the ForceFieldEnergy for topology 2.
717    *
718    * @return The ForceFieldEnergy for topology 2.
719    */
720   public ForceFieldEnergy getForceFieldEnergy2() {
721     return forceFieldEnergy2;
722   }
723 
724   /**
725    * Get the atom index for topology 1 into the overall dual-topology atom array.
726    *
727    * @return An array of indices for topology 1 atoms into the dual-topology atom array.
728    */
729   public int[] getDualTopologyIndex1() {
730     return system1AtomIndex;
731   }
732 
733   /**
734    * Get the atom index for topology 2 into the overall dual-topology atom array.
735    *
736    * @return An array of indices for topology 2 atoms into the dual-topology atom array.
737    */
738   public int[] getDualTopologyIndex2() {
739     return system2AtomIndex;
740   }
741 
742   /**
743    * The scale factor for the specified topology.
744    *
745    * @param topology The topology index (0 for topology 1, 1 for topology 2).
746    * @return The scale factor for the topology.
747    */
748   public double getTopologyScale(int topology) {
749     if (topology == 0) {
750       return f1L;
751     } else if (topology == 1) {
752       return f2L;
753     } else {
754       throw new IllegalArgumentException(" Invalid topology index: " + topology);
755     }
756   }
757 
758   /**
759    * Temporary method to be used as a benchmark. Move to SymOp?
760    *
761    * @param symOpString String containing sym op.
762    */
763   private void readSymOp(String symOpString) {
764     int numSymOps;
765     try {
766       String[] tokens = symOpString.split(" +");
767       int numTokens = tokens.length;
768       // Only need to move one system onto the other.
769       if (numTokens == 12) {
770         numSymOps = 1;
771         symOp = new SymOp[numSymOps];
772         inverse = new SymOp[numSymOps];
773         // Assume applies to all atoms...
774         symOpAtoms.add(parseAtomRanges("symmetryAtoms", "1-N", nActive2));
775         // Twelve values makes 1 sym op. so assign to [0][0].
776         symOp[0] = new SymOp(new double[][]{
777             {parseDouble(tokens[0]), parseDouble(tokens[1]), parseDouble(tokens[2])},
778             {parseDouble(tokens[3]), parseDouble(tokens[4]), parseDouble(tokens[5])},
779             {parseDouble(tokens[6]), parseDouble(tokens[7]), parseDouble(tokens[8])}},
780             new double[]{parseDouble(tokens[9]), parseDouble(tokens[10]), parseDouble(tokens[11])});
781       } else if (numTokens % 14 == 0) {
782         numSymOps = numTokens / 14;
783         symOp = new SymOp[numSymOps];
784         inverse = new SymOp[numSymOps];
785         if (logger.isLoggable(Level.FINER)) {
786           logger.finer(format(" Number of Tokens: %3d Number of Sym Ops: %2d", numTokens, numSymOps));
787         }
788         for (int i = 0; i < numSymOps; i++) {
789           int j = i * 14;
790           // Should be tokens[j] or tokens[j+1]?? Switches forward vs backward...
791           symOpAtoms.add(parseAtomRanges("symmetryAtoms", tokens[j], nActive2));
792           symOp[i] = new SymOp(new double[][]{
793               {parseDouble(tokens[j + 2]), parseDouble(tokens[j + 3]), parseDouble(tokens[j + 4])},
794               {parseDouble(tokens[j + 5]), parseDouble(tokens[j + 6]), parseDouble(tokens[j + 7])},
795               {parseDouble(tokens[j + 8]), parseDouble(tokens[j + 9]), parseDouble(tokens[j + 10])}},
796               new double[]{parseDouble(tokens[j + 11]), parseDouble(tokens[j + 12]), parseDouble(tokens[j + 13])});
797         }
798       } else {
799         logger.warning(format(" Provided symmetry operator is formatted incorrectly. Should have 12 or 14 entries, but found %3d.", numTokens));
800         return;
801       }
802       useSymOp = true;
803       // Define mask(s) needed to apply sym ops.
804       mask = new boolean[numSymOps][nActive2];
805       // Mask(s) used locally to check symmetry operator overlap.
806       boolean[][] sharedMask = new boolean[numSymOps][nShared];
807       for (int i = 0; i < numSymOps; i++) {
808         List<Integer> symAtoms = symOpAtoms.get(i);
809         for (int atom : symAtoms) {
810           if (sharedAtoms2[atom]) {
811             mask[i][atom] = true;
812           }
813         }
814         // TODO: Condense with loop above by replacing nActive2 with incremental index?
815         int sharedIndex = 0;
816         for (int j = 0; j < nActive2; j++) {
817           if (sharedAtoms2[j]) {
818             if (mask[i][j]) {
819               sharedMask[i][sharedIndex] = true;
820             }
821             sharedIndex++;
822           }
823         }
824       }
825 
826       // Get the coordinates for active atoms.
827       potential1.getCoordinates(x1);
828       potential2.getCoordinates(x2);
829 
830       // Collect coordinates for shared atoms (remove alchemical atoms from the superposition).
831       double[] x1Shared = new double[nShared * 3];
832       double[] x2Shared = new double[nShared * 3];
833       int sharedIndex = 0;
834       for (int i = 0; i < nActive1; i++) {
835         if (sharedAtoms1[i]) {
836           int index = i * 3;
837           x1Shared[sharedIndex++] = x1[index];
838           x1Shared[sharedIndex++] = x1[index + 1];
839           x1Shared[sharedIndex++] = x1[index + 2];
840         }
841       }
842       sharedIndex = 0;
843       for (int i = 0; i < nActive2; i++) {
844         if (sharedAtoms2[i]) {
845           int index = i * 3;
846           x2Shared[sharedIndex++] = x2[index];
847           x2Shared[sharedIndex++] = x2[index + 1];
848           x2Shared[sharedIndex++] = x2[index + 2];
849         }
850       }
851 
852       double origRMSD = rmsd(x1Shared, x2Shared, mass);
853       logger.info("\n RMSD to topology 2 coordinates from input file:");
854       logger.info(format(" RMSD: %12.3f A", origRMSD));
855 
856       // Get a fresh copy of the original coordinates.
857       double[] origX1 = Arrays.copyOf(x1Shared, nShared * 3);
858       double[] origX2 = Arrays.copyOf(x2Shared, nShared * 3);
859 
860       // Determine if first topology or second?
861       // Explicitly written (as opposed to applySymOp()) since only applied to shared atoms.
862       for (int i = 0; i < numSymOps; i++) {
863         // Transpose == inverse for orthogonal matrices (value needed later).
864         inverse[i] = invertSymOp(symOp[i]);
865         // Apply symOp to appropriate coords (validity check).
866         // Applied to topology 1 to generate approximate coordinates for topology 2.
867         applyCartesianSymOp(origX1, origX1, symOp[i], sharedMask[i]);
868         logger.info(format("\n SymOp %3d of %3d between topologies:\n Applied to atoms: %s\n %s\n Inverse SymOp:\n %s",
869             i + 1, numSymOps, writeAtomRanges(symOpAtoms.get(i).stream().mapToInt(j -> j).toArray()), symOp[i].toString(), inverse[i].toString()));
870       }
871       double symOpRMSD = rmsd(origX1, origX2, mass);
872       logger.info("\n RMSD to topology 2 coordinates via loaded symop:");
873       logger.info(format(" RMSD: %12.3f A", symOpRMSD));
874 
875       // TODO: Validate 0th order check for alchemical atoms (i.e., not bonded between sym ops).
876       logger.info("\n Checking alchemical atoms for system 1:");
877       validateAlchemicalAtoms(nActive1, sharedAtoms1, activeAtoms1);
878       logger.info(" Checking alchemical atoms for system 2:");
879       validateAlchemicalAtoms(nActive2, sharedAtoms2, activeAtoms2);
880 
881       if (logger.isLoggable(Level.FINE)) {
882         for (int i = 0; i < nShared; i++) {
883           int i3 = i * 3;
884           double rmsd = rmsd(new double[]{origX1[i3], origX1[i3 + 1], origX1[i3 + 2]}, new double[]{origX2[i3], origX2[i3 + 1], origX2[i3 + 2]}, new double[]{mass[i]});
885           if (rmsd > 0.5) {
886             logger.fine(format(" Shared atom %3d has a distance of %8.3f", i + 1, rmsd));
887           }
888         }
889       }
890     } catch (Exception ex) {
891       logger.severe(" Error parsing SymOp for Dual Topology:\n (" + symOpString + ")" + Utilities.stackTraceToString(ex));
892     }
893   }
894 
895   /**
896    * {@inheritDoc}
897    *
898    * <p>Returns true if both force field energies are zero at the ends, and the switching function's
899    * first derivative is zero at the end bounds.
900    */
901   @Override
902   public boolean dEdLZeroAtEnds() {
903     if (!forceFieldEnergy1.dEdLZeroAtEnds() || !forceFieldEnergy2.dEdLZeroAtEnds()) {
904       return false;
905     }
906     return (switchFunction.highestOrderZeroDerivativeAtZeroBound() > 0);
907   }
908 
909   /**
910    * {@inheritDoc}
911    */
912   @Override
913   public boolean destroy() {
914     boolean ffe1Destroy = forceFieldEnergy1.destroy();
915     boolean ffe2Destroy = forceFieldEnergy2.destroy();
916     try {
917       if (parallelTeam != null) {
918         parallelTeam.shutdown();
919       }
920       return ffe1Destroy && ffe2Destroy;
921     } catch (Exception ex) {
922       logger.warning(format(" Exception in shutting down DualTopologyEnergy: %s", ex));
923       logger.info(Utilities.stackTraceToString(ex));
924       return false;
925     }
926   }
927 
928   /**
929    * {@inheritDoc}
930    */
931   @Override
932   public double energy(double[] x) {
933     return energy(x, false);
934   }
935 
936   /**
937    * {@inheritDoc}
938    */
939   @Override
940   public double energy(double[] x, boolean verbose) {
941     try {
942       energyRegion.setX(x);
943       energyRegion.setVerbose(verbose);
944       parallelTeam.execute(energyRegion);
945     } catch (Exception ex) {
946       throw new EnergyException(format(" Exception in calculating dual-topology energy: %s", ex));
947     }
948     return totalEnergy;
949   }
950 
951   /**
952    * {@inheritDoc}
953    *
954    * <p>The coordinate and gradient arrays are unpacked/packed based on the dual topology.
955    */
956   @Override
957   public double energyAndGradient(double[] x, double[] g) {
958     return energyAndGradient(x, g, false);
959   }
960 
961   /**
962    * {@inheritDoc}
963    *
964    * <p>The coordinate and gradient arrays are unpacked/packed based on the dual topology.
965    */
966   @Override
967   public double energyAndGradient(double[] x, double[] g, boolean verbose) {
968     assert Arrays.stream(x).allMatch(Double::isFinite);
969     try {
970       energyRegion.setX(x);
971       energyRegion.setG(g);
972       energyRegion.setVerbose(verbose);
973       parallelTeam.execute(energyRegion);
974     } catch (Exception ex) {
975       throw new EnergyException(format(" Exception in calculating dual-topology energy: %s", ex));
976     }
977     return totalEnergy;
978   }
979 
980   /**
981    * {@inheritDoc}
982    */
983   @Override
984   public double[] getAcceleration(double[] acceleration) {
985     if (acceleration == null || acceleration.length < nVariables) {
986       acceleration = new double[nVariables];
987     }
988     int indexCommon = 0;
989     int indexUnique = nShared * 3;
990     double[] accel = new double[3];
991     for (int i = 0; i < nActive1; i++) {
992       Atom atom = activeAtoms1[i];
993       atom.getAcceleration(accel);
994       if (sharedAtoms1[i]) {
995         acceleration[indexCommon++] = accel[0];
996         acceleration[indexCommon++] = accel[1];
997         acceleration[indexCommon++] = accel[2];
998       } else {
999         acceleration[indexUnique++] = accel[0];
1000         acceleration[indexUnique++] = accel[1];
1001         acceleration[indexUnique++] = accel[2];
1002       }
1003     }
1004     for (int i = 0; i < nActive2; i++) {
1005       if (!sharedAtoms2[i]) {
1006         Atom atom = activeAtoms2[i];
1007         atom.getAcceleration(accel);
1008         acceleration[indexUnique++] = accel[0];
1009         acceleration[indexUnique++] = accel[1];
1010         acceleration[indexUnique++] = accel[2];
1011       }
1012     }
1013 
1014     return acceleration;
1015   }
1016 
1017   /**
1018    * {@inheritDoc}
1019    */
1020   @Override
1021   public double[] getCoordinates(double[] x) {
1022     if (x == null) {
1023       x = new double[nVariables];
1024     }
1025     int indexCommon = 0;
1026     int indexUnique = nShared * 3;
1027     for (int i = 0; i < nActive1; i++) {
1028       Atom a = activeAtoms1[i];
1029       if (sharedAtoms1[i]) {
1030         x[indexCommon++] = a.getX();
1031         x[indexCommon++] = a.getY();
1032         x[indexCommon++] = a.getZ();
1033       } else {
1034         x[indexUnique++] = a.getX();
1035         x[indexUnique++] = a.getY();
1036         x[indexUnique++] = a.getZ();
1037       }
1038     }
1039     for (int i = 0; i < nActive2; i++) {
1040       if (!sharedAtoms2[i]) {
1041         Atom a = activeAtoms2[i];
1042         x[indexUnique++] = a.getX();
1043         x[indexUnique++] = a.getY();
1044         x[indexUnique++] = a.getZ();
1045       }
1046     }
1047     return x;
1048   }
1049 
1050   /**
1051    * {@inheritDoc}
1052    */
1053   @Override
1054   public void setCoordinates(@Nullable double[] x) {
1055     int indexCommon = 0;
1056     int indexUnique = nShared * 3;
1057     double[] xyz = new double[3];
1058     for (int i = 0; i < nActive1; i++) {
1059       if (sharedAtoms1[i]) {
1060         xyz[0] = x[indexCommon++];
1061         xyz[1] = x[indexCommon++];
1062         xyz[2] = x[indexCommon++];
1063       } else {
1064         xyz[0] = x[indexUnique++];
1065         xyz[1] = x[indexUnique++];
1066         xyz[2] = x[indexUnique++];
1067       }
1068       Atom a = activeAtoms1[i];
1069       a.setXYZ(xyz);
1070     }
1071     // Reset the common index.
1072     indexCommon = 0;
1073     for (int i = 0; i < nActive2; i++) {
1074       if (sharedAtoms2[i]) {
1075         xyz[0] = x[indexCommon++];
1076         xyz[1] = x[indexCommon++];
1077         xyz[2] = x[indexCommon++];
1078       } else {
1079         xyz[0] = x[indexUnique++];
1080         xyz[1] = x[indexUnique++];
1081         xyz[2] = x[indexUnique++];
1082       }
1083       Atom a = activeAtoms2[i];
1084       a.setXYZ(xyz);
1085     }
1086   }
1087 
1088   /**
1089    * {@inheritDoc}
1090    */
1091   @Override
1092   public Crystal getCrystal() {
1093     // TODO: Handle the case where each system has a unique crystal instance (e.g., different space groups).
1094     if (useSymOp && logger.isLoggable(Level.FINE)) {
1095       logger.fine(" Get method only returned first crystal.");
1096     }
1097     return potential1.getCrystal();
1098   }
1099 
1100   /**
1101    * {@inheritDoc}
1102    */
1103   @Override
1104   public void setCrystal(Crystal crystal) {
1105     // TODO: Handle the case where each system has a unique crystal instance (e.g., different space groups).
1106     if (useSymOp && logger.isLoggable(Level.FINE)) {
1107       logger.fine(" Both systems set to the same crystal.");
1108     }
1109     potential1.setCrystal(crystal);
1110     potential2.setCrystal(crystal);
1111   }
1112 
1113   /**
1114    * {@inheritDoc}
1115    */
1116   @Override
1117   public STATE getEnergyTermState() {
1118     return state;
1119   }
1120 
1121   /**
1122    * {@inheritDoc}
1123    */
1124   @Override
1125   public void setEnergyTermState(STATE state) {
1126     this.state = state;
1127     potential1.setEnergyTermState(state);
1128     potential2.setEnergyTermState(state);
1129   }
1130 
1131   /**
1132    * {@inheritDoc}
1133    */
1134   @Override
1135   public double getLambda() {
1136     return lambda;
1137   }
1138 
1139   /**
1140    * {@inheritDoc}
1141    */
1142   @Override
1143   public void setLambda(double lambda) {
1144     if (lambda <= 1.0 && lambda >= 0.0) {
1145       this.lambda = lambda;
1146       double oneMinusLambda = 1.0 - lambda;
1147       lambdaInterface1.setLambda(lambda);
1148       lambdaInterface2.setLambda(oneMinusLambda);
1149 
1150       f1L = switchFunction.valueAt(lambda);
1151       dF1dL = switchFunction.firstDerivative(lambda);
1152       d2F1dL2 = switchFunction.secondDerivative(lambda);
1153 
1154       f2L = switchFunction.valueAt(oneMinusLambda);
1155       dF2dL = -1.0 * switchFunction.firstDerivative(oneMinusLambda);
1156       d2F2dL2 = switchFunction.secondDerivative(oneMinusLambda);
1157     } else {
1158       String message = format("Lambda value %8.3f is not in the range [0..1].", lambda);
1159       throw new IllegalArgumentException(message);
1160     }
1161   }
1162 
1163   /**
1164    * {@inheritDoc}
1165    */
1166   @Override
1167   public double[] getMass() {
1168     return mass;
1169   }
1170 
1171   /**
1172    * Returns the number of shared variables (3 * number of shared atoms).
1173    *
1174    * @return An int
1175    */
1176   public int getNumSharedVariables() {
1177     return 3 * nShared;
1178   }
1179 
1180   /**
1181    * {@inheritDoc}
1182    */
1183   @Override
1184   public int getNumberOfVariables() {
1185     return nVariables;
1186   }
1187 
1188   /**
1189    * {@inheritDoc}
1190    */
1191   @Override
1192   public double[] getPreviousAcceleration(double[] previousAcceleration) {
1193     if (previousAcceleration == null || previousAcceleration.length < nVariables) {
1194       previousAcceleration = new double[nVariables];
1195     }
1196     int indexCommon = 0;
1197     int indexUnique = nShared * 3;
1198     double[] prev = new double[3];
1199     for (int i = 0; i < nActive1; i++) {
1200       Atom atom = activeAtoms1[i];
1201       atom.getPreviousAcceleration(prev);
1202       if (sharedAtoms1[i]) {
1203         previousAcceleration[indexCommon++] = prev[0];
1204         previousAcceleration[indexCommon++] = prev[1];
1205         previousAcceleration[indexCommon++] = prev[2];
1206       } else {
1207         previousAcceleration[indexUnique++] = prev[0];
1208         previousAcceleration[indexUnique++] = prev[1];
1209         previousAcceleration[indexUnique++] = prev[2];
1210       }
1211     }
1212     for (int i = 0; i < nActive2; i++) {
1213       Atom atom = activeAtoms2[i];
1214       if (!sharedAtoms2[i]) {
1215         atom.getPreviousAcceleration(prev);
1216         previousAcceleration[indexUnique++] = prev[0];
1217         previousAcceleration[indexUnique++] = prev[1];
1218         previousAcceleration[indexUnique++] = prev[2];
1219       }
1220     }
1221 
1222     return previousAcceleration;
1223   }
1224 
1225   /**
1226    * {@inheritDoc}
1227    */
1228   @Override
1229   public double[] getScaling() {
1230     return scaling;
1231   }
1232 
1233   /**
1234    * {@inheritDoc}
1235    */
1236   @Override
1237   public void setScaling(double[] scaling) {
1238     this.scaling = scaling;
1239   }
1240 
1241   /**
1242    * Returns the switching function used by this DualTopologyEnergy; presently, switching functions
1243    * are immutable, and cannot be changed once a DualTopologyEnergy is constructed.
1244    *
1245    * @return The switching function.
1246    */
1247   public UnivariateSwitchingFunction getSwitch() {
1248     return switchFunction;
1249   }
1250 
1251   /**
1252    * {@inheritDoc}
1253    */
1254   @Override
1255   public double getTotalEnergy() {
1256     return totalEnergy;
1257   }
1258 
1259   @Override
1260   public List<Potential> getUnderlyingPotentials() {
1261     List<Potential> under = new ArrayList<>(2);
1262     under.add(forceFieldEnergy1);
1263     under.add(forceFieldEnergy2);
1264     under.addAll(forceFieldEnergy1.getUnderlyingPotentials());
1265     under.addAll(forceFieldEnergy2.getUnderlyingPotentials());
1266     return under;
1267   }
1268 
1269   /**
1270    * {@inheritDoc}
1271    */
1272   @Override
1273   public VARIABLE_TYPE[] getVariableTypes() {
1274     return variableTypes;
1275   }
1276 
1277   /**
1278    * {@inheritDoc}
1279    */
1280   @Override
1281   public double[] getVelocity(double[] velocity) {
1282     if (velocity == null || velocity.length < nVariables) {
1283       velocity = new double[nVariables];
1284     }
1285     int indexCommon = 0;
1286     int indexUnique = nShared * 3;
1287     double[] vel = new double[3];
1288     for (int i = 0; i < nActive1; i++) {
1289       Atom atom = activeAtoms1[i];
1290       atom.getVelocity(vel);
1291       if (sharedAtoms1[i]) {
1292         velocity[indexCommon++] = vel[0];
1293         velocity[indexCommon++] = vel[1];
1294         velocity[indexCommon++] = vel[2];
1295       } else {
1296         velocity[indexUnique++] = vel[0];
1297         velocity[indexUnique++] = vel[1];
1298         velocity[indexUnique++] = vel[2];
1299       }
1300     }
1301     for (int i = 0; i < nActive2; i++) {
1302       if (!sharedAtoms2[i]) {
1303         Atom atom = activeAtoms2[i];
1304         atom.getVelocity(vel);
1305         velocity[indexUnique++] = vel[0];
1306         velocity[indexUnique++] = vel[1];
1307         velocity[indexUnique++] = vel[2];
1308       }
1309     }
1310 
1311     return velocity;
1312   }
1313 
1314   /**
1315    * {@inheritDoc}
1316    */
1317   @Override
1318   public double getd2EdL2() {
1319     double e1 =
1320         f1L * d2EdL2_1
1321             + 2.0 * dF1dL * dEdL_1
1322             + d2F1dL2 * energy1
1323             + f2L * restraintd2EdL2_1
1324             + 2.0 * dF2dL * restraintdEdL_1
1325             + d2F2dL2 * restraintEnergy1;
1326     double e2;
1327     if (!useFirstSystemBondedEnergy) {
1328       e2 = f2L * d2EdL2_2
1329           + 2.0 * dF2dL * dEdL_2
1330           + d2F2dL2 * energy2
1331           + f1L * restraintd2EdL2_2
1332           + 2.0 * dF1dL * restraintdEdL_2
1333           + d2F1dL2 * restraintEnergy2;
1334     } else {
1335       e2 = f2L * d2EdL2_2
1336           + 2.0 * dF2dL * dEdL_2
1337           + d2F2dL2 * energy2
1338           - f2L * restraintd2EdL2_2
1339           - 2.0 * dF2dL * restraintdEdL_2
1340           - d2F2dL2 * restraintEnergy2;
1341     }
1342     return e1 + e2;
1343   }
1344 
1345   /**
1346    * {@inheritDoc}
1347    */
1348   @Override
1349   public double getdEdL() {
1350     // Subtraction is implicit, as dEdL_2 and dF2dL are both multiplied by
1351     // negative 1 when set.
1352     double e1 = f1L * dEdL_1 + dF1dL * energy1 + f2L * restraintdEdL_1 + dF2dL * restraintEnergy1;
1353     double e2;
1354     if (!useFirstSystemBondedEnergy) {
1355       e2 = f2L * dEdL_2 + dF2dL * energy2 + f1L * restraintdEdL_2 + dF1dL * restraintEnergy2;
1356     } else {
1357       e2 = f2L * dEdL_2 + dF2dL * energy2 - f2L * restraintdEdL_2 - dF2dL * restraintEnergy2;
1358     }
1359     return e1 + e2;
1360   }
1361 
1362   /**
1363    * {@inheritDoc}
1364    */
1365   @Override
1366   public void getdEdXdL(double[] g) {
1367     if (g == null) {
1368       g = new double[nVariables];
1369     }
1370 
1371     int index = 0;
1372     int indexCommon = 0;
1373     int indexUnique = nShared * 3;
1374     // Coordinate Gradient from Topology 1.
1375     for (int i = 0; i < nActive1; i++) {
1376       if (sharedAtoms1[i]) {
1377         g[indexCommon++] =
1378             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1379         g[indexCommon++] =
1380             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1381         g[indexCommon++] =
1382             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1383       } else {
1384         g[indexUnique++] =
1385             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1386         g[indexUnique++] =
1387             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1388         g[indexUnique++] =
1389             f1L * gl1[index] + dF1dL * g1[index] + f2L * rgl1[index] + dF2dL * rg1[index++];
1390       }
1391     }
1392 
1393     // Coordinate Gradient from Topology 2.
1394     if (!useFirstSystemBondedEnergy) {
1395       index = 0;
1396       indexCommon = 0;
1397       for (int i = 0; i < nActive2; i++) {
1398         if (sharedAtoms2[i]) {
1399           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1400               + dF1dL * rg2[index++]);
1401           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1402               + dF1dL * rg2[index++]);
1403           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1404               + dF1dL * rg2[index++]);
1405         } else {
1406           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1407               + dF1dL * rg2[index++]);
1408           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1409               + dF1dL * rg2[index++]);
1410           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] - f1L * rgl2[index]
1411               + dF1dL * rg2[index++]);
1412         }
1413       }
1414     } else {
1415       index = 0;
1416       indexCommon = 0;
1417       for (int i = 0; i < nActive2; i++) {
1418         if (sharedAtoms2[i]) {
1419           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1420               - dF2dL * rg2[index++]);
1421           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1422               - dF2dL * rg2[index++]);
1423           g[indexCommon++] += (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1424               - dF2dL * rg2[index++]);
1425         } else {
1426           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1427               - dF2dL * rg2[index++]);
1428           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1429               - dF2dL * rg2[index++]);
1430           g[indexUnique++] = (-f2L * gl2[index] + dF2dL * g2[index] + f2L * rgl2[index]
1431               - dF2dL * rg2[index++]);
1432         }
1433       }
1434 
1435     }
1436   }
1437 
1438   /**
1439    * {@inheritDoc}
1440    */
1441   @Override
1442   public void setAcceleration(double[] acceleration) {
1443     double[] accel = new double[3];
1444     int indexCommon = 0;
1445     int indexUnique = 3 * nShared;
1446     for (int i = 0; i < nActive1; i++) {
1447       Atom atom = activeAtoms1[i];
1448       if (sharedAtoms1[i]) {
1449         accel[0] = acceleration[indexCommon++];
1450         accel[1] = acceleration[indexCommon++];
1451         accel[2] = acceleration[indexCommon++];
1452       } else {
1453         accel[0] = acceleration[indexUnique++];
1454         accel[1] = acceleration[indexUnique++];
1455         accel[2] = acceleration[indexUnique++];
1456       }
1457       atom.setAcceleration(accel);
1458     }
1459     indexCommon = 0;
1460     for (int i = 0; i < nActive2; i++) {
1461       Atom atom = activeAtoms2[i];
1462       if (sharedAtoms2[i]) {
1463         accel[0] = acceleration[indexCommon++];
1464         accel[1] = acceleration[indexCommon++];
1465         accel[2] = acceleration[indexCommon++];
1466       } else {
1467         accel[0] = acceleration[indexUnique++];
1468         accel[1] = acceleration[indexUnique++];
1469         accel[2] = acceleration[indexUnique++];
1470       }
1471       atom.setAcceleration(accel);
1472     }
1473   }
1474 
1475   /**
1476    * setParallel.
1477    *
1478    * @param parallel a boolean.
1479    */
1480   public void setParallel(boolean parallel) {
1481     if (parallelTeam != null) {
1482       try {
1483         parallelTeam.shutdown();
1484       } catch (Exception e) {
1485         logger.severe(format(" Exception in shutting down old ParallelTeam for DualTopologyEnergy: %s", e));
1486       }
1487     }
1488     parallelTeam = parallel ? new ParallelTeam(2) : new ParallelTeam(1);
1489   }
1490 
1491   /**
1492    * {@inheritDoc}
1493    */
1494   @Override
1495   public void setPreviousAcceleration(double[] previousAcceleration) {
1496     double[] prev = new double[3];
1497     int indexCommon = 0;
1498     int indexUnique = 3 * nShared;
1499     for (int i = 0; i < nActive1; i++) {
1500       Atom atom = activeAtoms1[i];
1501       if (sharedAtoms1[i]) {
1502         prev[0] = previousAcceleration[indexCommon++];
1503         prev[1] = previousAcceleration[indexCommon++];
1504         prev[2] = previousAcceleration[indexCommon++];
1505       } else {
1506         prev[0] = previousAcceleration[indexUnique++];
1507         prev[1] = previousAcceleration[indexUnique++];
1508         prev[2] = previousAcceleration[indexUnique++];
1509       }
1510       atom.setPreviousAcceleration(prev);
1511     }
1512     indexCommon = 0;
1513     for (int i = 0; i < nActive2; i++) {
1514       Atom atom = activeAtoms2[i];
1515       if (sharedAtoms2[i]) {
1516         prev[0] = previousAcceleration[indexCommon++];
1517         prev[1] = previousAcceleration[indexCommon++];
1518         prev[2] = previousAcceleration[indexCommon++];
1519       } else {
1520         prev[0] = previousAcceleration[indexUnique++];
1521         prev[1] = previousAcceleration[indexUnique++];
1522         prev[2] = previousAcceleration[indexUnique++];
1523       }
1524       atom.setPreviousAcceleration(prev);
1525     }
1526   }
1527 
1528   /**
1529    * Sets the printOnFailure flag; if override is true, over-rides any existing property. Essentially
1530    * sets the default value of printOnFailure for an algorithm. For example, rotamer optimization
1531    * will generally run into force field issues in the normal course of execution as it tries
1532    * unphysical self and pair configurations, so the algorithm should not print out a large number of
1533    * error PDBs.
1534    *
1535    * @param onFail   To set
1536    * @param override Override properties
1537    */
1538   public void setPrintOnFailure(boolean onFail, boolean override) {
1539     forceFieldEnergy1.setPrintOnFailure(onFail, override);
1540     forceFieldEnergy2.setPrintOnFailure(onFail, override);
1541   }
1542 
1543   /**
1544    * {@inheritDoc}
1545    */
1546   @Override
1547   public void setVelocity(double[] velocity) {
1548     double[] vel = new double[3];
1549     int indexCommon = 0;
1550     int indexUnique = 3 * nShared;
1551     for (int i = 0; i < nActive1; i++) {
1552       Atom atom = activeAtoms1[i];
1553       if (sharedAtoms1[i]) {
1554         vel[0] = velocity[indexCommon++];
1555         vel[1] = velocity[indexCommon++];
1556         vel[2] = velocity[indexCommon++];
1557       } else {
1558         vel[0] = velocity[indexUnique++];
1559         vel[1] = velocity[indexUnique++];
1560         vel[2] = velocity[indexUnique++];
1561       }
1562       atom.setVelocity(vel);
1563     }
1564 
1565     indexCommon = 0;
1566     for (int i = 0; i < nActive2; i++) {
1567       Atom atom = activeAtoms2[i];
1568       if (sharedAtoms2[i]) {
1569         vel[0] = velocity[indexCommon++];
1570         vel[1] = velocity[indexCommon++];
1571         vel[2] = velocity[indexCommon++];
1572       } else {
1573         vel[0] = velocity[indexUnique++];
1574         vel[1] = velocity[indexUnique++];
1575         vel[2] = velocity[indexUnique++];
1576       }
1577       atom.setVelocity(vel);
1578     }
1579   }
1580 
1581   /**
1582    * Moves two shared atoms together if there is a small discrepancy (such as that caused by the
1583    * mutator script).
1584    *
1585    * @param a1      Atom from topology 1
1586    * @param a2      Atom from topology 2
1587    * @param warnlev Logging level to use when warning about small movements
1588    */
1589   private void reconcileAtoms(Atom a1, Atom a2, Level warnlev) {
1590     double dist = 0;
1591     double[] xyz1 = a1.getXYZ(null);
1592     double[] xyz2 = a2.getXYZ(null);
1593     double[] xyzAv = new double[3];
1594     for (int i = 0; i < 3; i++) {
1595       double dx = xyz1[i] - xyz2[i];
1596       dist += (dx * dx);
1597       xyzAv[i] = xyz1[i] + (0.5 * dx);
1598     }
1599 
1600     // Square of the maximum distance permissible between two shared atoms.
1601     double maxDist2 = 0.09;
1602 
1603     /*
1604      Square of the minimum distance between shared atoms which will cause a
1605      warning. Intended to cover anything larger than a rounding error.
1606     */
1607     double minDistWarn2 = 0.00001;
1608 
1609     if (dist > maxDist2) {
1610       logger.log(
1611           Level.SEVERE,
1612           format(
1613               " Distance between atoms %s " + "and %s is %7.4f >> maximum allowed %7.4f",
1614               a1, a2, sqrt(dist), sqrt(maxDist2)));
1615     } else if (dist > minDistWarn2) {
1616       logger.log(
1617           warnlev,
1618           format(
1619               " Distance between atoms %s " + "and %s is %7.4f; moving atoms together.",
1620               a1, a2, sqrt(dist)));
1621       a1.setXYZ(xyzAv);
1622       a2.setXYZ(xyzAv);
1623     } else if (dist > 0) {
1624       // Silently move them together; probably just a rounding error.
1625       a1.setXYZ(xyzAv);
1626       a2.setXYZ(xyzAv);
1627     }
1628   }
1629 
1630   private void packGradient(double[] x, double[] g) {
1631     if (g == null) {
1632       g = new double[nVariables];
1633     }
1634     int indexCommon = 0;
1635     int indexUnique = nShared * 3;
1636 
1637     // Coordinate Gradient from Topology 1.
1638     int index = 0;
1639     for (int i = 0; i < nActive1; i++) {
1640       if (sharedAtoms1[i]) {
1641         g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1642         g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1643         g[indexCommon++] = f1L * g1[index] + f2L * rg1[index++];
1644       } else {
1645         g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1646         g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1647         g[indexUnique++] = f1L * g1[index] + f2L * rg1[index++];
1648       }
1649     }
1650 
1651     if (!useFirstSystemBondedEnergy) {
1652       // Coordinate Gradient from Topology 2.
1653       indexCommon = 0;
1654       index = 0;
1655       for (int i = 0; i < nActive2; i++) {
1656         if (sharedAtoms2[i]) {
1657           g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1658           g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1659           g[indexCommon++] += f2L * g2[index] + f1L * rg2[index++];
1660         } else {
1661           g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1662           g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1663           g[indexUnique++] = f2L * g2[index] + f1L * rg2[index++];
1664         }
1665       }
1666     } else {
1667       // Coordinate Gradient from Topology 2.
1668       indexCommon = 0;
1669       index = 0;
1670       for (int i = 0; i < nActive2; i++) {
1671         if (sharedAtoms2[i]) {
1672           g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1673           g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1674           g[indexCommon++] += f2L * g2[index] - f2L * rg2[index++];
1675         } else {
1676           g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1677           g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1678           g[indexUnique++] = f2L * g2[index] - f2L * rg2[index++];
1679         }
1680       }
1681     }
1682 
1683     scaleCoordinatesAndGradient(x, g);
1684   }
1685 
1686   private void unpackCoordinates(double[] x) {
1687 
1688     // Unscale the coordinates.
1689     unscaleCoordinates(x);
1690 
1691     int index = 0;
1692     int indexCommon = 0;
1693     int indexUnique = 3 * nShared;
1694     for (int i = 0; i < nActive1; i++) {
1695       if (sharedAtoms1[i]) {
1696         x1[index++] = x[indexCommon++];
1697         x1[index++] = x[indexCommon++];
1698         x1[index++] = x[indexCommon++];
1699       } else {
1700         x1[index++] = x[indexUnique++];
1701         x1[index++] = x[indexUnique++];
1702         x1[index++] = x[indexUnique++];
1703       }
1704     }
1705 
1706     index = 0;
1707     indexCommon = 0;
1708     for (int i = 0; i < nActive2; i++) {
1709       if (sharedAtoms2[i]) {
1710         x2[index++] = x[indexCommon++];
1711         x2[index++] = x[indexCommon++];
1712         x2[index++] = x[indexCommon++];
1713       } else {
1714         x2[index++] = x[indexUnique++];
1715         x2[index++] = x[indexUnique++];
1716         x2[index++] = x[indexUnique++];
1717       }
1718     }
1719   }
1720 
1721   /**
1722    * Reload the common atomic masses. Intended for quad-topology dual force field corrections, where
1723    * common atoms may have slightly different masses; this was found to be the case between AMOEBA
1724    * 2013 and AMBER99SB carbons.
1725    *
1726    * @param secondTopology Load from second topology
1727    */
1728   void reloadCommonMasses(boolean secondTopology) {
1729     int commonIndex = 0;
1730     if (secondTopology) {
1731       for (int i = 0; i < nActive2; i++) {
1732         Atom a = activeAtoms2[i];
1733         double m = a.getMass();
1734         if (sharedAtoms2[i]) {
1735           mass[commonIndex++] = m;
1736           mass[commonIndex++] = m;
1737           mass[commonIndex++] = m;
1738         }
1739       }
1740     } else {
1741       for (int i = 0; i < nActive1; i++) {
1742         Atom a = activeAtoms1[i];
1743         double m = a.getMass();
1744         if (sharedAtoms1[i]) {
1745           mass[commonIndex++] = m;
1746           mass[commonIndex++] = m;
1747           mass[commonIndex++] = m;
1748         }
1749       }
1750     }
1751   }
1752 
1753   /**
1754    * Bonded terms for alchemical atoms are used to prevent them from being unconstrained.
1755    * These restraints have the potential to interact with symmetry operators applied in dual-topology simulations.
1756    *
1757    * @param nActive     Number of active atoms (shared and alchemical) in simulation for this system.
1758    * @param sharedAtoms Mask to determine which atoms are shared vs alchemical.
1759    * @param activeAtoms Active atoms for this simulation.
1760    */
1761   private void validateAlchemicalAtoms(int nActive, boolean[] sharedAtoms, Atom[] activeAtoms) {
1762     int numSymOps = symOpAtoms.size();
1763     // Loop through atoms in simulation.
1764     for (int i = 0; i < nActive; i++) {
1765       // Determine if this atom is alchemical.
1766       if (!sharedAtoms[i]) {
1767         Atom alchAtom = activeAtoms[i];
1768         int symOpForAlchemicalAtom = -1;
1769         for (int j = 0; j < numSymOps; j++) {
1770           List<Integer> symOpAtomGroup = symOpAtoms.get(j);
1771           if (symOpAtomGroup.contains(i)) {
1772             symOpForAlchemicalAtom = j;
1773             break;
1774           }
1775         }
1776 
1777         StringBuilder sb = new StringBuilder();
1778         sb.append(format("\n Alchemical atom %s\n Symmetry group %s:\n", alchAtom, symOpForAlchemicalAtom + 1));
1779         List<Integer> symOpAtomGroup = symOpAtoms.get(symOpForAlchemicalAtom);
1780         for (Integer j : symOpAtomGroup) {
1781           if (j == i) {
1782             continue;
1783           }
1784           sb.append(format("  %d %s\n", j + 1, activeAtoms[j]));
1785         }
1786         sb.append(format("\n 1-2, 1-3 or 1-4 atoms in other symmetry groups:\n"));
1787 
1788         // Collect atoms that are 1-2, 1-3 or 1-4 to the alchemical atom.
1789         ArrayList<Integer> bondedAtoms = new ArrayList<>();
1790         addUniqueBondedIndices(bondedAtoms, alchAtom.get12List());
1791         addUniqueBondedIndices(bondedAtoms, alchAtom.get13List());
1792         addUniqueBondedIndices(bondedAtoms, alchAtom.get14List());
1793         // Loop through atoms bonded to alchemical atom to determine if they're part of
1794         // a different SymOp group.
1795         boolean conflict = false;
1796         for (int j = 0; j < numSymOps; j++) {
1797           // If the bonded atom is in the same symmetry group as the alchemical atom, then there is no conflict.
1798           if (j == symOpForAlchemicalAtom) {
1799             continue;
1800           }
1801           symOpAtomGroup = symOpAtoms.get(j);
1802           for (Integer integer : bondedAtoms) {
1803             if (symOpAtomGroup.contains(integer)) {
1804               conflict = true;
1805               sb.append(format("  %d %s uses symmetry group %2d.\n", integer + 1, activeAtoms[integer], j + 1));
1806             }
1807           }
1808         }
1809         if (conflict) {
1810           logger.info(sb.toString());
1811         }
1812       }
1813     }
1814   }
1815 
1816   /**
1817    * Take list of aggregated unique indices based on input list of bonded atoms..
1818    *
1819    * @param uniqueIndices List of unique indices of bonded atoms.
1820    * @param bondedAtoms   Atoms that are bonded to the atom of interest.
1821    */
1822   private void addUniqueBondedIndices(ArrayList<Integer> uniqueIndices, List<Atom> bondedAtoms) {
1823     // Loop through bonded atoms
1824     for (Atom a : bondedAtoms) {
1825       int index = a.getIndex() - 1;
1826       // Determine if bonded atom's index is already in unique list.
1827       if (!uniqueIndices.contains(index)) {
1828         // Add to list if it is not already included.
1829         uniqueIndices.add(index);
1830       }
1831     }
1832   }
1833 
1834   private class EnergyRegion extends ParallelRegion {
1835 
1836     private final Energy1Section e1sect;
1837     private final Energy2Section e2sect;
1838     private double[] x;
1839     private double[] g;
1840     private boolean gradient = false;
1841     private boolean verbose = false;
1842 
1843     EnergyRegion() {
1844       e1sect = new Energy1Section();
1845       e2sect = new Energy2Section();
1846     }
1847 
1848     @Override
1849     public void finish() {
1850       // Apply the dual-topology scaling for the total energy.
1851       if (!useFirstSystemBondedEnergy) {
1852         totalEnergy =
1853             f1L * energy1 + f2L * restraintEnergy1 + f2L * energy2 + f1L * restraintEnergy2;
1854       } else {
1855         totalEnergy =
1856             f1L * energy1 + f2L * restraintEnergy1 + f2L * energy2 - f2L * restraintEnergy2;
1857       }
1858 
1859       if (gradient) {
1860         packGradient(x, g);
1861       } else {
1862         scaleCoordinates(x);
1863       }
1864 
1865       if (verbose) {
1866         logger.info(format(" Total dual-topology energy: %12.4f", totalEnergy));
1867       }
1868       setVerbose(false);
1869       setGradient(false);
1870     }
1871 
1872     @Override
1873     public void run() throws Exception {
1874       execute(e1sect, e2sect);
1875     }
1876 
1877     public void setG(double[] g) {
1878       this.g = g;
1879       setGradient(true);
1880     }
1881 
1882     public void setGradient(boolean grad) {
1883       this.gradient = grad;
1884       e1sect.setGradient(grad);
1885       e2sect.setGradient(grad);
1886     }
1887 
1888     public void setVerbose(boolean verb) {
1889       this.verbose = verb;
1890       e1sect.setVerbose(verb);
1891       e2sect.setVerbose(verb);
1892     }
1893 
1894     public void setX(double[] x) {
1895       this.x = x;
1896     }
1897 
1898     @Override
1899     public void start() {
1900       unpackCoordinates(x);
1901     }
1902   }
1903 
1904   private class Energy1Section extends ParallelSection {
1905 
1906     private boolean gradient = false;
1907     private boolean verbose = false;
1908 
1909     @Override
1910     public void run() {
1911       if (gradient) {
1912         fill(gl1, 0.0);
1913         fill(rgl1, 0.0);
1914         energy1 = potential1.energyAndGradient(x1, g1, verbose);
1915         dEdL_1 = lambdaInterface1.getdEdL();
1916         d2EdL2_1 = lambdaInterface1.getd2EdL2();
1917         lambdaInterface1.getdEdXdL(gl1);
1918         if (doValenceRestraint1) {
1919           if (verbose) {
1920             logger.info(" Calculating lambda bonded terms for topology 1");
1921           }
1922           forceFieldEnergy1.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
1923           if (forceFieldEnergy1 instanceof OpenMMEnergy openMMEnergy) {
1924             // Use FFX to calculate the energy and gradient for the restraints.
1925             restraintEnergy1 = openMMEnergy.energyAndGradientFFX(x1, rg1, verbose);
1926           } else {
1927             restraintEnergy1 = forceFieldEnergy1.energyAndGradient(x1, rg1, verbose);
1928           }
1929           restraintdEdL_1 = forceFieldEnergy1.getdEdL();
1930           restraintd2EdL2_1 = forceFieldEnergy1.getd2EdL2();
1931           forceFieldEnergy1.getdEdXdL(rgl1);
1932           forceFieldEnergy1.setLambdaBondedTerms(false, false);
1933         } else {
1934           restraintEnergy1 = 0.0;
1935           restraintdEdL_1 = 0.0;
1936           restraintd2EdL2_1 = 0.0;
1937         }
1938         if (logger.isLoggable(Level.FINE)) {
1939           logger.fine(format(" Topology 1 Energy & Restraints: %15.8f %15.8f\n", f1L * energy1, f2L * restraintEnergy1));
1940           logger.fine(format(" Topology 1:    %15.8f * (%.2f)", energy1, f1L));
1941           logger.fine(format(" T1 Restraints: %15.8f * (%.2f)", restraintEnergy1, f2L));
1942         }
1943       } else {
1944         energy1 = potential1.energy(x1, verbose);
1945         if (doValenceRestraint1) {
1946           if (verbose) {
1947             logger.info(" Calculating lambda bonded terms for topology 1");
1948           }
1949           forceFieldEnergy1.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
1950           if (forceFieldEnergy1 instanceof OpenMMEnergy openMMEnergy) {
1951             // Use FFX to calculate the energy and gradient for the restraints.
1952             restraintEnergy1 = openMMEnergy.energyFFX(x1, verbose);
1953           } else {
1954             restraintEnergy1 = potential1.energy(x1, verbose);
1955           }
1956           forceFieldEnergy1.setLambdaBondedTerms(false, false);
1957         } else {
1958           restraintEnergy1 = 0.0;
1959         }
1960         if (logger.isLoggable(Level.FINE)) {
1961           logger.fine(format(" Topology 1 Energy & Restraints: %15.8f %15.8f\n", f1L * energy1, f2L * restraintEnergy1));
1962         }
1963       }
1964     }
1965 
1966     public void setGradient(boolean grad) {
1967       this.gradient = grad;
1968     }
1969 
1970     public void setVerbose(boolean verb) {
1971       this.verbose = verb;
1972     }
1973   }
1974 
1975   private class Energy2Section extends ParallelSection {
1976 
1977     private boolean gradient = false;
1978     private boolean verbose = false;
1979 
1980     @Override
1981     public void run() {
1982       // Number of symmetry operators to be applied to the system.
1983       int numSymOps = 0;
1984       if (useSymOp) {
1985         numSymOps = symOpAtoms.size();
1986         for (int i = 0; i < numSymOps; i++) {
1987           // Apply sym ops to system 1 coords (duplicate of x1) to get x2.
1988           applyCartesianSymOp(x2, x2, symOp[i], mask[i]);
1989         }
1990       }
1991 
1992       if (gradient) {
1993         fill(gl2, 0.0);
1994         fill(rgl2, 0.0);
1995 
1996         // Compute the energy and gradient of topology 2.
1997         energy2 = potential2.energyAndGradient(x2, g2, verbose);
1998         dEdL_2 = -lambdaInterface2.getdEdL();
1999         d2EdL2_2 = lambdaInterface2.getd2EdL2();
2000         lambdaInterface2.getdEdXdL(gl2);
2001         if (useSymOp) {
2002           // Rotate the gradient back for appropriate atoms.
2003           for (int i = 0; i < numSymOps; i++) {
2004             applyCartesianSymRot(g2, g2, inverse[i], mask[i]);
2005             applyCartesianSymRot(gl2, gl2, inverse[i], mask[i]);
2006           }
2007         }
2008         if (doValenceRestraint2) {
2009           if (verbose) {
2010             logger.info(" Calculating lambda bonded terms for topology 2");
2011           }
2012           forceFieldEnergy2.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
2013           if (forceFieldEnergy2 instanceof OpenMMEnergy openMMEnergy) {
2014             // Use FFX to calculate the energy and gradient for the restraints.
2015             restraintEnergy2 = openMMEnergy.energyAndGradientFFX(x2, rg2, verbose);
2016           } else {
2017             restraintEnergy2 = forceFieldEnergy2.energyAndGradient(x2, rg2, verbose);
2018           }
2019           restraintdEdL_2 = -forceFieldEnergy2.getdEdL();
2020           restraintd2EdL2_2 = forceFieldEnergy2.getd2EdL2();
2021           forceFieldEnergy2.getdEdXdL(rgl2);
2022           if (useSymOp) {
2023             // Rotate the gradient back for appropriate atoms.
2024             for (int i = 0; i < numSymOps; i++) {
2025               applyCartesianSymRot(rg2, rg2, inverse[i], mask[i]);
2026               applyCartesianSymRot(rgl2, rgl2, inverse[i], mask[i]);
2027             }
2028           }
2029           forceFieldEnergy2.setLambdaBondedTerms(false, false);
2030         } else {
2031           restraintEnergy2 = 0.0;
2032           restraintdEdL_2 = 0.0;
2033           restraintd2EdL2_2 = 0.0;
2034         }
2035         if (logger.isLoggable(Level.FINE)) {
2036           logger.fine(format(" Topology 2 Energy & Restraints: %15.8f %15.8f", f2L * energy2, f1L * restraintEnergy2));
2037           logger.fine(format(" Topology 2:    %15.8f * (%.2f)", energy2, f2L));
2038           logger.fine(format(" T2 Restraints: %15.8f * (%.2f)", restraintEnergy2, f1L));
2039         }
2040       } else {
2041         energy2 = potential2.energy(x2, verbose);
2042         if (doValenceRestraint2) {
2043           if (verbose) {
2044             logger.info(" Calculating lambda bonded terms for topology 2");
2045           }
2046           forceFieldEnergy2.setLambdaBondedTerms(true, useFirstSystemBondedEnergy);
2047           if (forceFieldEnergy2 instanceof OpenMMEnergy openMMEnergy) {
2048             // Use FFX to calculate the energy and gradient for the restraints.
2049             restraintEnergy2 = openMMEnergy.energyFFX(x2, verbose);
2050           } else {
2051             restraintEnergy2 = potential2.energy(x2, verbose);
2052           }
2053           forceFieldEnergy2.setLambdaBondedTerms(false, false);
2054         } else {
2055           restraintEnergy2 = 0.0;
2056         }
2057         if (logger.isLoggable(Level.FINE)) {
2058           logger.fine(format(" Topology 2 Energy & Restraints: %15.8f %15.8f", f2L * energy2, f1L * restraintEnergy2));
2059         }
2060       }
2061     }
2062 
2063     public void setGradient(boolean grad) {
2064       this.gradient = grad;
2065     }
2066 
2067     public void setVerbose(boolean verb) {
2068       this.verbose = verb;
2069     }
2070   }
2071 }