View Javadoc
1   package ffx.potential.commands;
2   
3   import static java.lang.String.format;
4   
5   import com.google.common.collect.MinMaxPriorityQueue;
6   import ffx.crystal.Crystal;
7   import ffx.numerics.Potential;
8   import ffx.potential.AssemblyState;
9   import ffx.potential.ForceFieldEnergy;
10  import ffx.potential.MolecularAssembly;
11  import ffx.potential.cli.AtomSelectionOptions;
12  import ffx.potential.cli.PotentialCommand;
13  import ffx.potential.parsers.PDBFilter;
14  import ffx.potential.parsers.SystemFilter;
15  import ffx.potential.parsers.XYZFilter;
16  import ffx.potential.utils.StructureMetrics;
17  import ffx.utilities.FFXContext;
18  import java.io.File;
19  import java.util.Collections;
20  import java.util.List;
21  import org.apache.commons.io.FilenameUtils;
22  import picocli.CommandLine.Command;
23  import picocli.CommandLine.Mixin;
24  import picocli.CommandLine.Option;
25  import picocli.CommandLine.Parameters;
26  
27  /**
28   * The Energy script evaluates the energy of a system.
29   * <br>
30   * Usage:
31   * <br>
32   * ffxc Energy &lt;filename&gt;
33   */
34  @Command(description = " Compute the force field potential energy.", name = "ffxc Energy")
35  public class Energy extends PotentialCommand {
36  
37    @Mixin
38    private AtomSelectionOptions atomSelectionOptions;
39    /**
40     * -m or --moments print out electrostatic moments.
41     */
42    @Option(names = {"-m",
43        "--moments"}, paramLabel = "false", defaultValue = "false", description = "Print out electrostatic moments.")
44    private boolean moments = false;
45    /**
46     * --rg or --gyrate Print out the radius of gyration.
47     */
48    @Option(names = {"--rg",
49        "--gyrate"}, paramLabel = "false", defaultValue = "false", description = "Print out the radius of gyration.")
50    private boolean gyrate = false;
51    /**
52     * --in or --inertia Print out the moments of inertia.
53     */
54    @Option(names = {"--in",
55        "--inertia"}, paramLabel = "false", defaultValue = "false", description = "Print out the moments of inertia.")
56    private boolean inertia = false;
57    /**
58     * -g or --gradient to print out gradients.
59     */
60    @Option(names = {"-g",
61        "--gradient"}, paramLabel = "false", defaultValue = "false", description = "Compute the atomic gradient as well as energy.")
62    private boolean gradient = false;
63    /**
64     * * --fl or --findLowest Return the n lowest energy structures from an ARC or PDB file.
65     */
66    @Option(names = {"--fl",
67        "--findLowest"}, paramLabel = "0", defaultValue = "0", description = "Return the n lowest energies from an ARC/PDB file.")
68    private int fl = 0;
69    /**
70     * -v or --verbose enables printing out all energy components for multi-snapshot files ( the first
71     * snapshot is always printed verbosely).
72     */
73    @Option(names = {"-v",
74        "--verbose"}, paramLabel = "false", defaultValue = "false", description = "Print out all energy components for each snapshot.")
75    private boolean verbose = false;
76    /**
77     * --dc or --densityCutoff Collect structures above a specified density.
78     */
79    @Option(names = {"--dc",
80        "--densityCutoff"}, paramLabel = "0.0", defaultValue = "0.0", description = "Create ARC file of structures above a specified density.")
81    private double dCutoff = 0.0;
82    /**
83     * --ec or --energyCutOff Collect structures below a specified energy range from the minimum
84     * energy.
85     */
86    @Option(names = {"--ec",
87        "--energyCutoff"}, paramLabel = "0.0", defaultValue = "0.0", description = "Create ARC file of structures within a specified energy of the lowest energy structure.")
88    private double eCutoff = 0.0;
89    /**
90     * The final argument is a PDB or XYZ coordinate file.
91     */
92    @Parameters(arity = "1", paramLabel = "file", description = "The atomic coordinate file in PDB or XYZ format.")
93    private String filename = null;
94    public double energy = 0.0;
95    public ForceFieldEnergy forceFieldEnergy = null;
96    private AssemblyState assemblyState = null;
97  
98    /**
99     * Energy constructor.
100    */
101   public Energy() {
102     this(new FFXContext());
103   }
104 
105   /**
106    * Energy constructor.
107    *
108    * @param ffxContext The FFXContext to use.
109    */
110   public Energy(FFXContext ffxContext) {
111     super(ffxContext);
112   }
113 
114   /**
115    * Execute the script.
116    */
117   public Energy run() {
118     // Init the context and bind variables.
119     if (!init()) {
120       return this;
121     }
122 
123     // Load the MolecularAssembly.
124     setActiveAssembly(getActiveAssembly(filename));
125     if (activeAssembly == null) {
126       logger.info(helpString());
127       return this;
128     }
129 
130     // Set the filename.
131     filename = activeAssembly.getFile().getAbsolutePath();
132 
133     logger.info("\n Running Energy on " + filename);
134 
135     // Apply atom selections
136     atomSelectionOptions.setActiveAtoms(activeAssembly);
137 
138     forceFieldEnergy = activeAssembly.getPotentialEnergy();
139     int nVars = forceFieldEnergy.getNumberOfVariables();
140     double[] x = new double[nVars];
141     forceFieldEnergy.getCoordinates(x);
142 
143     if (gradient) {
144       double[] g = new double[nVars];
145       int nAts = (nVars / 3);
146       energy = forceFieldEnergy.energyAndGradient(x, g, true);
147       logger.info("    Atom       X, Y and Z Gradient Components (kcal/mol/A)");
148       for (int i = 0; i < nAts; i++) {
149         int i3 = 3 * i;
150         logger.info(String.format(" %7d %16.8f %16.8f %16.8f", i + 1, g[i3], g[i3 + 1], g[i3 + 2]));
151       }
152     } else {
153       energy = forceFieldEnergy.energy(x, true);
154     }
155 
156     if (moments) {
157       forceFieldEnergy.getPmeNode().computeMoments(activeAssembly.getActiveAtomArray(), false);
158     }
159 
160     if (gyrate) {
161       double rg = StructureMetrics.radiusOfGyration(activeAssembly.getActiveAtomArray());
162       logger.info(String.format(" Radius of gyration:           %10.5f A", rg));
163     }
164 
165     if (inertia) {
166       double[][] inertiaValue = StructureMetrics.momentsOfInertia(
167           activeAssembly.getActiveAtomArray(), false, true, true);
168     }
169 
170     SystemFilter systemFilter = potentialFunctions.getFilter();
171     if (systemFilter instanceof XYZFilter || systemFilter instanceof PDBFilter) {
172 
173       int numSnaps = fl;
174       double lowestEnergy = energy;
175       assemblyState = new AssemblyState(activeAssembly);
176       int index = 1;
177 
178       // Making the MinMax priority queue that will expel the largest entry when it reaches its maximum size N/
179       MinMaxPriorityQueue<StateContainer> lowestEnergyQueue = null;
180       if (fl > 0) {
181         lowestEnergyQueue = MinMaxPriorityQueue.maximumSize(numSnaps).create();
182         lowestEnergyQueue.add(new StateContainer(assemblyState, lowestEnergy));
183       }
184 
185       int numModels = systemFilter.countNumModels();
186       //Store densities in ordered encountered (used in density cutoff implementation).
187       double[] densities = new double[numModels];
188       //Store energies in ordered encountered (used in energy cutoff implementation).
189       double[] energies = new double[numModels];
190       energies[0] = energy;
191 
192       while (systemFilter.readNext()) {
193         index = index++;
194         Crystal crystal = activeAssembly.getCrystal();
195         densities[index - 1] = crystal.getDensity(activeAssembly.getMass());
196         forceFieldEnergy.setCrystal(crystal);
197         forceFieldEnergy.getCoordinates(x);
198         if (verbose) {
199           logger.info(String.format(" Snapshot %4d", index));
200           if (!crystal.aperiodic()) {
201             logger.info(String.format("\n Density:                                %6.3f (g/cc)",
202                 crystal.getDensity(activeAssembly.getMass())));
203           }
204 
205           energy = forceFieldEnergy.energy(x, true);
206         } else {
207           energy = forceFieldEnergy.energy(x, false);
208           logger.info(String.format(" Snapshot %4d: %16.8f (kcal/mol)", index, energy));
209         }
210 
211         energies[index - 1] = energy;
212 
213         //Update lowest encountered energy
214         if (energy < lowestEnergy) {
215           lowestEnergy = energy;
216         }
217 
218         if (fl > 0) {
219           lowestEnergyQueue.add(new StateContainer(new AssemblyState(activeAssembly), energy));
220         }
221 
222         if (moments) {
223           forceFieldEnergy.getPmeNode().computeMoments(activeAssembly.getActiveAtomArray(), false);
224         }
225 
226         if (gyrate) {
227           double rg = StructureMetrics.radiusOfGyration(activeAssembly.getActiveAtomArray());
228           logger.info(String.format(" Radius of gyration:          %10.5f A", rg));
229         }
230 
231         if (inertia) {
232           double[][] inertiaValue = StructureMetrics.momentsOfInertia(
233               activeAssembly.getActiveAtomArray(), false, true, true);
234         }
235 
236       }
237 
238       // Use the current base directory, or update if necessary based on the given filename.
239       String dirString = getBaseDirString(filename);
240 
241       // If cutoffs have been selected create an ARC or PDB to store structures that satisfy cutoff.
242       try {
243         if ((eCutoff > 0.0 || dCutoff > 0.0) && numModels > 1) {
244           systemFilter.readNext(true);
245           setActiveAssembly(systemFilter.getActiveMolecularSystem());
246 
247           String name = FilenameUtils.getName(filename);
248           String ext = FilenameUtils.getExtension(name);
249           name = FilenameUtils.removeExtension(name);
250 
251           File saveFile;
252           SystemFilter writeFilter;
253           if (ext.toUpperCase().contains("XYZ")) {
254             saveFile = new File(dirString + name + ".xyz");
255             writeFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
256                 activeAssembly.getProperties());
257             potentialFunctions.saveAsXYZ(activeAssembly, saveFile);
258           } else if (ext.toUpperCase().contains("ARC")) {
259             saveFile = new File(dirString + name + ".arc");
260             saveFile = potentialFunctions.versionFile(saveFile);
261             writeFilter = new XYZFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
262                 activeAssembly.getProperties());
263             logger.info(" Saving to " + saveFile.getAbsolutePath());
264             saveFile.createNewFile();
265           } else {
266             saveFile = new File(dirString + name + ".pdb");
267             saveFile = potentialFunctions.versionFile(saveFile);
268             writeFilter = new PDBFilter(saveFile, activeAssembly, activeAssembly.getForceField(),
269                 activeAssembly.getProperties());
270             ((PDBFilter) writeFilter).setModelNumbering(0);
271             saveFile.createNewFile();
272           }
273 
274           // Determine if each structure meets the cutoff condition
275           if (systemFilter instanceof XYZFilter || systemFilter instanceof PDBFilter) {
276             int structNum = 0;
277             if (eCutoff > 0.0) {
278               logger.info(
279                   format("Lowest Energy of: %16.4f\n Saving structures with energy below: %16.4f",
280                       lowestEnergy, lowestEnergy + eCutoff));
281             }
282 
283             do {
284               if (dCutoff > 0.0 && eCutoff > 0.0) {
285                 if (energies[structNum] < lowestEnergy + eCutoff && densities[structNum] > dCutoff) {
286                   if (systemFilter instanceof PDBFilter) {
287                     PDBFilter pdbFilter = (PDBFilter) systemFilter;
288                     pdbFilter.writeFile(saveFile, true, false, false);
289                     // ResourceGroovyMethods.append(saveFile, "ENDMDL\n");
290                   } else if (systemFilter instanceof XYZFilter) {
291                     writeFilter.writeFile(saveFile, true);
292                   }
293                 }
294               } else if (dCutoff > 0.0) {
295                 if (densities[structNum] > dCutoff) {
296                   if (systemFilter instanceof PDBFilter) {
297                     PDBFilter pdbFilter = (PDBFilter) systemFilter;
298                     pdbFilter.writeFile(saveFile, true, false, false);
299                     // ResourceGroovyMethods.append(saveFile, "ENDMDL\n");
300                   } else if (systemFilter instanceof XYZFilter) {
301                     writeFilter.writeFile(saveFile, true);
302                   }
303                 }
304               } else if (eCutoff > 0.0) {
305                 if (energies[structNum] < lowestEnergy + eCutoff) {
306                   if (systemFilter instanceof PDBFilter) {
307                     PDBFilter pdbFilter = (PDBFilter) systemFilter;
308                     pdbFilter.writeFile(saveFile, true, false, false);
309                     // ResourceGroovyMethods.append(saveFile, "ENDMDL\n");
310                   } else if (systemFilter instanceof XYZFilter) {
311                     writeFilter.writeFile(saveFile, true);
312                   }
313                 }
314               }
315               structNum++;
316             } while (systemFilter.readNext());
317             if (systemFilter instanceof PDBFilter) {
318               // ResourceGroovyMethods.append(saveFile, "END\n");
319             }
320           }
321         }
322       } catch (Exception e) {
323         logger.info(" Exception evaluating cutoffs:\n " + e);
324       }
325 
326       if (fl > 0) {
327         if (numSnaps > index) {
328           logger.warning(String.format(
329               " Requested %d snapshots, but file %s has only %d snapshots. All %d energies will be reported",
330               numSnaps, filename, index, index));
331           numSnaps = index;
332         }
333 
334         String name = FilenameUtils.getName(filename);
335 
336         for (int i = 0; i < numSnaps - 1; i++) {
337           StateContainer savedState = lowestEnergyQueue.removeLast();
338           AssemblyState finalAssembly = savedState.state();
339           finalAssembly.revertState();
340           double finalEnergy = savedState.getEnergy();
341           logger.info(
342               String.format(" The potential energy found is %16.8f (kcal/mol)", finalEnergy));
343           File saveFile = potentialFunctions.versionFile(new File(dirString + name));
344           MolecularAssembly molecularAssembly = assemblyState.getMolecularAssembly();
345           potentialFunctions.saveAsPDB(molecularAssembly, saveFile);
346         }
347 
348         StateContainer savedState = lowestEnergyQueue.removeLast();
349         AssemblyState lowestAssembly = savedState.state();
350         lowestEnergy = savedState.getEnergy();
351 
352         assemblyState.revertState();
353         logger.info(
354             String.format(" The lowest potential energy found is %16.8f (kcal/mol)", lowestEnergy));
355 
356         // Prints our final energy (which will be the lowest energy
357         File saveFile = potentialFunctions.versionFile(new File(dirString + name));
358         MolecularAssembly molecularAssembly = assemblyState.getMolecularAssembly();
359         potentialFunctions.saveAsPDB(molecularAssembly, saveFile);
360       }
361 
362     }
363 
364     return this;
365   }
366 
367   @Override
368   public List<Potential> getPotentials() {
369     List<Potential> potentials;
370     if (forceFieldEnergy == null) {
371       potentials = Collections.emptyList();
372     } else {
373       potentials = Collections.singletonList((Potential) forceFieldEnergy);
374     }
375 
376     return potentials;
377   }
378 
379   /**
380    * This entry point is being used to test GraalVM ahead-of-time compilation.
381    *
382    * @param args Command line arguments.
383    */
384   public static void main(String... args) {
385     System.setProperty("java.awt.headless", "true");
386     System.setProperty("j3d.rend", "noop");
387     FFXContext binding = new FFXContext(args);
388     Energy energyScript = new Energy(binding);
389     energyScript.run();
390     System.exit(0);
391   }
392 
393   public AtomSelectionOptions getAtomSelectionOptions() {
394     return atomSelectionOptions;
395   }
396 
397   public void setAtomSelectionOptions(AtomSelectionOptions atomSelectionOptions) {
398     this.atomSelectionOptions = atomSelectionOptions;
399   }
400 
401   private record StateContainer(AssemblyState state, double e) implements Comparable<StateContainer> {
402 
403     public double getEnergy() {
404         return e;
405       }
406 
407       @Override
408       public int compareTo(StateContainer o) {
409         return Double.compare(e, o.getEnergy());
410       }
411 
412   }
413 }