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.potential.commands;
39  
40  import ffx.potential.bonded.Atom;
41  import ffx.potential.cli.PotentialCommand;
42  import ffx.potential.cli.SaveOptions;
43  import ffx.potential.extended.ExtendedSystem;
44  import ffx.potential.parsers.PDBFilter;
45  import ffx.potential.parsers.SystemFilter;
46  import ffx.potential.parsers.XPHFilter;
47  import ffx.potential.parsers.XYZFilter;
48  import ffx.utilities.FFXBinding;
49  import picocli.CommandLine.Command;
50  import picocli.CommandLine.Mixin;
51  import picocli.CommandLine.Option;
52  import picocli.CommandLine.Parameters;
53  
54  import java.io.File;
55  import java.io.IOException;
56  import java.nio.file.Files;
57  import java.nio.file.StandardOpenOption;
58  
59  import static org.apache.commons.io.FilenameUtils.concat;
60  import static org.apache.commons.io.FilenameUtils.getName;
61  import static org.apache.commons.io.FilenameUtils.removeExtension;
62  
63  /**
64   * Save the system as a PDB file.
65   *
66   * Usage:
67   *   ffxc SaveAsPDB [options] <filename>
68   */
69  @Command(name = "SaveAsPDB", description = " Save the system as a PDB file.")
70  public class SaveAsPDB extends PotentialCommand {
71  
72    @Mixin
73    private SaveOptions saveOptions = new SaveOptions();
74  
75    /** --fs or --firstSnapshot Provide the number of the first snapshot to be written. */
76    @Option(names = {"--fs", "--firstSnapshot"}, paramLabel = "-1", defaultValue = "-1",
77        description = "First snapshot to write out (indexed from 0).")
78    private int firstSnapshot = -1;
79  
80    /** --ls or --lastSnapshot Provide the number of the last snapshot to be written. */
81    @Option(names = {"--ls", "--lastSnapshot"}, paramLabel = "-1", defaultValue = "-1",
82        description = "Last snapshot to write out (indexed from 0).")
83    private int lastSnapshot = -1;
84  
85    /** --si or --snapshotIncrement Provide the number of the snapshot increment. */
86    @Option(names = {"--si", "--snapshotIncrement"}, paramLabel = "1", defaultValue = "1",
87        description = "Increment between written snapshots.")
88    private int snapshotIncrement = 1;
89  
90    /** --wd or --writeToDirectories Provide the number of the snapshot increment. */
91    @Option(names = {"--wd", "--writeToDirectories"}, paramLabel = "false", defaultValue = "false",
92        description = "Write snapshots to numbered subdirectories.")
93    private boolean writeToDirectories = false;
94  
95    /** --cp or --copyProperties Copy the property file to each subdirectory. */
96    @Option(names = {"--cp", "--copyProperties"}, paramLabel = "true", defaultValue = "true",
97        description = "Copy the property file to numbered subdirectories (ignored if not writing to subdirectories).")
98    private boolean copyProperties = true;
99  
100   /** --esv Handle an extended system at the bottom of XYZ files using XPHFilter. */
101   @Option(names = {"--esv"}, paramLabel = "file", defaultValue = "",
102       description = "PDB file to build extended system from.")
103   private String extended = "";
104 
105   /** The final argument is an XYZ or ARC coordinate file. */
106   @Parameters(arity = "1", paramLabel = "file",
107       description = "The atomic coordinate file in XYZ or ARC format.")
108   private String filename = null;
109 
110   public SaveAsPDB() { super(); }
111   public SaveAsPDB(FFXBinding binding) { super(binding); }
112   public SaveAsPDB(String[] args) { super(args); }
113 
114   @Override
115   public SaveAsPDB run() {
116     if (!init()) {
117       return this;
118     }
119 
120     // Load the MolecularAssembly.
121     activeAssembly = getActiveAssembly(filename);
122     if (activeAssembly == null) {
123       logger.info(helpString());
124       return this;
125     }
126 
127     // Set the filename.
128     filename = activeAssembly.getFile().getAbsolutePath();
129     SystemFilter openFilter = potentialFunctions.getFilter();
130     ExtendedSystem esvSystem = null;
131 
132     if (openFilter instanceof XYZFilter && extended != null && !extended.isEmpty()) {
133       logger.info("Building extended system from " + extended);
134       // Build extended system from file with residue info.
135       activeAssembly = getActiveAssembly(extended);
136       esvSystem = new ExtendedSystem(activeAssembly, 7.4, null);
137       // Restore original filename while retaining extended data.
138       activeAssembly.setFile(new File(filename));
139       openFilter = new XPHFilter(activeAssembly.getFile(), activeAssembly, activeAssembly.getForceField(),
140           activeAssembly.getProperties(), esvSystem);
141       openFilter.readFile();
142       logger.info("Reading ESV lambdas from XPH file");
143     }
144 
145     logger.info("\n Saving PDB for " + filename);
146 
147     // Use the current base directory, or update if necessary based on the given filename.
148     String dirString = getBaseDirString(filename);
149 
150     String name = removeExtension(getName(filename)) + ".pdb";
151     File saveFile = new File(dirString + name);
152 
153     if (firstSnapshot >= 0) {
154       // Write selected snapshots to separate files.
155       PDBFilter snapshotFilter = new PDBFilter(saveFile, activeAssembly,
156           activeAssembly.getForceField(), activeAssembly.getProperties());
157       openFilter.readNext(true);
158       boolean resetPosition = true;
159       int counter = 0;
160       int snapshotCounter = 0;
161       logger.info(" Writing snapshots from " + firstSnapshot + " to " + lastSnapshot + " with increment " + snapshotIncrement);
162 
163       while (openFilter.readNext(resetPosition)) {
164         // No more resets after first read.
165         resetPosition = false;
166         int offset = counter - firstSnapshot;
167         if (counter >= firstSnapshot && (lastSnapshot < 0 || counter <= lastSnapshot) && offset % snapshotIncrement == 0) {
168           File snapshotFile;
169           if (writeToDirectories) {
170             String subdirectory = concat(dirString, Integer.toString(snapshotCounter));
171             snapshotFile = new File(concat(subdirectory, name));
172             // Ensure directory exists and optionally copy properties (handled by caller environment typically).
173             //noinspection ResultOfMethodCallIgnored
174             new File(subdirectory).mkdirs();
175             if (copyProperties) {
176               String propertyFile = activeAssembly.getProperties().getString("propertyFile");
177               if (propertyFile != null) {
178                 File copyOfPropFile = new File(concat(subdirectory, getName(propertyFile)));
179                 try {
180                   Files.createDirectories(copyOfPropFile.getParentFile().toPath());
181                   Files.copy(new File(propertyFile).toPath(), copyOfPropFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
182                 } catch (IOException e) {
183                   logger.info(" Could not copy properties file: " + e.getMessage());
184                 }
185               }
186             }
187           } else {
188             snapshotFile = new File(concat(dirString,
189                 removeExtension(name) + "." + counter + ".pdb"));
190           }
191           potentialFunctions.versionFile(snapshotFile);
192           saveOptions.preSaveOperations(activeAssembly);
193           logger.info(" Saving PDB to         " + snapshotFile);
194           snapshotFilter.writeFile(snapshotFile, true, false, false);
195           try {
196             Files.writeString(snapshotFile.toPath(), "END\n", StandardOpenOption.APPEND, StandardOpenOption.CREATE);
197           } catch (IOException e) { /* ignore */ }
198           snapshotCounter++;
199         }
200         counter++;
201       }
202       return this;
203     }
204 
205     // Version the save file for multi-model writing when needed.
206     saveFile = potentialFunctions.versionFile(saveFile);
207 
208     int numModels = openFilter.countNumModels();
209     if (numModels == 1) {
210       // Write one model and return.
211       saveOptions.preSaveOperations(activeAssembly);
212       potentialFunctions.saveAsPDB(activeAssembly, saveFile);
213       return this;
214     }
215 
216     // Write out the first model as "MODEL 1".
217     try {
218       Files.writeString(saveFile.toPath(), "MODEL        1\n");
219     } catch (IOException e) { /* ignore */ }
220     saveOptions.preSaveOperations(activeAssembly);
221     potentialFunctions.saveAsPDB(activeAssembly, saveFile, false, true);
222     try {
223       Files.writeString(saveFile.toPath(), "ENDMDL\n", StandardOpenOption.APPEND, StandardOpenOption.CREATE);
224     } catch (IOException e) { /* ignore */ }
225 
226     PDBFilter saveFilter = (PDBFilter) potentialFunctions.getFilter();
227     saveFilter.setModelNumbering(1);
228 
229     // Iterate through the rest of the models in an arc or pdb.
230     if (openFilter instanceof XYZFilter || openFilter instanceof PDBFilter || openFilter instanceof XPHFilter) {
231       try {
232         while (openFilter.readNext(false)) {
233           if (esvSystem != null) {
234             for (Atom atom : activeAssembly.getAtomList()) {
235               int atomIndex = atom.getIndex() - 1;
236               atom.setOccupancy(esvSystem.getTitrationLambda(atomIndex));
237               atom.setTempFactor(esvSystem.getTautomerLambda(atomIndex));
238             }
239           }
240           saveOptions.preSaveOperations(activeAssembly);
241           saveFilter.writeFile(saveFile, true, true, false);
242         }
243       } catch (Exception e) {
244         // Do nothing.
245       }
246       // Add a final "END" record.
247       try {
248         Files.writeString(saveFile.toPath(), "END\n", StandardOpenOption.APPEND, StandardOpenOption.CREATE);
249       } catch (IOException e) { /* ignore */ }
250     }
251     return this;
252   }
253 }