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-2024.
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.algorithms.cli;
39  
40  import static java.lang.String.format;
41  
42  import ffx.algorithms.AlgorithmListener;
43  import ffx.algorithms.dynamics.MolecularDynamics;
44  import ffx.crystal.CrystalPotential;
45  import ffx.potential.MolecularAssembly;
46  import ffx.potential.cli.WriteoutOptions;
47  
48  import java.io.File;
49  import java.util.Arrays;
50  import java.util.Collections;
51  import java.util.Set;
52  import java.util.TreeSet;
53  import java.util.logging.Logger;
54  
55  import picocli.CommandLine.ArgGroup;
56  import picocli.CommandLine.Option;
57  
58  /**
59   * Represents command line options for scripts that calculate thermodynamics.
60   *
61   * @author Michael J. Schnieders
62   * @author Jacob M. Litman
63   * @since 1.0
64   */
65  public class ThermodynamicsOptions {
66  
67    private static final Logger logger = Logger.getLogger(ThermodynamicsOptions.class.getName());
68  
69    /**
70     * The ArgGroup keeps the ThermodynamicsOptions together when printing help.
71     */
72    @ArgGroup(heading = "%n Thermodynamics Options%n", validate = false)
73    private final ThermodynamicsOptionGroup group = new ThermodynamicsOptionGroup();
74  
75    /**
76     * Return the selected Thermodynamics algorithm as an enumerated type.
77     *
78     * @return Corresponding thermodynamics algorithm
79     */
80    public ThermodynamicsAlgorithm getAlgorithm() {
81      return ThermodynamicsAlgorithm.parse(group.thermoAlgoString);
82    }
83  
84    /**
85     * getEquilSteps.
86     *
87     * @return The number of equilibration steps.
88     */
89    public long getEquilSteps() {
90      return group.equilibrationSteps;
91    }
92  
93    /**
94     * Getter for the field <code>resetNumSteps</code>.
95     *
96     * @return a boolean.
97     */
98    public boolean getResetNumSteps() {
99      return group.resetNumSteps;
100   }
101 
102   /**
103    * Run an alchemical free energy window.
104    *
105    * @param molecularAssemblies All involved MolecularAssemblies.
106    * @param crystalPotential    The Potential to be sampled.
107    * @param dynamicsOptions     DynamicsOptions.
108    * @param writeoutOptions     WriteoutOptions
109    * @param dyn                 MD restart file
110    * @param algorithmListener   AlgorithmListener
111    * @return The MolecularDynamics object constructed.
112    */
113   public MolecularDynamics runFixedAlchemy(MolecularAssembly[] molecularAssemblies,
114                                            CrystalPotential crystalPotential, DynamicsOptions dynamicsOptions,
115                                            WriteoutOptions writeoutOptions, File dyn, AlgorithmListener algorithmListener) {
116     dynamicsOptions.init();
117 
118     MolecularDynamics molDyn = dynamicsOptions.getDynamics(writeoutOptions, crystalPotential,
119         molecularAssemblies[0], algorithmListener);
120     for (int i = 1; i < molecularAssemblies.length; i++) {
121       molDyn.addAssembly(molecularAssemblies[i]);
122     }
123 
124     boolean initVelocities = true;
125     long nSteps = dynamicsOptions.getSteps();
126     molDyn.setRestartFrequency(dynamicsOptions.getCheckpoint());
127     // Start sampling.
128     if (group.equilibrationSteps > 0) {
129       logger.info("\n Beginning Equilibration");
130       runDynamics(molDyn, group.equilibrationSteps, dynamicsOptions, writeoutOptions, true, dyn);
131       logger.info(" Beginning Fixed-Lambda Alchemical Sampling");
132       initVelocities = false;
133     } else {
134       logger.info("\n Beginning Fixed-Lambda Alchemical Sampling Without Equilibration");
135       if (!group.resetNumSteps) {
136         // Workaround for being unable to pick up pre-existing steps.
137         initVelocities = true;
138       }
139     }
140 
141     if (nSteps > 0) {
142       runDynamics(molDyn, nSteps, dynamicsOptions, writeoutOptions, initVelocities, dyn);
143     } else {
144       logger.info(" No steps remaining for this process!");
145     }
146     return molDyn;
147   }
148 
149   /**
150    * The number of equilibration steps prior to production OST counts begin.
151    *
152    * @return Returns the number of equilibration steps.
153    */
154   public long getEquilibrationSteps() {
155     return group.equilibrationSteps;
156   }
157 
158   private void runDynamics(MolecularDynamics molecularDynamics, long nSteps,
159                            DynamicsOptions dynamicsOptions, WriteoutOptions writeoutOptions, boolean initVelocities,
160                            File dyn) {
161     molecularDynamics.dynamic(nSteps, dynamicsOptions.getDt(), dynamicsOptions.getReport(),
162         dynamicsOptions.getWrite(), dynamicsOptions.getTemperature(), initVelocities,
163         writeoutOptions.getFileType(), dynamicsOptions.getCheckpoint(), dyn);
164   }
165 
166   public void setEquilibrationSteps(long equilibrationSteps) {
167     group.equilibrationSteps = equilibrationSteps;
168   }
169 
170   /**
171    * Ignores steps detected in .lam lambda-restart files.
172    *
173    * @return Returns true if the number of steps is being reset.
174    */
175   public boolean isResetNumSteps() {
176     return group.resetNumSteps;
177   }
178 
179   public void setResetNumSteps(boolean resetNumSteps) {
180     group.resetNumSteps = resetNumSteps;
181   }
182 
183   /**
184    * The algorithm to be used (e.g. OST, window-based methods, etc.).
185    *
186    * @return Returns a String for requested algorithm.
187    */
188   public String getThermoAlgoString() {
189     return group.thermoAlgoString;
190   }
191 
192   public void setThermoAlgoString(String thermoAlgoString) {
193     group.thermoAlgoString = thermoAlgoString;
194   }
195 
196   /**
197    * Collection of Thermodynamics Options.
198    */
199   private static class ThermodynamicsOptionGroup {
200 
201     /**
202      * -Q or --equilibrate sets the number of equilibration steps prior to production OST counts
203      * begin.
204      */
205     @Option(names = {"-Q", "--equilibrate"}, paramLabel = "1000", defaultValue = "1000",
206         description = "Number of equilibration steps before evaluation of thermodynamics.")
207     private long equilibrationSteps = 1000;
208 
209     /**
210      * -rn or --resetNumSteps, ignores steps detected in .lam lambda-restart files and thus resets
211      * the histogram; use -rn false to continue from the end of any prior simulation.
212      */
213     @Option(names = {"--rn", "--resetNumSteps"}, defaultValue = "false",
214         description = "Ignore prior steps logged in .lam or similar files")
215     private boolean resetNumSteps = false;
216 
217     /**
218      * --tA or --thermodynamicsAlgorithm specifies the algorithm to be used; currently serves as a
219      * switch between OST and window-based methods.
220      */
221     @Option(names = {"--tA", "--thermodynamicsAlgorithm"}, paramLabel = "OST", defaultValue = "OST",
222         description = "Choice of thermodynamics algorithm. The default is OST, while FIXED runs MD at a fixed lambda value (e.g. BAR)")
223     private String thermoAlgoString = "OST";
224   }
225 
226   /**
227    * Represents categories of thermodynamics algorithms that must be handled differentially. For
228    * legacy reasons, MC-OST and MD-OST are both just "OST", and the differences are handled in
229    * OSTOptions and Thermodynamics.groovy. Introduced primarily to get BAR working.
230    */
231   public enum ThermodynamicsAlgorithm {
232     // TODO: Separate MC-OST from MD-OST. Requires coupled changes elsewhere.
233     // Fixed represents generation of snapshots for estimators like BAR, FEP, etc.
234     OST("OST", "MC-OST", "MD-OST", "DEFAULT"), FIXED("BAR", "MBAR", "FEP", "WINDOWED");
235 
236     private final Set<String> aliases;
237 
238     ThermodynamicsAlgorithm(String... aliases) {
239       // If, for some reason, there are 100+ aliases, might change to a HashSet.
240       Set<String> names = new TreeSet<>(Arrays.asList(aliases));
241       names.add(this.name());
242       this.aliases = Collections.unmodifiableSet(names);
243     }
244 
245     /**
246      * Parse a String to a corresponding thermodynamics algorithm, recognizing aliases.
247      *
248      * @param name Name to parse
249      * @return A ThermodynamicsAlgorithm.
250      * @throws IllegalArgumentException If name did not correspond to any alias of any
251      *                                  ThermodynamicsAlgorithm.
252      */
253     public static ThermodynamicsAlgorithm parse(String name) throws IllegalArgumentException {
254       String ucName = name.toUpperCase();
255       for (ThermodynamicsAlgorithm thermodynamicsAlgorithm : values()) {
256         if (thermodynamicsAlgorithm.aliases.contains(ucName)) {
257           return thermodynamicsAlgorithm;
258         }
259       }
260       throw new IllegalArgumentException(
261           format(" Could not parse %s as a ThermodynamicsAlgorithm", name));
262     }
263   }
264 }