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.cli.MinimizeOptions;
42  import ffx.algorithms.optimize.Minimize;
43  import ffx.algorithms.optimize.Minimize.MinimizationEngine;
44  import ffx.potential.ForceFieldEnergy;
45  import ffx.potential.MolecularAssembly;
46  import ffx.potential.bonded.Atom;
47  import ffx.potential.bonded.Polymer;
48  import ffx.potential.bonded.Residue;
49  import ffx.potential.bonded.ResidueState;
50  import ffx.potential.bonded.Rotamer;
51  import ffx.potential.bonded.RotamerLibrary;
52  import ffx.potential.bonded.RotamerLibrary.ProteinLibrary;
53  import ffx.utilities.FFXBinding;
54  import org.apache.commons.io.FilenameUtils;
55  import picocli.CommandLine.Command;
56  import picocli.CommandLine.Mixin;
57  import picocli.CommandLine.Option;
58  import picocli.CommandLine.Parameters;
59  
60  import java.io.BufferedWriter;
61  import java.io.File;
62  import java.io.FileWriter;
63  import java.io.IOException;
64  import java.util.ArrayList;
65  import java.util.List;
66  import java.util.stream.Collectors;
67  
68  /**
69   * The CreateRotamers script creates a set of conformation dependent rotamers.
70   * <br>
71   * Usage:
72   * <br>
73   * ffxc test.CreateRotamers [options] &lt;filename&gt;
74   */
75  @Command(description = " Creates a set of conformation dependent rotamers.", name = "test.CreateRotamers")
76  public class CreateRotamers extends AlgorithmsCommand {
77  
78    @Mixin
79    private MinimizeOptions minimizeOptions;
80  
81    // TODO: instead @Mixin a subset of current ManyBodyOptions.
82  
83    /**
84     * -L or --library Choose either Ponder and Richards (1) or Richardson (2)
85     * rotamer library.
86     */
87    @Option(names = {"-L", "--library"}, paramLabel = "2",
88        description = "Ponder and Richards (1) or Richardson (2) rotamer library.")
89    private String library = "2";
90  
91    /**
92     * -d or --rmsd Set the RMSD cut off for rotamer exclusion
93     * Any rotamer with an RMSD from any previous rotamer that is
94     * less than or equal to the cut off will be thrown out.
95     */
96    @Option(names = {"-d", "--rmsd"}, paramLabel = "0.1",
97        description = "RMSD cut off for rotamer exclusion: only rotamers with an RMSD greater than this cut off value to all previously saved rotamers will be kept")
98    private double rmsdCutoff = 0.1;
99  
100   /**
101    * The final argument should be a filename.
102    */
103   @Parameters(arity = "1..*", paramLabel = "files", description = "Atomic coordinate file in PDB or XYZ format.")
104   private List<String> filenames = null;
105 
106   public ForceFieldEnergy forceFieldEnergy = null;
107 
108   /**
109    * CreateRotamers Constructor.
110    */
111   public CreateRotamers() {
112     super();
113   }
114 
115   /**
116    * CreateRotamers Constructor.
117    * @param binding The Binding to use.
118    */
119   public CreateRotamers(FFXBinding binding) {
120     super(binding);
121   }
122 
123   /**
124    * CreateRotamers constructor that sets the command line arguments.
125    * @param args Command line arguments.
126    */
127   public CreateRotamers(String[] args) {
128     super(args);
129   }
130 
131   /**
132    * {@inheritDoc}
133    */
134   @Override
135   public CreateRotamers run() {
136 
137     if (!init()) {
138       return this;
139     }
140 
141     if (filenames != null && !filenames.isEmpty()) {
142       MolecularAssembly[] assemblies = new MolecularAssembly[]{algorithmFunctions.open(filenames.get(0))};
143       activeAssembly = assemblies[0];
144     } else if (activeAssembly == null) {
145       logger.info(helpString());
146       return this;
147     }
148 
149     String filename = activeAssembly.getFile().getAbsolutePath();
150     logger.info(" Running CreateRotamers on " + filename);
151 
152     Atom[] atoms = activeAssembly.getAtomArray();
153     int nAtoms = atoms.length;
154 
155     // Set all atoms to be "inactive".
156     for (int i = 0; i < nAtoms; i++) {
157       atoms[i].setActive(false);
158     }
159 
160     // For now, always use the original coordinates as a (fixed) rotamer.
161     boolean useOriginalRotamers = true;
162 
163     // AA Library
164     RotamerLibrary rotamerLibrary = new RotamerLibrary(ProteinLibrary.getProteinLibrary(library),
165         useOriginalRotamers);
166 
167     // Initialize Default NA Coordinates
168     Polymer[] polymers = activeAssembly.getChains();
169     RotamerLibrary.initializeDefaultAtomicCoordinates(polymers);
170 
171     // Get the residue list.
172     List<Residue> residues = activeAssembly.getResidueList().stream()
173         .filter(r -> {
174           Rotamer[] rots = r.setRotamers(rotamerLibrary);
175           return rots != null && rots.length > 1;
176         })
177         .collect(Collectors.toList());
178 
179     logger.info(String.format(" Number of residues: %d\n", residues.size()));
180 
181     // Loop over Residues and set side chain atoms to not be used.
182     for (Residue residue : residues) {
183       for (Atom atom : residue.getVariableAtoms()) {
184         atom.setUse(false);
185       }
186     }
187 
188     // Create .rot file name: should match input file name and end in ".rot"
189     String rotFileName = String.format("%s.rot", FilenameUtils.removeExtension(filename));
190 
191     try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File(rotFileName)))) {
192 
193       // TODO: Make this ALGORITHM:[ALGORITHM]:[box/window number] instead of assuming global:1.
194       bw.write("ALGORITHM:GLOBAL:1");
195       bw.newLine();
196 
197       // Loop over Residues
198       for (Residue residue : residues) {
199 
200         StringBuilder resLine = new StringBuilder(" RES:");
201         resLine.append(residue.getChainID()).append(":");
202         resLine.append(residue.getSegID()).append(":");
203         resLine.append(residue.getName()).append(":");
204         resLine.append(residue.getResidueNumber()).append("\n");
205         bw.write(resLine.toString());
206 
207         // Get this residue's rotamers.
208         Rotamer[] rotamers = residue.getRotamers();
209 
210         assert rotamers != null && rotamers.length > 1;
211 
212         // Configure "active" and "use" flags.
213         // .getVariableAtoms returns backbone atoms for
214         // nucleic acids, which is what we want
215         List<Atom> sideChainAtoms = residue.getVariableAtoms();
216         for (Atom atom : sideChainAtoms) {
217           atom.setActive(true);
218           atom.setUse(true);
219         }
220 
221         // Define "all previously saved rotamers" arrayList to be used for RMSD comparison
222         ArrayList<ResidueState> keptRotamers = new ArrayList<>();
223         int keptRotamersCount = 0;
224 
225         // Loop over rotamers for this Residue.
226         for (int i = 0; i < rotamers.length; i++) {
227           Rotamer rotamer = rotamers[i];
228 
229           // Apply the rotamer (i.e. amino acid side-chain or nucleic acid suite).
230           RotamerLibrary.applyRotamer(residue, rotamer);
231 
232           if (i > 0 || !useOriginalRotamers) {
233             // -Dplatform=omm
234             MinimizationEngine engine =
235                 Minimize.defaultEngine(activeAssembly, activeAssembly.getPotentialEnergy());
236             Minimize minimize = Minimize.minimizeFactory(activeAssembly,
237                 activeAssembly.getPotentialEnergy(), algorithmListener, engine);
238             // Locally minimize.
239             minimize.minimize(minimizeOptions.getNBFGS(), minimizeOptions.getEps(), minimizeOptions.getIterations());
240           } else {
241             logger.info(" Skipping minimization of original-coordinates rotamer.");
242           }
243 
244           // Stores a copy of minimized residue coordinates
245           // newResidueState will be added to keptRotamers if its RMSD
246           // to all previously kept rotamers is above cut off (user defined or
247           // default of 0.1 kcal/mol)
248           ResidueState newResState = new ResidueState(residue);
249 
250           if (i == 0) {
251             // Add 0th rotamer to the keptRotamers ArrayList
252             keptRotamers.add(newResState);
253 
254             // Save out coordinates to a rotamer file (inputFileName.rot)
255             bw.write(String.format("  ROT:%d\n", keptRotamersCount));
256             for (Atom atom : sideChainAtoms) {
257               double x = atom.getX();
258               double y = atom.getY();
259               double z = atom.getZ();
260               logger.info(String.format(" %s %16.8f %16.8f %16.8f", atom.toString(), x, y, z));
261               StringBuilder atomLine = new StringBuilder("   ATOM:");
262               atomLine.append(atom.getName()).append(":");
263               atomLine.append(x).append(":");
264               atomLine.append(y).append(":");
265               atomLine.append(z).append("\n");
266               bw.write(atomLine.toString());
267             }
268             bw.write("  ENDROT\n");
269             keptRotamersCount++;
270           } else {
271             // For all but the 0th rotamer, do RMSD calculations to determine if the "new" rotamer
272             // is within a cut off (default: 0.1 kcal/mol) of any other previously saved rotamer.
273             logger.info("Number of rotamers kept for this residue: " + keptRotamers.size());
274 
275             // Define RMSD threshold value boolean
276             boolean withinRange = false;
277 
278             // Compare newResState (i.e.: residue with newly applied rotamer) to all
279             // previously saved rotamers in keptRotamers
280             for (int k = 0; k < keptRotamers.size(); k++) {
281               double RMSD = newResState.compareTo(keptRotamers.get(k));
282               logger.info("RMSD: " + RMSD + "\n");
283               if (RMSD <= rmsdCutoff) {
284                 withinRange = true;
285               }
286             }
287 
288             // Keep new rotamer if and only if it is different from all previously
289             // kept rotamers by an RMSD of greater than the user-defined cut off
290             // (or default cut off of 0.1 kcal/mol)
291             if (withinRange) {
292               // Rotamer not written because it's too energetically similar to another rotamer in the set
293               // Keeping too many similar rotamers causes problems with Dead End Elimination
294               logger.info("Rotamer not kept");
295             } else {
296               // Save out coordinates to a rotamer file.
297               bw.write(String.format("  ROT:%d\n", keptRotamersCount));
298               for (Atom atom : sideChainAtoms) {
299                 double x = atom.getX();
300                 double y = atom.getY();
301                 double z = atom.getZ();
302                 logger.info(String.format(" %s %16.8f %16.8f %16.8f", atom.toString(), x, y, z));
303                 StringBuilder atomLine = new StringBuilder("   ATOM:");
304                 atomLine.append(atom.getName()).append(":");
305                 atomLine.append(x).append(":");
306                 atomLine.append(y).append(":");
307                 atomLine.append(z).append("\n");
308                 bw.write(atomLine.toString());
309               }
310               bw.write("  ENDROT\n");
311 
312               // Add the new rotamer to keptRotamers list
313               keptRotamers.add(newResState);
314               keptRotamersCount++;
315             }
316 
317           }
318         }
319 
320         // Set the Residue conformation back to rotamer 0.
321         RotamerLibrary.applyRotamer(residue, rotamers[0]);
322 
323         // Revert the active and use flags.
324         for (Atom atom : sideChainAtoms) {
325           atom.setActive(false);
326           atom.setUse(false);
327         }
328       }
329 
330     } catch (IOException e) {
331       logger.severe("Error writing rotamer file: " + e.getMessage());
332       throw new RuntimeException(e);
333     }
334 
335     return this;
336   }
337 }