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 }