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.algorithms.commands.test;
39  
40  import ffx.algorithms.cli.AlgorithmsCommand;
41  import ffx.algorithms.optimize.ConformationScan;
42  import ffx.algorithms.optimize.Minimize;
43  import ffx.algorithms.optimize.TorsionSearch;
44  import ffx.potential.AssemblyState;
45  import ffx.potential.ForceFieldEnergy;
46  import ffx.potential.MolecularAssembly;
47  import ffx.potential.bonded.Atom;
48  import ffx.potential.bonded.Molecule;
49  import ffx.potential.utils.PotentialsUtils;
50  import ffx.utilities.FFXBinding;
51  import org.apache.commons.io.FilenameUtils;
52  import picocli.CommandLine.Command;
53  import picocli.CommandLine.Option;
54  import picocli.CommandLine.Parameters;
55  
56  import java.io.BufferedReader;
57  import java.io.BufferedWriter;
58  import java.io.File;
59  import java.io.FileReader;
60  import java.io.FileWriter;
61  import java.io.IOException;
62  import java.util.ArrayList;
63  import java.util.List;
64  import java.util.logging.Logger;
65  
66  /**
67   * CoformerBindingSearch is a Groovy script that generates a set of molecular orientations in vacuum and
68   * calculates the energy of each conformation.
69   * <br>
70   * Usage:
71   * <br>
72   * ffxc test.CoformerBindingSearch [options] &lt;filename&gt;
73   */
74  @Command(description = " Calculates interaction energies of different molecular orientations and saves low energy orientations.",
75          name = "test.CoformerBindingSearch")
76  public class CoformerBindingSearch extends AlgorithmsCommand {
77  
78    private static final Logger logger = Logger.getLogger(CoformerBindingSearch.class.getName());
79  
80    /**
81     * --eps
82     */
83    @Option(names = {"--eps"}, paramLabel = ".01",
84            description = "Gradient cutoff for minimization.")
85    private double eps = 0.01;
86  
87    /**
88     * --maxIter
89     */
90    @Option(names = {"--maxIter"}, paramLabel = "10000",
91            description = "Max iterations for minimization.")
92    private int maxIter = 10000;
93  
94    /**
95     * --solventDielectric
96     */
97    @Option(names = {"--solventDielectric"}, paramLabel = "78.4",
98            description = "Sets the gk solvent dielectric constant.")
99    private double gkSolventDielec = 78.4;
100 
101   /**
102    * --skipHomodimerNumber
103    */
104   @Option(names = {"--skipHomodimerNumber"}, paramLabel = "-1",
105           description = "Skip conformation search on input dimer one or two.")
106   private int skipHomodimerNumber = -1;
107 
108   /**
109    * --torsionScan
110    */
111   @Option(names = {"--torsionScan", "--tscan"}, paramLabel = "false", defaultValue = "false",
112           description = "During sampling, statically scan torsions after direct minimization to find the lowest energy conformation.")
113   private boolean tscan = false;
114 
115   /**
116    * --noMinimize
117    */
118   @Option(names = {"--noMinimize", "--noMin"}, paramLabel = "false", defaultValue = "false",
119           description = "Don't minimize or torsion scan after conformations are generated. Useful for testing.")
120   private boolean noMinimize = false;
121 
122   /**
123    * --excludeH
124    */
125   @Option(names = {"--excludeH", "--eh"}, paramLabel = "false", defaultValue = "false",
126           description = "Only include H bonded to electronegative atoms in conformations.")
127   private boolean excludeH = false;
128 
129   /**
130    * --coformerOnly
131    */
132   @Option(names = {"--coformerOnly"}, paramLabel = "false", defaultValue = "false",
133           description = "Only conformation search the coformer.")
134   private boolean coformerOnly = false;
135 
136   /**
137    * --gk
138    */
139   @Option(names = {"--gk"}, paramLabel = "false", defaultValue = "false",
140           description = "Use generalized kirkwood solvent.")
141   private boolean gk = false;
142 
143   /**
144    * Filename.
145    */
146   @Parameters(arity = "1..*", paramLabel = "files",
147           description = "XYZ input file.")
148   private List<String> filenames = null;
149 
150   private Boolean minimize = null;
151 
152 
153   /**
154    * Constructor.
155    */
156   public CoformerBindingSearch() {
157     super();
158   }
159 
160   /**
161    * Constructor.
162    * @param binding The Binding to use.
163    */
164   public CoformerBindingSearch(FFXBinding binding) {
165     super(binding);
166   }
167 
168   /**
169    * Constructor that sets the command line arguments.
170    * @param args Command line arguments.
171    */
172   public CoformerBindingSearch(String[] args) {
173     super(args);
174   }
175 
176   /**
177    * {@inheritDoc}
178    */
179   @Override
180   public CoformerBindingSearch run() {
181     System.setProperty("direct-scf-fallback", "true");
182 
183     // Init the context and bind variables.
184     if (!init()) {
185       return this;
186     }
187 
188     // Cat the key files together and set the -Dkey property to be the new file we created
189     // Write default gk options to the key file
190     // Only does a simple search for the patch file so it needs to be named accordingly with the .xyz
191     try {
192       setKeyAndPatchFiles(gk, gkSolventDielec, filenames);
193     } catch (IOException e) {
194       logger.severe("Error creating key/patch files: " + e.getMessage());
195       return this;
196     }
197 
198     // Check the size of the filenames list
199     if (!(filenames.size() == 1 || filenames.size() == 2)) {
200       logger.severe("Must provide one or two filenames.");
201       return this;
202     }
203 
204     minimize = !noMinimize;
205     boolean skipMoleculeOne = skipHomodimerNumber == 1;
206     boolean skipMoleculeTwo = skipHomodimerNumber == 2;
207 
208     // Perform scan on monomer one w/ itself
209     ConformationScan systemOneScan = null;
210     ConformationScan systemTwoScan = null;
211     if (!coformerOnly) {
212       if (!skipMoleculeOne) {
213         systemOneScan = runScan(filenames.get(0), filenames.get(0));
214       } else {
215         logger.info(" Skipping monomer one scan.");
216       }
217 
218       if (!skipMoleculeTwo && filenames.size() == 2) {
219         systemTwoScan = runScan(filenames.get(1), filenames.get(1));
220       } else if (!skipMoleculeTwo && filenames.size() == 1) {
221         logger.info(" Only one file provided, skipping second homodimer scan.");
222       }
223     } else {
224       logger.info(" Skipping monomer one and two scan.");
225     }
226 
227     if (filenames.size() == 2) {
228       ConformationScan bothSystems = runScan(filenames.get(0), filenames.get(1));
229       if (systemOneScan != null && systemTwoScan != null) {
230         logger.info("\n System one (" + FilenameUtils.removeExtension(filenames.get(0)) + ") self scan energy information:");
231         systemOneScan.logAllEnergyInformation();
232         logger.info("\n System two (" + FilenameUtils.removeExtension(filenames.get(1)) + ") self scan energy information:");
233         systemTwoScan.logAllEnergyInformation();
234         ConformationScan.logBindingEnergyCalculation(systemOneScan, systemTwoScan, bothSystems);
235       }
236     } else {
237       logger.info(" Only one file provided, skipping coformer scan.");
238     }
239 
240     return this;
241   }
242 
243   private ConformationScan runScan(String fileOne, String fileTwo) {
244     boolean coformer = !fileOne.equals(fileTwo);
245     PotentialsUtils potentialsUtils = new PotentialsUtils();
246     MolecularAssembly[] molecularAssemblies = potentialsUtils.openAll(new String[]{fileOne, fileTwo});
247     minimizeMolecularAssemblies(molecularAssemblies); // Only if noMinimize is false
248     int secondSystemStartIndex = molecularAssemblies[0].getMoleculeArray().length;
249     MolecularAssembly combined = combineTwoMolecularAssemblies(molecularAssemblies[0], molecularAssemblies[1]);
250     // Split combined mola into two lists based on molecules in files
251     ArrayList<Molecule> setOne = new ArrayList<>();
252     ArrayList<Molecule> setTwo = new ArrayList<>();
253     for (int i = 0; i < combined.getMoleculeArray().length; i++) {
254       if (i < secondSystemStartIndex) {
255         setOne.add(combined.getMoleculeArray()[i]);
256       } else {
257         setTwo.add(combined.getMoleculeArray()[i]);
258       }
259     }
260     Molecule[] systemOne = setOne.toArray(new Molecule[0]);
261     Molecule[] systemTwo = setTwo.toArray(new Molecule[0]);
262     ConformationScan scan = new ConformationScan(
263             combined,
264             systemOne,
265             systemTwo,
266             eps,
267             maxIter,
268             tscan,
269             excludeH,
270             minimize
271     );
272     scan.scan();
273     String fileName = coformer ? "coformerScan.arc" : FilenameUtils.removeExtension(fileOne) + ".arc";
274     File file = new File(fileName);
275     if (!scan.writeStructuresToXYZ(file)) {
276       logger.warning(" No structures saved from scan.");
277       scan = null;
278     } else if (!coformer) {
279       logger.info("\n System (" + FilenameUtils.removeExtension(fileOne) + ") self scan energy information:");
280       scan.logAllEnergyInformation();
281     } else {
282       logger.info("\n System (" + FilenameUtils.removeExtension(fileOne) +
283               ") and system (" + FilenameUtils.removeExtension(fileTwo) + ") scan energy information:");
284       scan.logAllEnergyInformation();
285     }
286     return scan;
287   }
288 
289   private static MolecularAssembly combineTwoMolecularAssemblies(MolecularAssembly mola1, MolecularAssembly mola2) {
290     MolecularAssembly mainMonomerAssembly = mola1;
291     MolecularAssembly feederAssembly = mola2;
292     Molecule[] assemblyTwoMolecules = feederAssembly.getMoleculeArray();
293     int molNum = mainMonomerAssembly.getMoleculeArray().length;
294     for (Molecule m : assemblyTwoMolecules) {
295       for (Atom a : m.getAtomList()) {
296         a.setMoleculeNumber(molNum);
297       }
298       m.setName("AddedMol" + molNum);
299       mainMonomerAssembly.addMSNode(m);
300       molNum++;
301     }
302     mainMonomerAssembly.update();
303     mainMonomerAssembly.setPotential(null); // energyFactory doesn't do anything if it isn't null
304     ForceFieldEnergy forceFieldEnergy = ForceFieldEnergy.energyFactory(mainMonomerAssembly);
305     return mainMonomerAssembly;
306   }
307 
308   private static void setKeyAndPatchFiles(boolean gk, double gkSolventDielec, List<String> filenames) throws IOException {
309     String key = "coformerScan.key";
310     String patch = "coformerScan.patch";
311     // Create the key file
312     File keyFile = new File(key);
313     File patchFile = new File(patch);
314     logger.info(" Creating key file: " + key);
315     keyFile.createNewFile();
316     // concatenate the two files together with bufferedReader and bufferedWriter
317     try (BufferedWriter bw = new BufferedWriter(new FileWriter(keyFile))) {
318       bw.write("patch " + FilenameUtils.removeExtension(key) + ".patch\n\n");
319       if (gk) {
320         bw.write("gkterm true\n");
321         bw.write("solvent-dielectric " + gkSolventDielec + "\n");
322         bw.write("gk-radius solute\n");
323         bw.write("cavmodel gauss-disp\n");
324       }
325     }
326     logger.info(" Creating patch file: " + patch);
327     patchFile.createNewFile();
328     String patchOne = FilenameUtils.removeExtension(filenames.get(0)) + ".patch";
329     String patchTwo = filenames.size() > 1
330             ? FilenameUtils.removeExtension(filenames.get(1)) + ".patch"
331             : patchOne;
332     String[] files = new String[]{patchOne, patchTwo};
333     try (BufferedWriter bw = new BufferedWriter(new FileWriter(patchFile))) {
334       for (String file : files) {
335         try (BufferedReader br = new BufferedReader(new FileReader(file))) {
336           String line = br.readLine();
337           while (line != null) {
338             bw.write(line + "\n");
339             line = br.readLine();
340           }
341         }
342       }
343     }
344     System.setProperty("key", key);
345   }
346 
347   private void minimizeMolecularAssemblies(MolecularAssembly[] molecularAssemblies) {
348     if (!noMinimize) {
349       logger.info(" Minimizing molecular assemblies.");
350       for (MolecularAssembly molecularAssembly : molecularAssemblies) {
351         for (Molecule m : molecularAssembly.getMoleculeArray()) {
352           if (tscan) {
353             TorsionSearch ts = new TorsionSearch(molecularAssembly, m, 32, 1);
354             ts.staticAnalysis(0, 100);
355             if (!ts.getStates().isEmpty()) {
356               AssemblyState minState = ts.getStates().get(0);
357               minState.revertState();
358             }
359           }
360         }
361         Minimize minimizer = new Minimize(molecularAssembly, molecularAssembly.getPotentialEnergy(), algorithmListener);
362         minimizer.minimize(eps, maxIter);
363       }
364       logger.info(" Done minimizing molecular assemblies.");
365     }
366   }
367 }