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.potential.cli;
39  
40  import static ffx.potential.cli.AtomSelectionOptions.actOnAtoms;
41  import static java.lang.String.format;
42  
43  import ffx.potential.MolecularAssembly;
44  import ffx.potential.bonded.Atom;
45  import ffx.potential.utils.PotentialsFunctions;
46  
47  import java.util.logging.Logger;
48  import java.util.regex.Pattern;
49  
50  import picocli.CommandLine.ArgGroup;
51  import picocli.CommandLine.Option;
52  
53  import javax.annotation.Nullable;
54  
55  /**
56   * Represents command line options for scripts that utilize alchemistry on at least one topology.
57   *
58   * @author Michael J. Schnieders
59   * @author Jacob M. Litman
60   * @since 1.0
61   */
62  public class AlchemicalOptions {
63  
64    /**
65     * The logger for this class.
66     */
67    public static final Logger logger = Logger.getLogger(AlchemicalOptions.class.getName());
68  
69    /**
70     * A regular expression used to parse ranges of atoms.
71     */
72    public static final Pattern rangeRegEx = Pattern.compile("([0-9]+)-?([0-9]+)?");
73  
74    /**
75     * The ArgGroup keeps the AlchemicalOptions together when printing help.
76     */
77    @ArgGroup(heading = "%n Alchemical Options%n", validate = false)
78    private final AlchemicalOptionGroup group = new AlchemicalOptionGroup();
79  
80    /**
81     * Sets the uncharged atoms for a MolecularAssembly.
82     *
83     * @param assembly       Assembly to which the atoms belong.
84     * @param unchargedAtoms Uncharged atoms selection string.
85     */
86    public static void setUnchargedAtoms(MolecularAssembly assembly, String unchargedAtoms) {
87      actOnAtoms(assembly, unchargedAtoms, (Atom a, Boolean b) -> a.setElectrostatics(!b), "Uncharged");
88    }
89  
90    /**
91     * Sets the alchemical atoms for a MolecularAssembly.
92     *
93     * @param assembly        Assembly to which the atoms belong.
94     * @param alchemicalAtoms Alchemical atoms selection string.
95     */
96    public static void setAlchemicalAtoms(MolecularAssembly assembly, String alchemicalAtoms) {
97      actOnAtoms(assembly, alchemicalAtoms, Atom::setApplyLambda, "Alchemical");
98    }
99  
100   /**
101    * --ac or --alchemicalAtoms Specify alchemical atoms [ALL, NONE, Range(s): 1-3,6-N]."
102    *
103    * @return Returns alchemical atoms.
104    */
105   public String getAlchemicalAtoms() {
106     return group.alchemicalAtoms;
107   }
108 
109   /**
110    * --uc or --unchargedAtoms Specify atoms without electrostatics [ALL, NONE, Range(s): 1-3,6-N].
111    *
112    * @return Returns atoms without electrostatics.
113    */
114   public String getUnchargedAtoms() {
115     return group.unchargedAtoms;
116   }
117 
118   /**
119    * -l or --lambda sets the initial lambda value.
120    *
121    * @return Returns the initial value of lambda.
122    */
123   public double getInitialLambda() {
124     return getInitialLambda(false);
125   }
126 
127   /**
128    * Gets the initial value of lambda.
129    *
130    * @param quiet No logging if quiet.
131    * @return Initial lambda.
132    */
133   public double getInitialLambda(boolean quiet) {
134     return getInitialLambda(1, 0, quiet);
135   }
136 
137   /**
138    * Gets the initial value of lambda.
139    *
140    * @param size The number of processes.
141    * @param rank THe rank of this process.
142    * @return Initial lambda.
143    */
144   public double getInitialLambda(int size, int rank) {
145     return getInitialLambda(size, rank, false);
146   }
147 
148   /**
149    * Gets the initial value of lambda.
150    *
151    * @param size  The number of processes.
152    * @param rank  The rank of this process.
153    * @param quiet No logging if quiet.
154    * @return Initial lambda.
155    */
156   public double getInitialLambda(int size, int rank, boolean quiet) {
157 
158     // Assume initial lambda is not set
159     // How to handle is we get nWindows and lambda
160     // nWindows is size and rank is the window you're looking at (0 - nWindows-1)
161 
162     double initialLambda = group.initialLambda;
163     if (initialLambda < 0.0 || initialLambda > 1.0) {
164       if (rank == 0 || size < 2) {
165         initialLambda = 0.0;
166       } else if (rank == size - 1) {
167         initialLambda = 1.0;
168       } else {
169         double dL = 1.0 / (size - 1);
170         initialLambda = dL * rank;
171       }
172 
173       if (!quiet) {
174         logger.info(format(" Setting lambda to %5.3f.", initialLambda));
175       }
176     }
177     return initialLambda;
178   }
179 
180   /**
181    * If any softcore Atoms have been detected.
182    *
183    * @return Presence of softcore Atoms.
184    */
185   public boolean hasSoftcore() {
186     String alchemicalAtoms = getAlchemicalAtoms();
187     return (alchemicalAtoms != null
188         && !alchemicalAtoms.equalsIgnoreCase("NONE")
189         && !alchemicalAtoms.equalsIgnoreCase(""));
190   }
191 
192   /**
193    * Set the alchemical atoms for this molecularAssembly.
194    *
195    * @param topology a {@link ffx.potential.MolecularAssembly} object.
196    */
197   public void setFirstSystemAlchemistry(MolecularAssembly topology) {
198     setAlchemicalAtoms(topology, getAlchemicalAtoms());
199   }
200 
201   /**
202    * Opens a File to a MolecularAssembly for alchemistry.
203    *
204    * @param potentialFunctions A utility object for opening Files into MolecularAssemblies.
205    * @param topologyOptions    TopologyOptions in case a dual-topology or greater is to be used.
206    * @param threadsPer         Number of threads to be used for this MolecularAssembly.
207    * @param toOpen             The name of the File to be opened.
208    * @param topNum             The index of this topology.
209    * @return The processed MolecularAssembly.
210    */
211   public MolecularAssembly openFile(PotentialsFunctions potentialFunctions,
212                                     TopologyOptions topologyOptions, int threadsPer,
213                                     String toOpen, int topNum) {
214     potentialFunctions.openAll(toOpen, threadsPer);
215     MolecularAssembly molecularAssembly = potentialFunctions.getActiveAssembly();
216     return processFile(topologyOptions, molecularAssembly, topNum);
217   }
218 
219   /**
220    * Performs processing on a MolecularAssembly for alchemistry.
221    *
222    * @param topologyOptions   TopologyOptions in case a dual-topology or greater is to be used.
223    * @param molecularAssembly The MolecularAssembly to be processed.
224    * @param topNum            The index of this topology, 0-indexed.
225    * @return The processed MolecularAssembly.
226    */
227   public MolecularAssembly processFile(@Nullable TopologyOptions topologyOptions,
228                                        MolecularAssembly molecularAssembly, int topNum) {
229 
230     int remainder = (topNum % 2) + 1;
231     switch (remainder) {
232       case 1 -> {
233         setFirstSystemAlchemistry(molecularAssembly);
234         setFirstSystemUnchargedAtoms(molecularAssembly);
235       }
236       case 2 -> {
237         if (topologyOptions == null) {
238           throw new IllegalArgumentException(
239               " For >= 2 systems, topologyOptions must not be empty!");
240         }
241         topologyOptions.setSecondSystemAlchemistry(molecularAssembly);
242         topologyOptions.setSecondSystemUnchargedAtoms(molecularAssembly);
243       }
244     }
245     return molecularAssembly;
246   }
247 
248   /**
249    * Set uncharged atoms for this molecularAssembly.
250    *
251    * @param topology a {@link ffx.potential.MolecularAssembly} object.
252    */
253   public void setFirstSystemUnchargedAtoms(MolecularAssembly topology) {
254     setUnchargedAtoms(topology, getUnchargedAtoms());
255   }
256 
257   /**
258    * Collection of Alchemical Options.
259    */
260   private static class AlchemicalOptionGroup {
261 
262     /**
263      * -l or --lambda sets the initial lambda value.
264      */
265     @Option(
266         names = {"-l", "--lambda"},
267         paramLabel = "-1",
268         description = "Initial lambda value.")
269     double initialLambda = -1.0;
270 
271     /**
272      * --ac or --alchemicalAtoms Specify alchemical atoms [ALL, NONE, Range(s): 1-3,6-N]."
273      */
274     @Option(
275         names = {"--ac", "--alchemicalAtoms"},
276         paramLabel = "<selection>",
277         defaultValue = "",
278         description = "Specify alchemical atoms [ALL, NONE, Range(s): 1-3,6-N].")
279     String alchemicalAtoms = "";
280 
281     /**
282      * --uc or --unchargedAtoms Specify atoms without electrostatics [ALL, NONE, Range(s):
283      * 1-3,6-N]."
284      */
285     @Option(
286         names = {"--uc", "--unchargedAtoms"},
287         paramLabel = "<selection>",
288         defaultValue = "",
289         description = "Specify atoms without electrostatics [ALL, NONE, Range(s): 1-3,6-N].")
290     String unchargedAtoms = "";
291   }
292 }