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