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.crystal.SymOp;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.bonded.Atom;
43  import ffx.potential.cli.PotentialCommand;
44  import ffx.potential.cli.SaveOptions;
45  import ffx.potential.parameters.ForceField;
46  import ffx.potential.parsers.SystemFilter;
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  
56  import static ffx.crystal.SymOp.applyCartesianSymOp;
57  import static org.apache.commons.io.FilenameUtils.concat;
58  import static org.apache.commons.io.FilenameUtils.getName;
59  import static org.apache.commons.io.FilenameUtils.removeExtension;
60  
61  /**
62   * Save the system as an XYZ (or ARC) file.
63   *
64   * Usage:
65   *   ffxc SaveAsXYZ [options] <filename>
66   */
67  @Command(name = "SaveAsXYZ", description = " Save the system as an XYZ file.")
68  public class SaveAsXYZ extends PotentialCommand {
69  
70    @Mixin
71    private SaveOptions saveOptions = new SaveOptions();
72  
73    /** -p or --pos-offset to set the positive atom type offset */
74    @Option(names = {"-p", "--pos-offset"}, paramLabel = "0",
75        description = "Positive offset of atom types in the new file")
76    private int posOffset = 0;
77  
78    /** -n or --neg-offset to set the negative atom type offset */
79    @Option(names = {"-n", "--neg-offset"}, paramLabel = "0",
80        description = "Negative offset of atom types in the new file.")
81    private int negOffset = 0;
82  
83    /** -r or --random to apply a random Cartesian symmetry operator with specified translation range. */
84    @Option(names = {"-r", "--random"}, paramLabel = "X",
85        description = "Apply a random Cartesian SymOp with translation range -X .. X.")
86    private double scalar = -1.0;
87  
88    /** --fs or --firstSnapshot Provide the number of the first snapshot to be written. */
89    @Option(names = {"--fs", "--firstSnapshot"}, paramLabel = "-1", defaultValue = "-1",
90        description = "First snapshot to write out (indexed from 0).")
91    private int firstSnapshot = -1;
92  
93    /** --ls or ---lastSnapshot Provide the number of the last snapshot to be written. */
94    @Option(names = {"--ls", "--lastSnapshot"}, paramLabel = "-1", defaultValue = "-1",
95        description = "Last snapshot to write out (indexed from 0).")
96    private int lastSnapshot = -1;
97  
98    /** --si or --snapshotIncrement Provide the number of the snapshot increment. */
99    @Option(names = {"--si", "--snapshotIncrement"}, paramLabel = "1", defaultValue = "1",
100       description = "Increment between written snapshots.")
101   private int snapshotIncrement = 1;
102 
103   /** --wd or --writeToDirectories Write snapshots to numbered subdirectories. */
104   @Option(names = {"--wd", "--writeToDirectories"}, paramLabel = "false", defaultValue = "false",
105       description = "Write snapshots to numbered subdirectories.")
106   private boolean writeToDirectories = false;
107 
108   /** --alt or --alternateLocation Choose an alternate location for a PDB file. */
109   @Option(names = {"--alt", "--alternateLocation"}, paramLabel = "A", defaultValue = "A",
110       description = "Choose an alternate location for the PDB file (not supported for PDBs with multiple models.")
111   private Character alternateLocation = 'A';
112 
113   /** The final argument is a PDB coordinate file. */
114   @Parameters(arity = "1", paramLabel = "file",
115       description = "The atomic coordinate file PDB format.")
116   private String filename = null;
117 
118   public SaveAsXYZ() { super(); }
119   public SaveAsXYZ(FFXBinding binding) { super(binding); }
120   public SaveAsXYZ(String[] args) { super(args); }
121 
122   @Override
123   public SaveAsXYZ run() {
124     if (!init()) {
125       return this;
126     }
127 
128     // Load one or more MolecularAssembly instances.
129     MolecularAssembly[] molecularAssemblies = getActiveAssemblies(filename);
130     if (molecularAssemblies == null) {
131       logger.info(helpString());
132       return this;
133     }
134 
135     if (molecularAssemblies.length > 1) {
136       Character currentAltLoc = activeAssembly.getAlternateLocation();
137       logger.info("\n Current alternate location: " + currentAltLoc);
138       if (!currentAltLoc.equals(alternateLocation)) {
139         for (MolecularAssembly molecularAssembly : molecularAssemblies) {
140           Character altLoc = molecularAssembly.getAlternateLocation();
141           if (altLoc.equals(alternateLocation)) {
142             activeAssembly = molecularAssembly;
143             logger.info(" Switching to alternate location: " + altLoc);
144             break;
145           }
146         }
147       }
148     }
149 
150     // Set the filename.
151     filename = activeAssembly.getFile().getAbsolutePath();
152 
153     SystemFilter openFilter = potentialFunctions.getFilter();
154     int numModels = openFilter.countNumModels();
155 
156     int offset = 0;
157     if (posOffset > 0) {
158       offset = posOffset;
159     }
160     if (negOffset > 0) {
161       offset = -negOffset;
162     }
163 
164     if (offset != 0) {
165       logger.info("\n Offset atom types by " + offset);
166       ForceField forceField = activeAssembly.getForceField();
167       forceField.renumberForceField(0, offset, 0);
168     }
169 
170     if (scalar > 0.0) {
171       SymOp symOp = SymOp.randomSymOpFactory(scalar);
172       logger.info(String.format("\n Applying random Cartesian SymOp\n: %s", symOp));
173       Atom[] atoms = activeAssembly.getAtomArray();
174       double[] xyz = new double[3];
175       for (Atom atom : atoms) {
176         atom.getXYZ(xyz);
177         applyCartesianSymOp(xyz, xyz, symOp);
178         atom.setXYZ(xyz);
179       }
180     }
181 
182     String dirString = getBaseDirString(filename);
183     String name = getName(filename);
184 
185     // Choose a single snapshot to write out from an archive.
186     if (firstSnapshot >= 0) {
187       XYZFilter snapshotFilter = new XYZFilter(new File(dirString + name),
188           activeAssembly, activeAssembly.getForceField(), activeAssembly.getProperties());
189       openFilter.readNext(true);
190       int counter = 0;
191       int snapshotCounter = 0;
192       logger.info(" Writing snapshots from " + firstSnapshot + " to " + lastSnapshot + " with increment " + snapshotIncrement);
193       boolean reset = true;
194 
195       while (openFilter.readNext(reset)) {
196         // No more resets
197         reset = false;
198         int counterOffset = counter - firstSnapshot;
199         if (counter >= firstSnapshot && (lastSnapshot < 0 || counter <= lastSnapshot) && counterOffset % snapshotIncrement == 0) {
200           File snapshotFile;
201           if (writeToDirectories) {
202             String subdirectory = concat(dirString, Integer.toString(snapshotCounter));
203             new File(subdirectory).mkdirs();
204             snapshotFile = new File(concat(subdirectory, name));
205           } else {
206             snapshotFile = new File(concat(dirString, removeExtension(name) + "." + counter + ".xyz"));
207           }
208 
209           potentialFunctions.versionFile(snapshotFile);
210           saveOptions.preSaveOperations(activeAssembly);
211           logger.info("\n Writing out XYZ for " + snapshotFile);
212           snapshotFilter.writeFile(snapshotFile, true);
213           snapshotCounter++;
214         }
215         counter++;
216       }
217       return this;
218     }
219 
220     logger.info("\n Writing out XYZ for " + filename);
221 
222     if (numModels <= 1) {
223       // Just save a single snapshot.
224       name = removeExtension(name) + ".xyz";
225       File saveFile = new File(dirString + name);
226       saveOptions.preSaveOperations(activeAssembly);
227       potentialFunctions.save(activeAssembly, saveFile);
228     } else {
229       // Save to an arc file rather than an xyz file if more than one model exists.
230       name = removeExtension(name) + ".arc";
231       File saveFile = new File(dirString + name);
232       saveFile = potentialFunctions.versionFile(saveFile);
233       saveOptions.preSaveOperations(activeAssembly);
234       potentialFunctions.save(activeAssembly, saveFile);
235 
236       XYZFilter saveFilter = (XYZFilter) potentialFunctions.getFilter();
237       while (openFilter.readNext(false)) {
238         saveOptions.preSaveOperations(activeAssembly);
239         saveFilter.writeFile(saveFile, true);
240       }
241     }
242 
243     return this;
244   }
245 }