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;
39  
40  import ffx.crystal.Crystal;
41  import ffx.numerics.Potential;
42  import ffx.potential.AssemblyState;
43  import ffx.potential.ForceFieldEnergy;
44  import ffx.potential.MolecularAssembly;
45  import ffx.potential.bonded.Atom;
46  import ffx.potential.bonded.Residue;
47  import ffx.potential.cli.AtomSelectionOptions;
48  import ffx.potential.cli.PotentialCommand;
49  import ffx.potential.extended.ExtendedSystem;
50  import ffx.potential.parsers.PDBFilter;
51  import ffx.potential.parsers.SystemFilter;
52  import ffx.potential.parsers.XPHFilter;
53  import ffx.potential.parsers.XYZFilter;
54  import ffx.utilities.FFXBinding;
55  import org.apache.commons.io.FilenameUtils;
56  import picocli.CommandLine.Command;
57  import picocli.CommandLine.Mixin;
58  import picocli.CommandLine.Option;
59  import picocli.CommandLine.Parameters;
60  
61  import java.io.BufferedWriter;
62  import java.io.File;
63  import java.io.FileWriter;
64  import java.io.IOException;
65  import java.math.BigDecimal;
66  import java.util.ArrayList;
67  import java.util.Arrays;
68  import java.util.Collections;
69  import java.util.List;
70  import java.util.regex.Matcher;
71  import java.util.regex.Pattern;
72  
73  import static ffx.potential.utils.StructureMetrics.momentsOfInertia;
74  import static ffx.potential.utils.StructureMetrics.radiusOfGyration;
75  import static java.lang.String.format;
76  import static org.apache.commons.io.FilenameUtils.getBaseName;
77  import static org.apache.commons.io.FilenameUtils.removeExtension;
78  import static org.apache.commons.math3.util.FastMath.pow;
79  
80  /**
81   * The Energy script evaluates the energy of a system.
82   * <br>
83   * Usage:
84   * <br>
85   * ffxc PhEnergy &lt;filename&gt;
86   */
87  @Command(description = " Compute the force field potential energy for a CpHMD system.",
88      name = "PhEnergy")
89  public class PhEnergy extends PotentialCommand {
90  
91      @Mixin
92      private AtomSelectionOptions atomSelectionOptions = new AtomSelectionOptions();
93  
94      /**
95       * -m or --moments print out electrostatic moments.
96       */
97      @Option(names = {"-m", "--moments"}, paramLabel = "false", defaultValue = "false",
98              description = "Print out electrostatic moments.")
99      private boolean moments = false;
100 
101     /**
102      * --rg or --gyrate Print out the radius of gyration.
103      */
104     @Option(names = {"--rg", "--gyrate"}, paramLabel = "false", defaultValue = "false",
105             description = "Print out the radius of gyration.")
106     private boolean gyrate = false;
107 
108     /**
109      * --in or --inertia Print out the moments of inertia.
110      */
111     @Option(names = {"--in", "--inertia"}, paramLabel = "false", defaultValue = "false",
112             description = "Print out the moments of inertia.")
113     private boolean inertia = false;
114 
115     /**
116      * -g or --gradient to print out gradients.
117      */
118     @Option(names = {"-g", "--gradient"}, paramLabel = "false", defaultValue = "false",
119             description = "Compute the atomic gradient as well as energy.")
120     private boolean gradient = false;
121 
122     /**
123      * * --fl or --findLowest Return the n lowest energy structures from an ARC or PDB file.
124      */
125     @Option(names = {"--fl", "--findLowest"}, paramLabel = "0", defaultValue = "0",
126             description = "Return the n lowest energies from an ARC/PDB file.")
127     private int fl = 0;
128 
129     /**
130      * -v or --verbose enables printing out all energy components for multi-snapshot files (
131      * the first snapshot is always printed verbosely).
132      */
133     @Option(names = {"-v", "--verbose"}, paramLabel = "false", defaultValue = "false",
134             description = "Print out all energy components for each snapshot.")
135     private boolean verbose = false;
136 
137     /**
138      * --dc or --densityCutoff Collect structures above a specified density.
139      */
140     @Option(names = {"--dc", "--densityCutoff"}, paramLabel = "0.0", defaultValue = "0.0",
141             description = "Create ARC file of structures above a specified density.")
142     private double dCutoff = 0.0;
143 
144     /**
145      * --ec or --energyCutOff Collect structures below a specified energy range from the minimum energy.
146      */
147     @Option(names = {"--ec", "--energyCutoff"}, paramLabel = "0.0", defaultValue = "0.0",
148             description = "Create ARC file of structures within a specified energy of the lowest energy structure.")
149     private double eCutoff = 0.0;
150 
151     /**
152      * --pH or --constantPH Constant pH value for the test.
153      */
154     @Option(names = {"--pH", "--constantPH"}, paramLabel = "7.4",
155             description = "pH value for the energy evaluation. (Only applies when esvTerm is true)")
156     double pH = 7.4;
157 
158     @Option(names = {"--aFi", "--arcFile"}, paramLabel = "traj",
159             description = "A file containing snapshots to evaluate on when using a PDB as a reference to build from. There is currently no default.")
160     private String arcFileName = null;
161 
162     @Option(names = {"--bar", "--mbar"}, paramLabel = "false",
163             description = "Run (restartable) energy evaluations for MBAR. Requires an ARC file to be passed in. Set the tautomer flag to true for tautomer parameterization.")
164     boolean mbar = false;
165 
166     @Option(names = {"--numLambda", "--nL", "--nw"}, paramLabel = "-1",
167             description = "Required for lambda energy evaluations. Ensure numLambda is consistent with the trajectory lambdas, i.e. gaps between traj can be filled easily. nL >> nTraj is recommended.")
168     int numLambda = -1;
169 
170     @Option(names = {"--outputDir", "--oD"}, paramLabel = "",
171             description = "Where to place MBAR files. Default is ../mbarFiles/energy_(window#).mbar. Will write out a file called energy_0.mbar.")
172     String outputDirectory = "";
173 
174     @Option(names = {"--lambdaDerivative", "--lD"}, paramLabel = "false",
175             description = "Perform dU/dL evaluations and save to mbarFiles.")
176     boolean derivatives = false;
177 
178     @Option(names = {"--perturbTautomer"}, paramLabel = "false",
179             description = "Change tautomer instead of lambda state for MBAR energy evaluations.")
180     boolean tautomer = false;
181 
182     /**
183      * --testEndStateEnergies
184      */
185     @Option(names = {"--testEndStateEnergies"}, paramLabel = "false",
186             description = "Test both ESV energy end states as if the polarization damping factor is initialized from the respective protonated or deprotonated state")
187     boolean testEndstateEnergies = false;
188 
189     @Option(names = {"--recomputeAverage"}, paramLabel = "false",
190             description = "Recompute average position and spit out said structure from trajectory")
191     boolean recomputeAverage = false;
192 
193     /**
194      * The final argument is a PDB or XPH coordinate file.
195      */
196     @Parameters(arity = "1", paramLabel = "file",
197             description = "The atomic coordinate file in PDB or XPH format.")
198     private String filename = null;
199 
200     public double energy = 0.0;
201     public ForceFieldEnergy forceFieldEnergy = null;
202     public MolecularAssembly mola = null;
203 
204     /**
205      * Energy constructor.
206      */
207     public PhEnergy() {
208         super();
209     }
210 
211     /**
212      * Energy constructor.
213      * @param binding The Binding to use.
214      */
215     public PhEnergy(FFXBinding binding) {
216         super(binding);
217     }
218 
219     /**
220      * PhEnergy constructor that sets the command line arguments.
221      * @param args Command line arguments.
222      */
223     public PhEnergy(String[] args) {
224         super(args);
225     }
226 
227     /**
228      * Execute the script.
229      */
230     public PhEnergy run() {
231         // Init the context and bind variables.
232         if (!init()) {
233             return this;
234         }
235         if (mbar){ // This is probably set to true since parameterization requires locked lambda states
236             System.setProperty("lock.esv.states", "false");
237         }
238 
239         // Load the MolecularAssembly.
240         activeAssembly = getActiveAssembly(filename);
241         if (activeAssembly == null) {
242             logger.info(helpString());
243             return this;
244         }
245 
246         // Set the filename.
247         filename = activeAssembly.getFile().getAbsolutePath();
248 
249         logger.info("\n Running Energy on " + filename);
250         forceFieldEnergy = activeAssembly.getPotentialEnergy();
251 
252         // Restart File
253         File esv = new File(removeExtension(filename) + ".esv");
254         if (!esv.exists()) {
255             esv = null;
256         }
257 
258         ExtendedSystem esvSystem = new ExtendedSystem(activeAssembly, pH, esv);
259         if(testEndstateEnergies && BigDecimal.valueOf(esvSystem.getExtendedLambdas()[0]).compareTo(BigDecimal.ZERO) == 0){
260             for(Atom atom : esvSystem.getExtendedAtoms()){
261                 int atomIndex = atom.getArrayIndex();
262                 if (esvSystem.isTitratingHeavy(atomIndex)) {
263                     double endstatePolar = esvSystem.getTitrationUtils().getPolarizability(atom, 0.0, 0.0, atom.getPolarizeType().polarizability);
264                     double sixth = 1.0 / 6.0;
265                     atom.getPolarizeType().pdamp = pow(endstatePolar, sixth);
266                 }
267             }
268         } else if(testEndstateEnergies && BigDecimal.valueOf(esvSystem.getExtendedLambdas()[0]).compareTo(BigDecimal.ONE) == 0){
269             for(Atom atom : esvSystem.getExtendedAtoms()){
270                 int atomIndex = atom.getArrayIndex();
271                 if (esvSystem.isTitratingHeavy(atomIndex)) {
272                     double endstatePolar = esvSystem.getTitrationUtils().getPolarizability(atom, 1.0, 0.0, atom.getPolarizeType().polarizability);
273                     double sixth = 1.0 / 6.0;
274                     atom.getPolarizeType().pdamp = pow(endstatePolar, sixth);
275                 }
276             }
277         }
278         esvSystem.setConstantPh(pH);
279         int numESVs = esvSystem.getExtendedResidueList().size();
280         forceFieldEnergy.attachExtendedSystem(esvSystem);
281         logger.info(format(" Attached extended system with %d residues.", numESVs));
282 
283         // Apply atom selections
284         atomSelectionOptions.setActiveAtoms(activeAssembly);
285 
286         int nVars = forceFieldEnergy.getNumberOfVariables();
287         double[] x = new double[nVars];
288         forceFieldEnergy.getCoordinates(x);
289         double[] averageCoordinates = Arrays.copyOf(x, x.length);
290         if (gradient) {
291             double[] g = new double[nVars];
292             int nAts = nVars / 3;
293             energy = forceFieldEnergy.energyAndGradient(x, g, true);
294             logger.info(format("    Atom       X, Y and Z Gradient Components (kcal/mol/A)"));
295             for (int i = 0; i < nAts; i++) {
296                 int i3 = 3 * i;
297                 logger.info(format(" %7d %16.8f %16.8f %16.8f", i + 1, g[i3], g[i3 + 1], g[i3 + 2]));
298             }
299         } else {
300             energy = forceFieldEnergy.energy(x, true);
301         }
302 
303         if (moments) {
304             forceFieldEnergy.getPmeNode().computeMoments(activeAssembly.getActiveAtomArray(), false);
305         }
306 
307         if (gyrate) {
308             double rg = radiusOfGyration(activeAssembly.getActiveAtomArray());
309             logger.info(format(" Radius of gyration:           %10.5f A", rg));
310         }
311 
312         if (inertia) {
313             double[][] inertiaValue = momentsOfInertia(activeAssembly.getActiveAtomArray(), false, true, true);
314         }
315 
316         SystemFilter systemFilter;
317         if(arcFileName != null){
318             File arcFile = new File(arcFileName);
319             systemFilter = new XPHFilter(arcFile, activeAssembly, activeAssembly.getForceField(), activeAssembly.getProperties(), esvSystem);
320         } else{
321             systemFilter = potentialFunctions.getFilter();
322             if(systemFilter instanceof XYZFilter){
323                 systemFilter = new XPHFilter(activeAssembly.getFile(), activeAssembly, activeAssembly.getForceField(), activeAssembly.getProperties(), esvSystem);
324                 systemFilter.readFile();
325                 logger.info("Reading ESV lambdas from XPH file");
326                 forceFieldEnergy.getCoordinates(x);
327                 forceFieldEnergy.energy(x, true);
328             } 
329         }
330 
331         if (mbar){
332             computeESVEnergiesAndWriteFile(systemFilter, esvSystem);
333             return this;
334         }
335 
336         if (systemFilter instanceof XPHFilter || systemFilter instanceof PDBFilter) {
337             int index = 1;
338             while (systemFilter.readNext()) {
339                 index++;
340                 Crystal crystal = activeAssembly.getCrystal();
341                 forceFieldEnergy.setCrystal(crystal);
342                 forceFieldEnergy.getCoordinates(x);
343                 if(recomputeAverage){
344                     for(int i = 0; i < x.length; i++){
345                         averageCoordinates[i] += x[i];
346                     }
347                 }
348                 if (verbose) {
349                     logger.info(format(" Snapshot %4d", index));
350                     if (!crystal.aperiodic()) {
351                         logger.info(format("\n Density:                                %6.3f (g/cc)",
352                                 crystal.getDensity(activeAssembly.getMass())));
353                     }
354                     energy = forceFieldEnergy.energy(x, true);
355                 } else {
356                     energy = forceFieldEnergy.energy(x, false);
357                     logger.info(format(" Snapshot %4d: %16.8f (kcal/mol)", index, energy));
358                 }
359             }
360             if(recomputeAverage){
361                 for(int i = 0; i < x.length; i++){
362                     x[i] = averageCoordinates[i] / index;
363                 }
364                 forceFieldEnergy.setCoordinates(x);
365             }
366         }
367         if(recomputeAverage){
368             String modelFilename = activeAssembly.getFile().getAbsolutePath();
369             if (baseDir == null || !baseDir.exists() || !baseDir.isDirectory() || !baseDir.canWrite()) {
370                 baseDir = new File(FilenameUtils.getFullPath(modelFilename));
371             }
372             String dirName = baseDir.toString() + File.separator;
373             String fileName = FilenameUtils.getName(modelFilename);
374             String ext = FilenameUtils.getExtension(fileName);
375             fileName = FilenameUtils.removeExtension(fileName);
376             File saveFile;
377             SystemFilter writeFilter;
378             if (ext.toUpperCase().contains("XYZ")) {
379                 saveFile = new File(dirName + fileName + ".xyz");
380                 writeFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
381                         activeAssembly.getProperties());
382                 potentialFunctions.saveAsXYZ(activeAssembly, saveFile);
383             } else if (ext.toUpperCase().contains("ARC")) {
384                 saveFile = new File(dirName + fileName + ".arc");
385                 saveFile = potentialFunctions.versionFile(saveFile);
386                 writeFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
387                         activeAssembly.getProperties());
388                 potentialFunctions.saveAsXYZ(activeAssembly, saveFile);
389             } else {
390                 saveFile = new File(dirName + fileName + ".pdb");
391                 saveFile = potentialFunctions.versionFile(saveFile);
392                 writeFilter = new PDBFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
393                         activeAssembly.getProperties());
394                 int numModels = systemFilter.countNumModels();
395                 if (numModels > 1) {
396                     ((PDBFilter) writeFilter).setModelNumbering(0);
397                 }
398                 ((PDBFilter) writeFilter).writeFile(saveFile, true, false, true);
399             }
400         }
401 
402 
403         return this;
404     }
405 
406     @Override
407     public List<Potential> getPotentials() {
408         List<Potential> potentials;
409         if (forceFieldEnergy == null) {
410             potentials = Collections.emptyList();
411         } else {
412             potentials = Collections.singletonList((Potential) forceFieldEnergy);
413         }
414         return potentials;
415     }
416 
417     private class StateContainer implements Comparable<StateContainer> {
418 
419         private final AssemblyState state;
420         private final double e;
421 
422         public StateContainer(AssemblyState state, double e) {
423             this.state = state;
424             this.e = e;
425         }
426 
427         public AssemblyState getState() {
428             return state;
429         }
430 
431         public double getEnergy() {
432             return e;
433         }
434 
435         @Override
436         public int compareTo(StateContainer o) {
437             return Double.compare(e, o.getEnergy());
438         }
439     }
440 
441     void computeESVEnergiesAndWriteFile(SystemFilter systemFilter, ExtendedSystem esvSystem) {
442         // Find all run directories to determine lambda states & make necessary files
443         File mbarFile;
444         File mbarGradFile;
445         double[] lambdas;
446         if (outputDirectory.isEmpty()) {
447             File dir = new File(filename).getParentFile();
448             File parentDir = dir.getParentFile();
449             int thisRung = -1;
450             Pattern pattern = Pattern.compile("(\\d+)");
451             Matcher matcher = pattern.matcher(dir.getName());
452             if (matcher.find()) {
453                 thisRung = Integer.parseInt(matcher.group(1));
454             }
455             assert thisRung != -1: "Could not determine the rung number from the directory name.";
456             mbarFile = new File(parentDir.getAbsolutePath() + File.separator + "mbarFiles" + File.separator + "energy_"
457                     + thisRung + ".mbar");
458             mbarGradFile = new File(parentDir.getAbsolutePath() + File.separator + "mbarFiles" + File.separator + "derivative_"
459                     + thisRung + ".mbar");
460             mbarFile.getParentFile().mkdir();
461             File[] lsFiles = parentDir.listFiles();
462             List<File> rungFiles = new ArrayList<>();
463             for (File file : lsFiles) {
464                 if (file.isDirectory() && file.getName().matches("\\d+")) {
465                     rungFiles.add(file);
466                 }
467             }
468             if (numLambda == -1) {
469                 numLambda = rungFiles.size();
470             }
471             lambdas = new double[numLambda];
472             for (int i = 0; i < numLambda; i++) {
473                 double dL = 1.0 / (numLambda - 1);
474                 lambdas[i] = i * dL;
475             }
476 
477 
478             logger.info(" Computing energies for each lambda state for generation of mbar file.");
479             logger.info(" MBAR File: " + mbarFile);
480             logger.info(" Lambda States: " + lambdas);
481         } else {
482             mbarFile     = new File(outputDirectory + File.separator + "energy_0.mbar");
483             mbarGradFile = new File(outputDirectory + File.separator + "derivative_0.mbar");
484             lambdas = new double[numLambda];
485             if (numLambda == -1){
486                 logger.severe("numLambda must be set when outputDirectory is set.");
487             }
488             for (int i = 0; i < numLambda; i++) {
489                 double dL = 1.0 / (numLambda - 1);
490                 lambdas[i] = i * dL;
491             }
492         }
493 
494         int progress = 1;
495         if (mbarFile.exists()){ // Restartable
496             // Count lines in mbarFile
497             try {
498                 progress = (int) java.nio.file.Files.lines(mbarFile.toPath()).count() - 1; // Subtract header
499             } catch (IOException e) {
500                 logger.severe("Error reading MBAR file for restart.");
501                 progress = 1;
502             }
503             for(int i = 0; i < progress; i++){
504                 systemFilter.readNext();
505             }
506             progress += 1; // Increment to next snapshot
507             logger.info("\n Restarting MBAR file at snapshot " + progress);
508         }
509 
510         if (systemFilter instanceof XPHFilter || systemFilter instanceof PDBFilter) {
511             int index = progress;
512             double[] x = new double[forceFieldEnergy.getNumberOfVariables()];
513             try(FileWriter fw = new FileWriter(mbarFile, mbarFile.exists());
514                 BufferedWriter writer = new BufferedWriter(fw);
515                 FileWriter fwGrad = new FileWriter(mbarGradFile, mbarGradFile.exists());
516                 BufferedWriter writerGrad = new BufferedWriter(fwGrad)
517             ){
518                 // Write header (Number of snaps, temp, and this.xyz)
519                 StringBuilder sb = new StringBuilder(systemFilter.countNumModels() + "\t" + "298.0" + "\t" + getBaseName(filename));
520                 StringBuilder sbGrad = new StringBuilder(systemFilter.countNumModels() + "\t" + "298.0" + "\t" + getBaseName(filename));
521                 logger.info(" MBAR file temp is hardcoded to 298.0 K. Please change if necessary.");
522                 sb.append("\n");
523                 sbGrad.append("\n");
524                 if (progress == 1) { // File didn't exist (more consistent than checking for existence)
525                     writer.write(sb.toString());
526                     writer.flush();
527                     logger.info(" Header: " + sb.toString());
528                     if(derivatives){
529                        writerGrad.write(sbGrad.toString());
530                        writerGrad.flush();
531                        logger.info(" Header: " + sbGrad.toString());
532                     }
533                 }
534                 while (systemFilter.readNext()) {
535                     // MBAR lines (\t index\t lambda0 lambda1 ... lambdaN)
536                     sb = new StringBuilder("\t" + index + "\t");
537                     sbGrad = new StringBuilder("\t" + index + "\t");
538                     index++;
539                     Crystal crystal = activeAssembly.getCrystal();
540                     forceFieldEnergy.setCrystal(crystal);
541                     for(double lambda : lambdas) {
542                         if (tautomer){
543                             setESVTautomer(lambda, esvSystem);
544                         } else {
545                             setESVLambda(lambda, esvSystem);
546                         }
547                         forceFieldEnergy.getCoordinates(x);
548                         if (derivatives) {
549                             energy = forceFieldEnergy.energyAndGradient(x, new double[x.length * 3]);
550                             double grad = esvSystem.getDerivatives()[0]; // Only one residue
551                             sbGrad.append(grad).append(" ");
552                         } else {
553                             energy = forceFieldEnergy.energy(x, false);
554                         }
555                         sb.append(energy).append(" ");
556                     }
557                     sb.append("\n");
558                     writer.write(sb.toString());
559                     writer.flush(); // Flush after each snapshot, otherwise it doesn't do it consistently
560                     logger.info(sb.toString());
561                     if (derivatives) {
562                         sbGrad.append("\n");
563                         writerGrad.write(sbGrad.toString());
564                         writerGrad.flush();
565                         logger.info(sbGrad.toString());
566                     }
567                 }
568             } catch (IOException e) {
569                 logger.severe("Error writing to MBAR file.");
570             }
571         }
572     }
573 
574     /**
575      * Sets lambda values for the extended system. Note that it is expected that the
576      * tautomer is set correctly from dynamics.
577      * @param lambda
578      * @param extendedSystem
579      */
580     public static void setESVLambda(double lambda, ExtendedSystem extendedSystem) {
581         List<Residue> residueList = extendedSystem.getExtendedResidueList();
582         if (residueList.size() == 1 || (residueList.size() == 2 && extendedSystem.isTautomer(residueList.get(0)))){
583             extendedSystem.setTitrationLambda(residueList.get(0), lambda, false);
584         } else {
585             if (residueList.size() == 0) {
586                 logger.severe("No residues found in the extended system.");
587             } else {
588                 logger.severe("Only one lambda path is allowed for MBAR energy evaluations.");
589             }
590         }
591     }
592 
593     /**
594      * Sets tautomer values for the extended system. Note that it is expected that the
595      * lambda is set correctly from dynamics.
596      * @param tautomer
597      * @param extendedSystem
598      */
599     public static void setESVTautomer(double tautomer, ExtendedSystem extendedSystem) {
600         List<Residue> residueList = extendedSystem.getExtendedResidueList();
601         if (residueList.size() == 1 || (residueList.size() == 2 && extendedSystem.isTautomer(residueList.get(0)))) {
602             extendedSystem.setTautomerLambda(residueList.get(0), tautomer, false);
603         } else {
604             if (residueList.size() == 0) {
605                 logger.severe("No residues found in the extended system.");
606             } else {
607                 logger.severe("Only one lambda path is allowed for MBAR energy evaluations.");
608             }
609         }
610     }
611 }