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.commands.test;
39  
40  import ffx.numerics.Potential;
41  import ffx.potential.ForceFieldEnergy;
42  import ffx.potential.bonded.Atom;
43  import ffx.potential.bonded.Residue;
44  import ffx.potential.cli.AtomSelectionOptions;
45  import ffx.potential.cli.GradientOptions;
46  import ffx.potential.cli.PotentialCommand;
47  import ffx.potential.extended.ExtendedSystem;
48  import ffx.potential.terms.AnglePotentialEnergy;
49  import ffx.potential.terms.BondPotentialEnergy;
50  import ffx.potential.terms.ImproperTorsionPotentialEnergy;
51  import ffx.potential.terms.OutOfPlaneBendPotentialEnergy;
52  import ffx.potential.terms.PiOrbitalTorsionPotentialEnergy;
53  import ffx.potential.terms.StretchBendPotentialEnergy;
54  import ffx.potential.terms.TorsionPotentialEnergy;
55  import ffx.potential.terms.TorsionTorsionPotentialEnergy;
56  import ffx.potential.terms.UreyBradleyPotentialEnergy;
57  import ffx.utilities.FFXBinding;
58  import picocli.CommandLine.Command;
59  import picocli.CommandLine.Mixin;
60  import picocli.CommandLine.Option;
61  import picocli.CommandLine.Parameters;
62  
63  import java.util.ArrayList;
64  import java.util.Collections;
65  import java.util.HashMap;
66  import java.util.List;
67  import java.util.Map;
68  import java.util.stream.IntStream;
69  
70  import static ffx.utilities.StringUtils.parseAtomRanges;
71  import static java.lang.String.format;
72  import static org.apache.commons.math3.util.FastMath.abs;
73  import static org.apache.commons.math3.util.FastMath.pow;
74  import static org.apache.commons.math3.util.FastMath.sqrt;
75  
76  /**
77   * The Gradient script evaluates the consistency of the energy and gradient for CpHMD.
78   * <br>
79   * Usage:
80   * <br>
81   * ffxc test.PhGradient [options] &lt;filename&gt;
82   */
83  @Command(description = " Test the potential energy gradient for CpHMD.", name = "test.PhGradient")
84  public class PhGradient extends PotentialCommand {
85  
86    @Mixin
87    GradientOptions gradientOptions;
88  
89    @Mixin
90    AtomSelectionOptions atomSelectionOptions;
91  
92    /** --pH or --constantPH Constant pH value for the test. */
93    @Option(names = {"--pH", "--constantPH"}, paramLabel = "7.4",
94        description = "Constant pH value for the test.")
95    double pH = 7.4;
96  
97    /** --esvLambda ESV Lambda at which to test gradient. */
98    @Option(names = {"--esvLambda"}, paramLabel = "0.5",
99        description = "ESV Lambda at which to test gradient.")
100   double esvLambda = 0.5;
101 
102   /** --scanLambdas Scan titration and tautomer lambda landscape. */
103   @Option(names = {"--scanLambdas"}, paramLabel = "false",
104       description = "Scan titration and tautomer lambda landscape.")
105   boolean scan = false;
106 
107   /** --testEndStateEnergies */
108   @Option(names = {"--testEndStateEnergies"}, paramLabel = "false",
109       description = "Test both ESV energy end states as if the polarization damping factor is initialized from the respective protonated or deprotonated state")
110   boolean testEndstateEnergies = false;
111 
112   /** The final argument should be a PDB coordinate file. */
113   @Parameters(arity = "1", paramLabel = "file", description = "The atomic coordinate file in PDB format.")
114   String filename = null;
115 
116   private ForceFieldEnergy energy;
117   // For unit test of endstate energies.
118   public HashMap<String, double[]> endstateEnergyMap = new HashMap<>();
119   public int nFailures = 0;
120   public int nESVFailures = 0;
121   public double minEnergy = 0.0;
122   public String minLambdaList = "";
123 
124   /** Default constructor. */
125   public PhGradient() {
126     super();
127   }
128 
129   /**
130    * Constructor with Binding.
131    * @param binding Binding
132    */
133   public PhGradient(FFXBinding binding) {
134     super(binding);
135   }
136 
137   /**
138    * Constructor that sets command-line args.
139    * @param args Args
140    */
141   public PhGradient(String[] args) {
142     super(args);
143   }
144 
145   /** Execute the script. */
146   @Override
147   public PhGradient run() {
148 
149     if (!init()) {
150       return this;
151     }
152     activeAssembly = getActiveAssembly(filename);
153     if (activeAssembly == null) {
154       logger.info(helpString());
155       return this;
156     }
157 
158     // Set the filename.
159     filename = activeAssembly.getFile().getAbsolutePath();
160 
161     logger.info("\n Testing the atomic coordinate gradient of " + filename + "\n");
162 
163     // Select all possible titrating residues.
164     energy = activeAssembly.getPotentialEnergy();
165 
166     ExtendedSystem esvSystem = new ExtendedSystem(activeAssembly, pH, null);
167     esvSystem.setConstantPh(pH);
168     List<Residue> extendedResidues = esvSystem.getExtendedResidueList();
169     List<Residue> titratingResidues = esvSystem.getTitratingResidueList();
170     List<Residue> tautomerResidues = esvSystem.getTautomerizingResidueList();
171 
172     int numESVs = esvSystem.getExtendedResidueList().size();
173     energy.attachExtendedSystem(esvSystem);
174     logger.info(format(" Attached extended system with %d residues.", numESVs));
175 
176     // Set all ESV variables to esvLambda
177     for (Residue residue : extendedResidues) {
178       esvSystem.setTitrationLambda(residue, esvLambda);
179       esvSystem.setTautomerLambda(residue, esvLambda);
180     }
181 
182     Atom[] atoms = activeAssembly.getAtomArray();
183     int nAtoms = atoms.length;
184 
185     // Apply atom selections
186     atomSelectionOptions.setActiveAtoms(activeAssembly);
187 
188     // Finite-difference step size in Angstroms.
189     double step = gradientOptions.getDx();
190     logger.info(" Finite-difference step size:\t" + step);
191 
192     // Print out the energy for each step.
193     boolean print = gradientOptions.getVerbose();
194     logger.info(" Verbose printing:\t\t" + print);
195 
196     // Collect atoms to test.
197     List<Integer> atomsToTest;
198     if (gradientOptions.getGradientAtoms().equalsIgnoreCase("NONE")) {
199       logger.info(" The gradient of no atoms will be evaluated.");
200       return this;
201     } else if (gradientOptions.getGradientAtoms().equalsIgnoreCase("ALL")) {
202       logger.info(" Checking gradient for all active atoms.\n");
203       atomsToTest = new ArrayList<>();
204       IntStream.range(0, nAtoms).forEach(val -> atomsToTest.add(val));
205     } else {
206       atomsToTest = parseAtomRanges(" Gradient atoms", gradientOptions.getGradientAtoms(), nAtoms);
207       logger.info(
208           " Checking gradient for active atoms in the range: " + gradientOptions.getGradientAtoms() +
209               "\n");
210     }
211 
212     // Map selected atom numbers to active atom numbers
213     Map<Integer, Integer> allToActive = new HashMap<>();
214     int nActive = 0;
215     for (int i = 0; i < nAtoms; i++) {
216       Atom atom = atoms[i];
217       if (atom.isActive()) {
218         allToActive.put(i, nActive);
219         nActive++;
220       }
221     }
222 
223     // Collect analytic gradient.
224     int n = energy.getNumberOfVariables();
225     double[] x = new double[n];
226     double[] g = new double[n];
227     energy.getCoordinates(x);
228     energy.energyAndGradient(x, g);
229     int index = 0;
230     double[][] allAnalytic = new double[nAtoms][3];
231     for (Atom a : atoms) {
232       a.getXYZGradient(allAnalytic[index++]);
233     }
234 
235     // Upper bound for a typical atomic gradient.
236     double expGrad = 1000.0;
237     double gradientTolerance = gradientOptions.getTolerance();
238     double width = 2.0 * step;
239     double avLen = 0.0;
240     double avGrad = 0.0;
241     double expGrad2 = expGrad * expGrad;
242 
243     int nTested = 0;
244     for (int k : atomsToTest) {
245       Atom a0 = atoms[k];
246       if (!a0.isActive()) {
247         continue;
248       }
249 
250       nTested++;
251       double[] analytic = allAnalytic[k];
252 
253       // Coordinate array only includes active atoms.
254       int ia = allToActive.get(k);
255       int i3 = ia * 3;
256       int i0 = i3 + 0;
257       int i1 = i3 + 1;
258       int i2 = i3 + 2;
259       double[] numeric = new double[3];
260 
261       // Find numeric dX
262       double orig = x[i0];
263       x[i0] = x[i0] + step;
264       double e = energy.energy(x);
265       x[i0] = orig - step;
266       e -= energy.energy(x);
267       x[i0] = orig;
268       numeric[0] = e / width;
269 
270       // Find numeric dY
271       orig = x[i1];
272       x[i1] = x[i1] + step;
273       e = energy.energy(x);
274       x[i1] = orig - step;
275       e -= energy.energy(x);
276       x[i1] = orig;
277       numeric[1] = e / width;
278 
279       // Find numeric dZ
280       orig = x[i2];
281       x[i2] = x[i2] + step;
282       e = energy.energy(x);
283       x[i2] = orig - step;
284       e -= energy.energy(x);
285       x[i2] = orig;
286       numeric[2] = e / width;
287 
288       double dx = analytic[0] - numeric[0];
289       double dy = analytic[1] - numeric[1];
290       double dz = analytic[2] - numeric[2];
291       double len = dx * dx + dy * dy + dz * dz;
292       avLen += len;
293       len = sqrt(len);
294 
295       double grad2 = analytic[0] * analytic[0] + analytic[1] * analytic[1] + analytic[2] * analytic[2];
296       avGrad += grad2;
297 
298       if (len > gradientTolerance) {
299         logger.info(format(" %s\n Failed: %10.6f\n", a0, len) +
300             format(" Analytic: (%12.4f, %12.4f, %12.4f)\n", analytic[0], analytic[1], analytic[2]) +
301             format(" Numeric:  (%12.4f, %12.4f, %12.4f)\n", numeric[0], numeric[1], numeric[2]));
302         ++nFailures;
303       } else {
304         logger.info(format(" %s\n Passed: %10.6f\n", a0, len) +
305             format(" Analytic: (%12.4f, %12.4f, %12.4f)\n", analytic[0], analytic[1], analytic[2]) +
306             format(" Numeric:  (%12.4f, %12.4f, %12.4f)", numeric[0], numeric[1], numeric[2]));
307       }
308 
309       if (grad2 > expGrad2) {
310         logger.info(format(" Atom %d has an unusually large gradient: %10.6f", ia + 1, Math.sqrt(
311             grad2)));
312       }
313       logger.info("\n");
314     }
315 
316     avLen = avLen / nTested;
317     avLen = sqrt(avLen);
318     if (avLen > gradientTolerance) {
319       logger.info(format(" Test failure: RMSD from analytic solution is %10.6f > %10.6f", avLen,
320           gradientTolerance));
321     } else {
322       logger.info(format(" Test success: RMSD from analytic solution is %10.6f < %10.6f", avLen,
323           gradientTolerance));
324     }
325     logger.info(format(" Number of atoms failing analytic test: %d", nFailures));
326 
327     avGrad = avGrad / nTested;
328     avGrad = sqrt(avGrad);
329     if (avGrad > expGrad) {
330       logger.info(format(" Unusually large RMS gradient: %10.6f > %10.6f", avGrad, expGrad));
331     } else {
332       logger.info(format(" RMS gradient: %10.6f", avGrad));
333     }
334     energy.setCoordinates(x);
335     energy.getCoordinates(x);
336     energy.energyAndGradient(x, g);
337     double[] esvDerivs = esvSystem.getDerivatives();
338 
339     // Check the dU/dL_i analytic results vs. finite-differences for extended system variables.
340     // Loop over extended system variables
341     for (int i = 0; i < titratingResidues.size(); i++) {
342       double eMinusTitr = 0.0;
343       double ePlusTitr = 0.0;
344       double eMinusTaut = 0.0;
345       double ePlusTaut = 0.0;
346       Residue residue = titratingResidues.get(i);
347       int tautomerIndex = tautomerResidues.indexOf(residue) + titratingResidues.size();
348       // Calculate backward finite difference if very close to lambda=1
349       if (esvLambda + step > 1.0) {
350         logger.info("Backward finite difference being applied. Consider using a smaller step size than the default in this case.\n");
351         esvSystem.setTitrationLambda(residue, esvLambda - 2 * step);
352         eMinusTitr = energy.energy(x);
353         esvSystem.setTitrationLambda(residue, esvLambda);
354         ePlusTitr = energy.energy(x);
355 
356         if (esvSystem.isTautomer(residue)) {
357           esvSystem.setTautomerLambda(residue, esvLambda - 2 * step);
358           eMinusTaut = energy.energy(x);
359           esvSystem.setTautomerLambda(residue, esvLambda);
360           ePlusTaut = energy.energy(x);
361         }
362       }
363 
364       // Calculate forward finite difference if very close to lambda=0
365       else if (esvLambda - step < 0.0) {
366         logger.info("Forward finite difference being applied. Consider using a smaller step size than the default in this case.\n");
367         esvSystem.setTitrationLambda(residue, esvLambda + 2 * step);
368         ePlusTitr = energy.energy(x);
369         esvSystem.setTitrationLambda(residue, esvLambda);
370         eMinusTitr = energy.energy(x);
371 
372         if (esvSystem.isTautomer(residue)) {
373           esvSystem.setTautomerLambda(residue, esvLambda + 2 * step);
374           ePlusTaut = energy.energy(x);
375           esvSystem.setTautomerLambda(residue, esvLambda);
376           eMinusTaut = energy.energy(x);
377         }
378       }
379 
380       // Calculate central finite difference
381       else {
382         esvSystem.setTitrationLambda(residue, esvLambda + step);
383         ePlusTitr = energy.energy(x);
384         esvSystem.setTitrationLambda(residue, esvLambda - step);
385         eMinusTitr = energy.energy(x);
386         esvSystem.setTitrationLambda(residue, esvLambda);
387 
388         if (esvSystem.isTautomer(residue)) {
389           esvSystem.setTautomerLambda(residue, esvLambda + step);
390           ePlusTaut = energy.energy(x);
391           esvSystem.setTautomerLambda(residue, esvLambda - step);
392           eMinusTaut = energy.energy(x);
393           esvSystem.setTautomerLambda(residue, esvLambda);
394         }
395       }
396 
397       double fdDerivTitr = (ePlusTitr - eMinusTitr) / width;
398       double errorTitr = abs(fdDerivTitr - esvDerivs[i]);
399 
400       if (errorTitr > gradientTolerance) {
401         logger.info(format(" Residue: %s Chain: %s ESV %d\n Failed: %10.6f\n", residue.toString(), residue.getChainID(), i, errorTitr) +
402             format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[i], fdDerivTitr));
403         ++nESVFailures;
404       } else {
405         logger.info(format(" Residue: %s Chain: %s ESV %d\n Passed: %10.6f\n", residue.toString(), residue.getChainID(), i, errorTitr) +
406             format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[i], fdDerivTitr));
407       }
408 
409       if (esvSystem.isTautomer(residue)) {
410         double fdDerivTaut = (ePlusTaut - eMinusTaut) / width;
411         int ti = tautomerIndex;
412         double errorTaut = abs(fdDerivTaut - esvDerivs[ti]);
413         if (errorTaut > gradientTolerance) {
414           logger.info(format(" Residue: %s (Tautomer) Chain: %s ESV %d\n Failed: %10.6f\n", residue.toString(), residue.getChainID(), ti, errorTaut) +
415               format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[ti], fdDerivTaut));
416           ++nESVFailures;
417         } else {
418           logger.info(format(" Residue: %s (Tautomer) Chain: %s ESV %d\n Passed: %10.6f\n", residue.toString(), residue.getChainID(), ti, errorTaut) +
419               format(" Analytic: %12.4f vs. Numeric: %12.4f\n", esvDerivs[ti], fdDerivTaut));
420         }
421       }
422       if (nESVFailures > 0) {
423         logger.info(format(" %d ESVs failed the gradient test.\n", nESVFailures));
424       }
425     }
426     if (nESVFailures == 0) {
427       logger.info(" All ESVs passed the gradient test.\n");
428     }
429 
430     if (scan) {
431       for (Residue residue : esvSystem.getTitratingResidueList()) {
432         esvSystem.setTitrationLambda(residue, 0.0);
433         esvSystem.setTautomerLambda(residue, 0.0);
434       }
435       scanLambdas(esvSystem, energy, x);
436       printPermutations(esvSystem, titratingResidues.size(), energy, x);
437     }
438 
439     if (testEndstateEnergies) {
440       testEndState(x, esvSystem, 0.0, 0.0);
441       testEndState(x, esvSystem, 1.0, 0.0);
442     }
443 
444     return this;
445   }
446 
447   private void printPermutations(ExtendedSystem esvSystem, int numESVs, ForceFieldEnergy energy, double[] x) {
448     for (Residue residue : esvSystem.getTitratingResidueList()) {
449       esvSystem.setTitrationLambda(residue, 0.0);
450     }
451     energy.getCoordinates(x);
452     printPermutationsR(esvSystem, numESVs - 1, energy, x);
453     logger.info("Minimum Energy:" + minEnergy + " acheived with lambdas: " + minLambdaList);
454   }
455 
456   private void printPermutationsR(ExtendedSystem esvSystem, int esvID, ForceFieldEnergy energy, double[] x) {
457     for (int i = 0; i <= 1; i++) {
458       Residue residue = esvSystem.getTitratingResidueList().get(esvID);
459       esvSystem.setTitrationLambda(residue, i);
460       if (esvID != 0) {
461         printPermutationsR(esvSystem, esvID - 1, energy, x);
462       } else {
463         String lambdaList = esvSystem.getLambdaList();
464         logger.info(format("Lambda List: %s", lambdaList));
465         double stateEnergy = energy.energy(x, true);
466         if (stateEnergy < minEnergy) {
467           minEnergy = stateEnergy;
468           minLambdaList = lambdaList;
469         }
470         logger.info("\n");
471       }
472     }
473   }
474 
475   private void scanLambdas(ExtendedSystem esvSystem, ForceFieldEnergy energy, double[] x) {
476     int nTitrESVs = esvSystem.getTitratingResidueList().size();
477     double[][][] peLandscape = new double[nTitrESVs][11][11];
478     for (int i = 0; i < nTitrESVs; i++) {
479       Residue residue = esvSystem.getTitratingResidueList().get(i);
480       for (int j = 0; j < 11; j++) {
481         esvSystem.setTitrationLambda(residue, (double) j / 10.0);
482         for (int k = 0; k < 11; k++) {
483           esvSystem.setTautomerLambda(residue, (double) k / 10.0);
484           peLandscape[i][j][k] = energy.energy(x, false);
485         }
486       }
487       esvSystem.setTitrationLambda(residue, 0.0);
488       esvSystem.setTautomerLambda(residue, 0.0);
489     }
490     StringBuilder tautomerHeader = new StringBuilder("      X→ ");
491     for (int k = 0; k < 11; k++) {
492       double lb = (double) k / 10;
493       tautomerHeader.append(String.format("%1$12s", "[" + lb + "]"));
494     }
495     tautomerHeader.append("\nλ↓");
496     for (int i = 0; i < nTitrESVs; i++) {
497       logger.info(format("ESV: %d \n", i));
498       logger.info(tautomerHeader.toString());
499       for (int j = 0; j < 11; j++) {
500         double lb = (double) j / 10;
501         StringBuilder histogram = new StringBuilder();
502         for (int k = 0; k < 11; k++) {
503           StringBuilder hisvalue = new StringBuilder();
504           String value = String.format("%5.4f", peLandscape[i][j][k]);
505           hisvalue.append(String.format("%1$12s", value));
506           histogram.append(hisvalue);
507         }
508         logger.info("[" + lb + "]    " + histogram);
509       }
510       logger.info("\n");
511     }
512   }
513 
514   private void testEndState(double[] x, ExtendedSystem esvSystem, double titrLambda, double tautLambda) {
515     for (Residue residue : esvSystem.getTitratingResidueList()) {
516       esvSystem.setTitrationLambda(residue, titrLambda);
517       esvSystem.setTautomerLambda(residue, tautLambda);
518     }
519     // Manually sets the pdamp value to match that of the polarizability of the desired endstate.
520     // This behavior is different from the ExtendedSystem's treatment of averaging the pdamps and keeping them constant.
521     // Currently we cannot handle changing the pdamp with changing lambdas as the derivatives are not set up to handle that.
522     // So what is happening below should not happen in the course of a simulation.
523     for (Atom atom : esvSystem.getExtendedAtoms()) {
524       int atomIndex = atom.getArrayIndex();
525       if (esvSystem.isTitratingHeavy(atomIndex)) {
526         // If polarization is turned off atom.getPolarizeType() will return null
527         if (atom.getPolarizeType() != null) {
528           double endstatePolar = esvSystem.getTitrationUtils().getPolarizability(atom, titrLambda, tautLambda, atom.getPolarizeType().polarizability);
529           double sixth = 1.0 / 6.0;
530           atom.getPolarizeType().pdamp = pow(endstatePolar, sixth);
531         }
532       }
533     }
534 
535     // Add ForceFieldEnergy to hashmap for testing. Protonation endstates used as key in map.
536     String lambdaList = esvSystem.getLambdaList();
537     energy.setCoordinates(x);
538     energy.getCoordinates(x);
539     double stateEnergy = energy.energy(x, true);
540     double[] energyAndInteractionList = new double[26];
541     // Bond Energy
542     BondPotentialEnergy bondPotentialEnergy = energy.getBondPotentialEnergy();
543     if (bondPotentialEnergy != null) {
544       energyAndInteractionList[0] = bondPotentialEnergy.getEnergy();
545       energyAndInteractionList[1] = bondPotentialEnergy.getNumberOfBonds();
546     }
547     // Angle Energy
548     AnglePotentialEnergy anglePotentialEnergy = energy.getAnglePotentialEnergy();
549     if (anglePotentialEnergy != null) {
550       energyAndInteractionList[2] = anglePotentialEnergy.getEnergy();
551       energyAndInteractionList[3] = anglePotentialEnergy.getNumberOfAngles();
552     }
553     // Stretch-Bend Energy
554     StretchBendPotentialEnergy stretchBendPotentialEnergy = energy.getStretchBendPotentialEnergy();
555     if (stretchBendPotentialEnergy != null) {
556       energyAndInteractionList[4] = stretchBendPotentialEnergy.getEnergy();
557       energyAndInteractionList[5] = stretchBendPotentialEnergy.getNumberOfStretchBends();
558     }
559     // Urey-Bradley Energy
560     UreyBradleyPotentialEnergy ureyBradleyPotentialEnergy = energy.getUreyBradleyPotentialEnergy();
561     if (ureyBradleyPotentialEnergy != null) {
562       energyAndInteractionList[6] = ureyBradleyPotentialEnergy.getEnergy();
563       energyAndInteractionList[7] = ureyBradleyPotentialEnergy.getNumberOfUreyBradleys();
564     }
565     // Out-of-Plane Bend
566     OutOfPlaneBendPotentialEnergy ofPlaneBendPotentialEnergy = energy.getOutOfPlaneBendPotentialEnergy();
567     if (ofPlaneBendPotentialEnergy != null) {
568       energyAndInteractionList[8] = ofPlaneBendPotentialEnergy.getEnergy();
569       energyAndInteractionList[9] = ofPlaneBendPotentialEnergy.getNumberOfOutOfPlaneBends();
570     }
571     // Torsional Angle
572     TorsionPotentialEnergy torsionPotentialEnergy = energy.getTorsionPotentialEnergy();
573     if (torsionPotentialEnergy != null) {
574       energyAndInteractionList[10] = torsionPotentialEnergy.getEnergy();
575       energyAndInteractionList[11] = torsionPotentialEnergy.getNumberOfTorsions();
576     }
577     // Improper Torsional Angle
578     ImproperTorsionPotentialEnergy improperTorsionPotentialEnergy = energy.getImproperTorsionPotentialEnergy();
579     if (improperTorsionPotentialEnergy != null) {
580       energyAndInteractionList[12] = improperTorsionPotentialEnergy.getEnergy();
581       energyAndInteractionList[13] = improperTorsionPotentialEnergy.getNumberOfImproperTorsions();
582     }
583     // Pi-Orbital Torsion
584     PiOrbitalTorsionPotentialEnergy piOrbitalTorsionPotentialEnergy = energy.getPiOrbitalTorsionPotentialEnergy();
585     if (piOrbitalTorsionPotentialEnergy != null) {
586       energyAndInteractionList[14] = piOrbitalTorsionPotentialEnergy.getEnergy();
587       energyAndInteractionList[15] = piOrbitalTorsionPotentialEnergy.getNumberOfPiOrbitalTorsions();
588     }
589     // Torsion-Torsion
590     TorsionTorsionPotentialEnergy torsionTorsionPotentialEnergy = energy.getTorsionTorsionPotentialEnergy();
591     if (torsionTorsionPotentialEnergy != null) {
592       energyAndInteractionList[16] = torsionTorsionPotentialEnergy.getEnergy();
593       energyAndInteractionList[17] = torsionTorsionPotentialEnergy.getNumberOfTorsionTorsions();
594     }
595     // van Der Waals
596     energyAndInteractionList[18] = energy.getVanDerWaalsEnergy();
597     energyAndInteractionList[19] = energy.getVanDerWaalsInteractions();
598     // Permanent Multipoles
599     energyAndInteractionList[20] = energy.getPermanentMultipoleEnergy();
600     energyAndInteractionList[21] = energy.getPermanentInteractions();
601     // Polarization Energy
602     energyAndInteractionList[22] = energy.getPolarizationEnergy();
603     energyAndInteractionList[23] = energy.getPermanentInteractions();
604     // Extended System Bias
605     energyAndInteractionList[24] = energy.getEsvBiasEnergy();
606     // Total Energy
607     energyAndInteractionList[25] = energy.getTotalEnergy();
608     endstateEnergyMap.put(lambdaList, energyAndInteractionList);
609   }
610 
611   @Override
612   public List<Potential> getPotentials() {
613     List<Potential> potentials;
614     if (energy == null) {
615       potentials = Collections.emptyList();
616     } else {
617       potentials = Collections.singletonList(energy);
618     }
619     return potentials;
620   }
621 }