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-2024.
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.xray.cli;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.utilities.FFXProperty;
42  import ffx.xray.CrystalReciprocalSpace;
43  import ffx.xray.CrystalReciprocalSpace.SolventModel;
44  import ffx.xray.DiffractionData;
45  import ffx.xray.RefinementEnergy;
46  import ffx.xray.RefinementMinimize;
47  import ffx.xray.RefinementMinimize.RefinementMode;
48  import ffx.xray.parsers.DiffractionFile;
49  import org.apache.commons.configuration2.CompositeConfiguration;
50  import org.apache.commons.io.FilenameUtils;
51  import picocli.CommandLine.ArgGroup;
52  import picocli.CommandLine.Option;
53  import picocli.CommandLine.ParseResult;
54  
55  import java.util.ArrayList;
56  import java.util.List;
57  import java.util.logging.Logger;
58  
59  import static ffx.utilities.PropertyGroup.StructuralRefinement;
60  import static java.lang.Boolean.parseBoolean;
61  import static java.lang.Double.parseDouble;
62  import static java.lang.String.format;
63  
64  /**
65   * Represents command line options for scripts that utilize X-ray data with a maximum likelihood
66   * target.
67   *
68   * @author Michael J. Schnieders
69   * @since 1.0
70   */
71  public class XrayOptions extends DataRefinementOptions {
72  
73    private static final Logger logger = Logger.getLogger(XrayOptions.class.getName());
74  
75    /**
76     * The ArgGroup keeps the XrayOptionGroup together when printing help.
77     */
78    @ArgGroup(heading = "%n X-ray Refinement Options%n", validate = false)
79    private final XrayOptionGroup group = new XrayOptionGroup();
80    /**
81     * The ArgGroup keeps the XrayReflectionsGroup together when printing help.
82     */
83    @ArgGroup(heading = "%n X-ray Reflection Data Options%n", validate = false)
84    private final XrayReflectionsGroup reflectionGroup = new XrayReflectionsGroup();
85    /**
86     * The ArgGroup keeps the BFactorGroup together when printing help.
87     */
88    @ArgGroup(heading = "%n X-ray B-Factor Options%n", validate = false)
89    private final BFactorGroup bfactorGroup = new BFactorGroup();
90    /**
91     * The ArgGroup keeps the BFactorGroup together when printing help.
92     */
93    @ArgGroup(heading = "%n X-ray Target Options%n", validate = false)
94    private final ScatteringGroup targetGroup = new ScatteringGroup();
95    /**
96     * The ArgGroup keeps the XrayOptionGroup together when printing help.
97     */
98    @ArgGroup(heading = "%n X-ray Bulk Solvent Options%n", validate = false)
99    private final BulkSolventGroup solventGroup = new BulkSolventGroup();
100   /**
101    * The refinement mode to use.
102    */
103   public RefinementMode refinementMode = RefinementMode.COORDINATES;
104   /**
105    * The SolventModel to use.
106    */
107   public SolventModel solventModel = SolventModel.POLYNOMIAL;
108 
109   /**
110    * Parse options.
111    */
112   public void init() {
113     refinementMode = RefinementMinimize.parseMode(group.modeString);
114     solventModel = CrystalReciprocalSpace.parseSolventModel(solventGroup.solventString);
115   }
116 
117   /**
118    * Process input to collect Diffraction Files.
119    *
120    * @param filenames Input filenames (first filename is ignored).
121    * @param systems   Currently open systems.
122    * @return a list of DiffractionFile instances.
123    */
124   public List<DiffractionFile> processData(List<String> filenames, MolecularAssembly[] systems) {
125     List<DiffractionFile> diffractionfiles = new ArrayList<>();
126 
127     logger.info("\n");
128 
129     if (filenames.size() > 1) {
130       logger.info(format(" Diffraction file = %s, weight = %3.1f, neutron = %b", filenames.get(1), wA, Boolean.FALSE));
131       DiffractionFile diffractionfile = new DiffractionFile(filenames.get(1), wA, false);
132       diffractionfiles.add(diffractionfile);
133     }
134 
135     if (reflectionGroup.data != null) {
136       for (int i = 0; i < reflectionGroup.data.length; i += 3) {
137         boolean neutron = false;
138         double w = wA;
139         if (reflectionGroup.data.length > i + 1) {
140           try {
141             w = parseDouble(reflectionGroup.data[i + 1]);
142           } catch (Exception e) {
143             //
144           }
145         }
146         if (reflectionGroup.data.length > i + 2) {
147           try {
148             neutron = parseBoolean(reflectionGroup.data[i + 2]);
149           } catch (Exception e) {
150             //
151           }
152         }
153         logger.info(format(" Diffraction file = %s, weight = %3.1f, neutron = %b", reflectionGroup.data[i], w, neutron));
154         DiffractionFile diffractionfile = new DiffractionFile(reflectionGroup.data[i], w, neutron);
155         diffractionfiles.add(diffractionfile);
156       }
157     }
158 
159     if (diffractionfiles.isEmpty()) {
160       String filename = systems[0].getFile().getAbsolutePath();
161       filename = FilenameUtils.removeExtension(filename);
162       filename = FilenameUtils.getBaseName(filename);
163 
164       logger.info(format(" Diffraction from = %s, weight = %3.1f, neutron = %b", filename, wA, false));
165       DiffractionFile diffractionfile = new DiffractionFile(systems, 1.0, false);
166       diffractionfiles.add(diffractionfile);
167     }
168 
169     return diffractionfiles;
170   }
171 
172   /**
173    * setProperties.
174    *
175    * @param parseResult a {@link picocli.CommandLine.ParseResult} object.
176    * @param properties  a {@link org.apache.commons.configuration2.CompositeConfiguration} object.
177    */
178   public void setProperties(ParseResult parseResult, CompositeConfiguration properties) {
179     // wA
180     if (!parseResult.hasMatchedOption("wA")) {
181       wA = properties.getDouble("data-weight", wA);
182     }
183     properties.setProperty("data-weight", wA);
184 
185     // bSimWeight
186     if (!parseResult.hasMatchedOption("bSimWeight")) {
187       bfactorGroup.bSimWeight = properties.getDouble("b-sim-weight", bfactorGroup.bSimWeight);
188     }
189     properties.setProperty("b-sim-weight", bfactorGroup.bSimWeight);
190 
191     // F/SigF Cutoff
192     if (!parseResult.hasMatchedOption("fSigFCutoff")) {
193       reflectionGroup.fSigFCutoff = properties.getDouble("f-sigf-cutoff", reflectionGroup.fSigFCutoff);
194     }
195     properties.setProperty("f-sigf-cutoff", reflectionGroup.fSigFCutoff);
196 
197     // Solvent Grid Search
198     if (!parseResult.hasMatchedOption("gridSearch")) {
199       solventGroup.gridSearch = properties.getBoolean("solvent-grid-search", solventGroup.gridSearch);
200     }
201     properties.setProperty("solvent-grid-search", solventGroup.gridSearch);
202 
203     // Number of Bins
204     if (!parseResult.hasMatchedOption("nBins")) {
205       reflectionGroup.nBins = properties.getInt("reflection-bins", reflectionGroup.nBins);
206     }
207     properties.setProperty("reflection-bins", reflectionGroup.nBins);
208 
209     // Grid Sampling
210     if (!parseResult.hasMatchedOption("sampling")) {
211       targetGroup.sampling = properties.getDouble("sampling", targetGroup.sampling);
212     }
213     properties.setProperty("sampling", targetGroup.sampling);
214 
215     // Atomic Radius Buffer
216     if (!parseResult.hasMatchedOption("aRadBuffer")) {
217       targetGroup.aRadBuffer = properties.getDouble("scattering-buffer", targetGroup.aRadBuffer);
218     }
219     properties.setProperty("scattering-buffer", targetGroup.aRadBuffer);
220 
221     // RFreeFlag
222     if (!parseResult.hasMatchedOption("rFreeFlag")) {
223       reflectionGroup.rFreeFlag = properties.getInt("rfree-flag", reflectionGroup.rFreeFlag);
224     }
225     properties.setProperty("rfree-flag", reflectionGroup.rFreeFlag);
226 
227     // Spline Fit
228     if (!parseResult.hasMatchedOption("noSplineFit")) {
229       targetGroup.noSplineFit = properties.getBoolean("no-spline-fit", targetGroup.noSplineFit);
230     }
231     properties.setProperty("no-spline-fit", targetGroup.noSplineFit);
232 
233     // Use All Gaussians
234     if (!parseResult.hasMatchedOption("allGaussians")) {
235       targetGroup.useAllGaussians = !properties.getBoolean("use-3g", !targetGroup.useAllGaussians);
236     }
237     properties.setProperty("use-3g", !targetGroup.useAllGaussians);
238 
239     // X-ray Scale Tolerance
240     if (!parseResult.hasMatchedOption("xrayScaleTol")) {
241       targetGroup.xrayScaleTol = properties.getDouble("xray-scale-tol", targetGroup.xrayScaleTol);
242     }
243     properties.setProperty("xray-scale-tol", targetGroup.xrayScaleTol);
244 
245     // Sigma A Tolerance
246     if (!parseResult.hasMatchedOption("sigmaATol")) {
247       targetGroup.sigmaATol = properties.getDouble("sigmaa-tol", targetGroup.sigmaATol);
248     }
249     properties.setProperty("sigmaa-tol", targetGroup.sigmaATol);
250 
251     // Number of Residues per B-Factor
252     if (!parseResult.hasMatchedOption("nResidueBFactor")) {
253       bfactorGroup.nResidueBFactor = properties
254           .getInt("n-residue-bfactor", bfactorGroup.nResidueBFactor);
255     }
256     properties.setProperty("n-residue-bfactor", Integer.toString(bfactorGroup.nResidueBFactor));
257     if (bfactorGroup.nResidueBFactor > 0) {
258       properties.setProperty("residue-bfactor", "true");
259     }
260 
261     // Add AnisoU B-Factors to the Refinement
262     if (!parseResult.hasMatchedOption("anisoU")) {
263       bfactorGroup.anisoU = properties.getBoolean("add-anisou", bfactorGroup.anisoU);
264     }
265     properties.setProperty("add-anisou", bfactorGroup.anisoU);
266 
267     // Refine Molecular Occupancies
268     if (!parseResult.hasMatchedOption("refineMolOcc")) {
269       group.refineMolOcc = properties.getBoolean("refine-mol-occ", group.refineMolOcc);
270     }
271     properties.setProperty("refine-mol-occ", group.refineMolOcc);
272   }
273 
274   /**
275    * Process input from opened molecular assemblies to a DiffractionData.
276    *
277    * @param filenames   All filenames included in the diffraction data.
278    * @param assemblies  All molecular assemblies included in the diffraction data.
279    * @param properties  The properties to apply.
280    * @return An assembled DiffractionData
281    */
282   public DiffractionData getDiffractionData(List<String> filenames, MolecularAssembly[] assemblies,
283       CompositeConfiguration properties) {
284     // Set up diffraction data (can be multiple files)
285     List<DiffractionFile> diffractionFiles = processData(filenames, assemblies);
286     return new DiffractionData(assemblies, properties, solventModel,
287         diffractionFiles.toArray(new DiffractionFile[0]));
288   }
289 
290   /**
291    * Collection of X-ray Refinement Options.
292    */
293   private static class XrayOptionGroup {
294 
295     /**
296      * --rmo or --refineMolOcc
297      */
298     @Option(names = {"--rmo", "--refineMolOcc"}, paramLabel = "false", defaultValue = "false",
299         description = "Refine molecular occupancy.")
300     @FFXProperty(name = "refine-mol-occ", propertyGroup = StructuralRefinement, defaultValue = "false",
301         description = "Refine molecular occupancy.")
302     boolean refineMolOcc = false;
303 
304     /**
305      * -m or --mode sets the desired refinement mode [COORDINATES, BFACTORS,
306      * COORDINATES_AND_BFACTORS, OCCUPANCIES, BFACTORS_AND_OCCUPANCIES, COORDINATES_AND_OCCUPANCIES,
307      * COORDINATES_AND_BFACTORS_AND_OCCUPANCIES].
308      */
309     @Option(names = {"-m", "--mode"}, paramLabel = "coordinates", defaultValue = "coordinates",
310         description = "Refinement mode: coordinates, bfactors and/or occupancies.")
311     String modeString = "coordinates";
312   }
313 
314   /**
315    * Collection of X-ray Reflection Options.
316    */
317   private static class XrayReflectionsGroup {
318 
319     /**
320      * --nBins sets the number of reflection bins to use.
321      */
322     @Option(names = {"--nBins"}, paramLabel = "10", defaultValue = "10",
323         description = "The number of refection bins.")
324     @FFXProperty(name = "reflection-bins", propertyGroup = StructuralRefinement, defaultValue = "10",
325         description = "The number of refection bins.")
326     int nBins = 10;
327 
328     /**
329      * --FSigFCutoff
330      */
331     @Option(names = {"--FSigFCutoff"}, paramLabel = "-1.0", defaultValue = "-1.0",
332         description = "F / SigF cutoff (-1.0 is no cutoff).")
333     @FFXProperty(name = "f-sigf-cutoff", propertyGroup = StructuralRefinement, defaultValue = "-1.0",
334         description = "F / SigF cutoff (-1.0 is no cutoff)."
335     )
336     double fSigFCutoff = -1.0;
337 
338     /**
339      * -R or --rFreeFlag
340      */
341     @Option(names = {"-R", "--rFreeFlag"}, paramLabel = "-1", defaultValue = "-1",
342         description = "R-Free Flag value (-1 attempts to auto-determine from the data).")
343     @FFXProperty(name = "rfree-flag", propertyGroup = StructuralRefinement, defaultValue = "-1",
344         description = "R-Free Flag value (-1 attempts to auto-determine from the data)."
345     )
346     int rFreeFlag = -1;
347 
348     /**
349      * -X or --data Specify input data filename, weight applied to the data (wA) and if the data is
350      * from a neutron experiment.
351      */
352     @Option(names = {"-X", "--data"}, arity = "3",
353         description = "Specify input data filename, its weight (wA) and if its from a neutron experiment (e.g. -X filename 1.0 false).")
354     String[] data = null;
355   }
356 
357   /**
358    * Collection of B-Factor Options.
359    */
360   private static class BFactorGroup {
361 
362     /**
363      * -B or --bSimWeight
364      */
365     @Option(names = {"-B", "--bSimWeight"}, paramLabel = "1.0", defaultValue = "1.0",
366         description = "B-Factor similarity weight.")
367     @FFXProperty(name = "b-sim-weight", propertyGroup = StructuralRefinement, defaultValue = "1.0", description = """
368         B-factor harmonic restraint weight between bonded atoms.
369         This can be increased for low resolution structures to ~5-10.
370         """)
371     double bSimWeight = 1.0;
372 
373     /**
374      * --nResidueBFactor
375      */
376     @Option(names = {"--nResidueBFactor"}, paramLabel = "0", defaultValue = "0",
377         description = "Number of residues per B-factor. 0 uses atomic B-factors (default).")
378     @FFXProperty(name = "n-residue-bfactor", propertyGroup = StructuralRefinement, defaultValue = "0",
379         description = "Number of residues per B-factor. 0 uses atomic B-factors (default).")
380     int nResidueBFactor = 0;
381 
382     /**
383      * -u or --addAnisoU
384      */
385     @Option(names = {"-U", "--addAnisoU"}, paramLabel = "false", defaultValue = "false",
386         description = "Add Anisotropic B-Factors to refinement.")
387     @FFXProperty(name = "add-anisou", propertyGroup = StructuralRefinement, defaultValue = "false",
388         description = "Add Anisotropic B-Factors to refinement.")
389     boolean anisoU = false;
390 
391   }
392 
393   /**
394    * Collection of X-ray Scattering Options.
395    */
396   private static class ScatteringGroup {
397 
398     /**
399      * --aRadBuffer
400      */
401     @Option(names = {"--aRadBuffer"}, paramLabel = "0.75", defaultValue = "0.75",
402         description = "Scattering is evaluated within the atomic radius plus this buffer (Å).")
403     @FFXProperty(name = "scattering-buffer", propertyGroup = StructuralRefinement, defaultValue = "0.6",
404         description = "Scattering is evaluated within the atomic radius plus this buffer (Å).")
405     double aRadBuffer = 0.75;
406 
407     /**
408      * -G or --sampling
409      */
410     @Option(names = {"-G", "--sampling"}, paramLabel = "0.6", defaultValue = "0.6",
411         description = "The number of grid spaces per Angstrom for the scattering FFT grid.")
412     @FFXProperty(name = "sampling", propertyGroup = StructuralRefinement, defaultValue = "0.6",
413         description = "The number of grid spaces per Angstrom for the scattering FFT grid.")
414     double sampling = 0.6;
415 
416     /**
417      * --sf or --splineFit
418      */
419     @Option(names = {"--nsf", "--noSplineFit"}, paramLabel = "false", defaultValue = "false",
420         description = "Use a resolution dependent spline scale factor.")
421     @FFXProperty(name = "no-spline-fit", propertyGroup = StructuralRefinement, defaultValue = "false",
422         description = "Do not use a resolution dependent spline scale factor.")
423     boolean noSplineFit = false;
424 
425     /**
426      * -A or --allGaussians
427      */
428     @Option(names = {"-A", "--allGaussians"}, paramLabel = "false", defaultValue = "false",
429         description = "Use all defined Gaussians for atomic scattering density (the default is to use the top 3).")
430     @FFXProperty(name = "use-3g", propertyGroup = StructuralRefinement, defaultValue = "true",
431         description = "The three Gaussians with the largest amplitudes define the atomic scattering density.")
432     boolean useAllGaussians = false;
433 
434     /**
435      * --xrayScaleTol
436      */
437     @Option(names = {"--xrayScaleTol"}, paramLabel = "1.0e-4", defaultValue = "1.0e-4",
438         description = "X-ray scale optimization tolerance.")
439     @FFXProperty(name = "xray-scale-tol", propertyGroup = StructuralRefinement, defaultValue = "1.0e-4",
440         description = "X-ray scale optimization tolerance.")
441     double xrayScaleTol = 1.0e-4;
442 
443     /**
444      * --sigmaATol
445      */
446     @Option(names = {"--sigmaATol"}, paramLabel = "0.05", defaultValue = "0.05",
447         description = "Sigma A optimization tolerance.")
448     @FFXProperty(name = "sigmaa-tol", propertyGroup = StructuralRefinement, defaultValue = "0.05",
449         description = "Sigma A optimization tolerance.")
450     double sigmaATol = 0.05;
451   }
452 
453   /**
454    * Collection of Bulk Solvent Options.
455    */
456   private static class BulkSolventGroup {
457 
458     /**
459      * -S or --solventGridSearch
460      */
461     @Option(names = {"-S", "--solventGridSearch"}, paramLabel = "false", defaultValue = "false",
462         description = "Perform a grid search for optimal bulk solvent parameters.")
463     @FFXProperty(name = "solvent-grid-search", propertyGroup = StructuralRefinement, defaultValue = "false",
464         description = "Perform a grid search for optimal bulk solvent parameters.")
465     boolean gridSearch = false;
466 
467     /**
468      * -sol or --solvent Bulk solvent scattering model [Polynomial/Gaussian/Binary/None].
469      */
470     @Option(names = {"--sol", "--solvent"}, paramLabel = "POLYNOMIAL", defaultValue = "POLYNOMIAL",
471         description = "Bulk solvent scattering model [Polynomial/Gaussian/Binary/None]")
472     String solventString = "POLYNOMIAL";
473   }
474 
475   /**
476    * Process input from opened molecular assemblies and diffraction data to a RefinementEnergy.
477    *
478    * @param diffractionData Diffraction data.
479    * @return An assembled RefinementEnergy with X-ray energy.
480    */
481   public RefinementEnergy toXrayEnergy(DiffractionData diffractionData) {
482     diffractionData.scaleBulkFit();
483     diffractionData.printStats();
484     return new RefinementEnergy(diffractionData, refinementMode);
485   }
486 }