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