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.Crystal;
41  import ffx.crystal.SpaceGroup;
42  import ffx.crystal.SymOp;
43  import ffx.numerics.Potential;
44  import ffx.potential.ForceFieldEnergy;
45  import ffx.potential.bonded.Atom;
46  import ffx.potential.cli.PotentialCommand;
47  import ffx.utilities.FFXBinding;
48  import org.apache.commons.configuration2.CompositeConfiguration;
49  import picocli.CommandLine.Command;
50  import picocli.CommandLine.Option;
51  import picocli.CommandLine.Parameters;
52  
53  import java.io.BufferedReader;
54  import java.io.BufferedWriter;
55  import java.io.File;
56  import java.io.FileReader;
57  import java.io.FileWriter;
58  import java.io.IOException;
59  import java.io.PrintWriter;
60  import java.util.ArrayList;
61  import java.util.Collections;
62  import java.util.List;
63  
64  import static ffx.crystal.ReplicatesCrystal.replicatesCrystalFactory;
65  import static ffx.crystal.SpaceGroupDefinitions.spaceGroupFactory;
66  import static ffx.crystal.SpaceGroupInfo.getCCDCPercent;
67  import static ffx.crystal.SpaceGroupInfo.getPDBRank;
68  import static ffx.crystal.SymOp.applyCartesianSymOp;
69  import static ffx.crystal.SymOp.randomSymOpFactory;
70  import static java.lang.String.format;
71  import static org.apache.commons.io.FilenameUtils.getName;
72  import static org.apache.commons.io.FilenameUtils.removeExtension;
73  
74  /**
75   * Create sub-directories for selected space groups.
76   *
77   * Usage:
78   *   ffxc PrepareSpaceGroups [options] <filename>
79   */
80  @Command(name = "PrepareSpaceGroups", description = " Create sub-directories for selected space groups.")
81  public class PrepareSpaceGroups extends PotentialCommand {
82  
83    /** Only consider space groups populated in the PDB above the specified rank (231 excludes none). */
84    @Option(names = {"-r", "--PDB"}, paramLabel = "231", defaultValue = "231",
85        description = "Only consider space groups populated in the PDB above the specified rank (231 excludes none).")
86    private int rank = 231;
87  
88    /** Only consider space groups populated above the specified percentage in the CSD. */
89    @Option(names = {"-p", "--CSD"}, paramLabel = "1.0", defaultValue = "0.0",
90        description = "Only consider space groups populated above the specified percentage in the CSD.")
91    private double percent = 0.0;
92  
93    /** Only consider chiral space groups. */
94    @Option(names = {"-c", "--chiral"}, paramLabel = "false", defaultValue = "false",
95        description = "Only consider chiral space groups.")
96    private boolean chiral = false;
97  
98    /** Only consider achiral space groups. */
99    @Option(names = {"-a", "--achiral"}, paramLabel = "false", defaultValue = "false",
100       description = "Only consider achiral space groups.")
101   private boolean achiral = false;
102 
103   /** Random Cartesian sym op will choose a translation in the range -Arg .. Arg (default of 1.0 A). */
104   @Option(names = {"--rsym", "--randomSymOp"}, paramLabel = "Arg", defaultValue = "1.0",
105       description = "Random Cartesian sym op will choose a translation in the range -Arg .. Arg (default of 1.0 A).")
106   private double symScalar = 1.0;
107 
108   /** Random unit cell axes will be used to achieve the specified density (default of 1.0 g/cc). */
109   @Option(names = {"-d", "--density"}, paramLabel = "1.0", defaultValue = "1.0",
110       description = "Random unit cell axes will be used to achieve the specified density (default of 1.0 g/cc).")
111   private double density = 1.0;
112 
113   /** Prepare a directory for a single spacegroup. */
114   @Option(names = {"--sg", "--spacegroup"}, description = "Prepare a directory for a single spacegroup.")
115   private String sg;
116 
117   /** The final argument is a single filename in PDB or XYZ format. */
118   @Parameters(arity = "1", paramLabel = "file",
119       description = "The atomic coordinate file in PDB or XYZ format.")
120   private String filename = null;
121 
122   /** Number of directories created. */
123   public int numberCreated = 0;
124 
125   private ForceFieldEnergy energy;
126 
127   public PrepareSpaceGroups() { super(); }
128   public PrepareSpaceGroups(FFXBinding binding) { super(binding); }
129   public PrepareSpaceGroups(String[] args) { super(args); }
130 
131   @Override
132   public PrepareSpaceGroups run() {
133     // Init the context and bind variables.
134     if (!init()) {
135       return this;
136     }
137 
138     // Turn off electrostatic interactions.
139     System.setProperty("mpoleterm", "false");
140 
141     // Set the spacegroup to P1 and choose a default a-axis.
142     System.setProperty("spacegroup", "P1");
143     System.setProperty("a-axis", "10.0");
144 
145     // Load the MolecularAssembly.
146     activeAssembly = getActiveAssembly(filename);
147     if (activeAssembly == null) {
148       logger.info(helpString());
149       return this;
150     }
151 
152     // Set the filename.
153     filename = activeAssembly.getFile().getAbsolutePath();
154 
155     logger.info("\n Preparing space group directories for " + filename);
156 
157     energy = activeAssembly.getPotentialEnergy();
158     CompositeConfiguration properties = activeAssembly.getProperties();
159 
160     File propertyFile = new File(properties.getString("propertyFile"));
161 
162     Atom[] atoms = activeAssembly.getAtomArray();
163     double mass = activeAssembly.getMass();
164     if (density < 0.0) {
165       density = 1.0;
166     }
167 
168     // Use the current base directory, or update if necessary based on the given filename.
169     String dirString = getBaseDirString(filename);
170     String name = getName(filename);
171 
172     for (int num = 1; num <= 230; num++) {
173       SpaceGroup spacegroup = spaceGroupFactory(num);
174       // Statistics for alternative space groups are not listed, spacegroup is used for statistics, spacegroup2 is
175       // used for alternative space group names/symmetry operations.
176       SpaceGroup spacegroup2;
177       if (sg != null && !sg.isEmpty()) {
178         spacegroup2 = spaceGroupFactory(sg);
179         if (spacegroup2 == null) {
180           logger.info(format("\n Space group %s was not recognized.\n", sg));
181           clearProps();
182           return this;
183         }
184         if (spacegroup2.number != spacegroup.number) {
185           continue;
186         }
187       } else {
188         spacegroup2 = spacegroup;
189       }
190 
191       if (chiral && !spacegroup.respectsChirality()) {
192         continue;
193       }
194 
195       if (achiral && spacegroup.respectsChirality()) {
196         continue;
197       }
198 
199       if (getCCDCPercent(num) < percent) {
200         continue;
201       }
202 
203       if (getPDBRank(spacegroup) > rank) {
204         continue;
205       }
206 
207       logger.info(format("\n Preparing %d %s (CSD percent: %7.4f, PDB Rank: %d)",
208           num, spacegroup2.shortName, getCCDCPercent(num), getPDBRank(spacegroup)));
209 
210       // Create the directory.
211       String sgDirName = spacegroup2.shortName.replace('/', '_');
212       File sgDir = new File(dirString + sgDirName);
213 
214       if (!sgDir.exists()) {
215         logger.info("\n Creating space group directory: " + sgDir);
216         //noinspection ResultOfMethodCallIgnored
217         sgDir.mkdir();
218       }
219 
220       double[] abc = spacegroup2.latticeSystem.resetUnitCellParams();
221       Crystal crystal = new Crystal(abc[0], abc[1], abc[2], abc[3], abc[4], abc[5],
222           spacegroup2.shortName);
223       crystal.setDensity(density, mass);
224       double cutoff2 = energy.getCutoffPlusBuffer() * 2.0;
225       // Cut off of aperiodic systems are infinity... replicates crystal factory stalls...
226       if (cutoff2 == Double.POSITIVE_INFINITY) {
227         cutoff2 = 0.1;
228       }
229       crystal = replicatesCrystalFactory(crystal, cutoff2);
230       // Turn off special position checks.
231       crystal.setSpecialPositionCutoff(0.0);
232       crystal.getUnitCell().setSpecialPositionCutoff(0.0);
233       energy.setCrystal(crystal);
234 
235       activeAssembly.moveAllIntoUnitCell();
236 
237       if (symScalar > 0.0) {
238         SymOp symOp = randomSymOpFactory(symScalar);
239         logger.info(format("\n Applying random Cartesian SymOp:\n %s", symOp));
240         double[] xyz = new double[3];
241         for (Atom atom : atoms) {
242           atom.getXYZ(xyz);
243           applyCartesianSymOp(xyz, xyz, symOp);
244           atom.setXYZ(xyz);
245         }
246       }
247 
248       // Save the coordinate file.
249       File sgFile = new File(sgDir.getAbsolutePath() + File.separator + name);
250       logger.info(" Saving " + sgDirName + " coordinates to: " + sgFile);
251       potentialFunctions.save(activeAssembly, sgFile);
252 
253       File keyFile = new File(removeExtension(sgFile.getAbsolutePath()) + ".properties");
254       logger.info(" Saving " + sgDirName + " properties to: " + keyFile);
255 
256       numberCreated++;
257 
258       // Write the new properties file.
259       try (BufferedReader keyReader = new BufferedReader(new FileReader(propertyFile));
260            PrintWriter keyWriter = new PrintWriter(new BufferedWriter(new FileWriter(keyFile)))) {
261         String line;
262         while ((line = keyReader.readLine()) != null) {
263           line = line.trim();
264           if (!line.isEmpty()) {
265             String[] tokens = line.split(" +");
266             if (tokens.length > 1) {
267               String first = tokens[0].toLowerCase();
268               if (first.equals("a-axis") || first.equals("b-axis") || first.equals("c-axis") ||
269                   first.equals("alpha") || first.equals("beta") || first.equals("gamma") ||
270                   first.equals("spacegroup")) {
271                 // This line is skipped, and updated below.
272                 logger.fine(format(" Updating : %s", line));
273               } else if (first.equals("parameters")) {
274                 if (tokens.length > 1) {
275                   if (tokens[1].startsWith("/") || tokens[1].startsWith("\\")) {
276                     // Absolute path specified, therefore do not modify.
277                     keyWriter.println(format("parameters %s", tokens[1]));
278                   } else {
279                     keyWriter.println(format("parameters ../%s", tokens[1]));
280                   }
281                 } else {
282                   logger.warning("Parameter file may not have been specified");
283                 }
284               } else {
285                 keyWriter.println(line);
286               }
287             } else {
288               keyWriter.println(line);
289             }
290           }
291         }
292 
293         // Update the space group and unit cell parameters.
294         keyWriter.println(format("spacegroup %s", spacegroup2.shortName));
295         Crystal unitCell = crystal.getUnitCell();
296         keyWriter.println(format("a-axis %12.8f", unitCell.a));
297         keyWriter.println(format("b-axis %12.8f", unitCell.b));
298         keyWriter.println(format("c-axis %12.8f", unitCell.c));
299         keyWriter.println(format("alpha  %12.8f", unitCell.alpha));
300         keyWriter.println(format("beta   %12.8f", unitCell.beta));
301         keyWriter.println(format("gamma  %12.8f", unitCell.gamma));
302       } catch (IOException ex) {
303         logger.warning(" Exception writing keyfile." + ex);
304       }
305     }
306 
307     clearProps();
308     return this;
309   }
310 
311   private static void clearProps() {
312     // Clear the JVM properties set above.
313     System.clearProperty("mpoleterm");
314     System.clearProperty("spacegroup");
315     System.clearProperty("a-axis");
316   }
317 
318   @Override
319   public List<Potential> getPotentials() {
320     if (energy == null) {
321       return Collections.emptyList();
322     } else {
323       List<Potential> list = new ArrayList<>();
324       list.add(energy);
325       return list;
326     }
327   }
328 }