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.commands.test;
39  
40  import ffx.numerics.Potential;
41  import ffx.potential.MolecularAssembly;
42  import ffx.potential.cli.AtomSelectionOptions;
43  import ffx.potential.cli.PotentialCommand;
44  import ffx.utilities.FFXBinding;
45  import ffx.xray.DiffractionData;
46  import ffx.xray.refine.RefinedParameter;
47  import ffx.xray.refine.RefinementMode;
48  import ffx.xray.refine.RefinementModel;
49  import ffx.xray.XRayEnergy;
50  import ffx.xray.cli.XrayOptions;
51  import org.apache.commons.configuration2.CompositeConfiguration;
52  import picocli.CommandLine.Command;
53  import picocli.CommandLine.Mixin;
54  import picocli.CommandLine.Option;
55  import picocli.CommandLine.Parameters;
56  
57  import java.util.ArrayList;
58  import java.util.List;
59  
60  import static ffx.utilities.StringUtils.parseAtomRanges;
61  import static java.lang.String.format;
62  import static org.apache.commons.math3.util.FastMath.sqrt;
63  
64  /**
65   * The Gradient script evaluates the consistency of the energy and gradient.
66   * <br>
67   * Usage:
68   * <br>
69   * ffxc test.Gradient [options] &lt;filename&gt;
70   */
71  @Command(description = " Test the potential energy gradient.", name = "test.Gradient")
72  public class Gradient extends PotentialCommand {
73  
74    @Mixin
75    AtomSelectionOptions atomSelectionOptions;
76  
77    @Mixin
78    XrayOptions xrayOptions;
79  
80    /**
81     * -d or --dx Finite-difference step size.
82     */
83    @Option(names = {"-d", "--dx"}, defaultValue = "1.0e-5", paramLabel = "1.0e-5",
84        description = "Finite-difference step size.")
85    public double dx = 1.0e-5;
86  
87    /**
88     * --tol or --tolerance Gradient error tolerance.
89     */
90    @Option(names = {"--tol", "--tolerance"}, defaultValue = "1.0e-3",
91        paramLabel = "1.0e-3 kcal/mol/param", description = "Gradient error tolerance.")
92    public double tolerance = 1.0e-3;
93  
94    /**
95     * --params or --gradientAtoms Ranges of degrees of freedom to test [ALL, NONE, Range(s): 1-3,6-N].
96     */
97    @Option(names = {"--params", "--gradientParams"}, paramLabel = "ALL", defaultValue = "ALL",
98        description = "Ranges of degrees of freedom to test [ALL, NONE, Range(s): 1-3,6-N].")
99    public String gradientParams = "ALL";
100 
101 
102   /**
103    * One or more filenames.
104    */
105   @Parameters(arity = "1..*", paramLabel = "files", description = "PDB and Diffraction input files.")
106   private List<String> filenames;
107 
108   private MolecularAssembly[] molecularAssemblies;
109   private DiffractionData diffractionData;
110   private XRayEnergy xrayEnergy;
111   public int nFailures = 0;
112 
113   /**
114    * Gradient constructor.
115    */
116   public Gradient() {
117     super();
118   }
119 
120   /**
121    * Gradient constructor.
122    *
123    * @param binding The Binding to use.
124    */
125   public Gradient(FFXBinding binding) {
126     super(binding);
127   }
128 
129   /**
130    * Gradient constructor that sets the command line arguments.
131    *
132    * @param args Command line arguments.
133    */
134   public Gradient(String[] args) {
135     super(args);
136   }
137 
138   /**
139    * Execute the script.
140    */
141   @Override
142   public Gradient run() {
143 
144     if (!init()) {
145       return this;
146     }
147 
148     xrayOptions.init();
149 
150     String filename;
151     if (filenames != null && !filenames.isEmpty()) {
152       // Each alternate conformer is returned in a separate MolecularAssembly.
153       molecularAssemblies = potentialFunctions.openAll(filenames.getFirst());
154       activeAssembly = molecularAssemblies[0];
155     } else if (activeAssembly == null) {
156       logger.info(helpString());
157       return this;
158     } else {
159       molecularAssemblies = new MolecularAssembly[]{activeAssembly};
160     }
161 
162     // Update the active filename
163     filename = activeAssembly.getFile().getAbsolutePath();
164 
165     // Set active atoms for each assembly.
166     for (MolecularAssembly assembly : molecularAssemblies) {
167       atomSelectionOptions.setActiveAtoms(assembly);
168     }
169 
170     // Combine script flags (in parseResult) with properties.
171     CompositeConfiguration properties = activeAssembly.getProperties();
172     xrayOptions.setProperties(parseResult, properties);
173 
174     // Set up diffraction data (can be multiple files)
175     diffractionData = xrayOptions.getDiffractionData(filenames, molecularAssemblies, properties);
176     diffractionData.scaleBulkFit();
177     diffractionData.printStats();
178     xrayEnergy = new XRayEnergy(diffractionData);
179 
180     RefinementModel refinementModel = diffractionData.getRefinementModel();
181     RefinementMode refinementMode = refinementModel.getRefinementMode();
182 
183     logger.info("\n Testing the gradient of " + filename);
184     logger.info(refinementMode.toString());
185     logger.info(refinementModel.toString());
186 
187     int n = refinementModel.getNumParameters();
188     double[] x = new double[n];
189     double[] g = new double[n];
190     // Collect the analytic gradient.
191     refinementModel.getParameters(x);
192     xrayEnergy.energyAndGradient(x, g);
193 
194     List<RefinedParameter> refinedParameters = refinementModel.getRefinedParameters();
195     int numParameters = refinedParameters.size();
196 
197     List<Integer> parametersToTest;
198     if (gradientParams.equalsIgnoreCase("NONE")) {
199       logger.info(" The gradient of no parameters will be evaluated.");
200       return this;
201     } else if (gradientParams.equalsIgnoreCase("ALL")) {
202       logger.info(" Checking the gradient for all parameters.\n");
203       parametersToTest = new ArrayList<>();
204       for (int i = 0; i < numParameters; i++) {
205         parametersToTest.add(i);
206       }
207     } else {
208       parametersToTest = parseAtomRanges(" Parameters", gradientParams, numParameters);
209       logger.info(" Checking the gradient for parameters in the range: " + gradientParams + "\n");
210     }
211 
212     for (Integer parameter : parametersToTest) {
213       RefinedParameter refinedParameter = refinedParameters.get(parameter);
214       int index = refinedParameter.getIndex();
215       int numParams = refinedParameter.getNumberOfParameters();
216       double[] grad = new double[numParams];
217       double error = 0.0;
218       // Collect the gradient
219       for (int i = 0; i < numParams; i++) {
220         double orig = x[index + i];
221         x[index + i] = orig + dx;
222         double eplus = xrayEnergy.energy(x);
223         x[index + i] = orig - dx;
224         double eminus = xrayEnergy.energy(x);
225         x[index + i] = orig;
226         grad[i] = (eplus - eminus) / (2.0 * dx);
227         double diff = grad[i] - g[index + i];
228         error += diff * diff;
229       }
230       error = sqrt(error);
231       if (numParams == 1) {
232         logger.info(format("\n Refinement parameter %d with index %d", parameter + 1, index));
233       } else {
234         logger.info(format("\n Refinement parameter %d with indices %d - %s",
235             parameter + 1, index, index + numParams - 1));
236       }
237       logger.info(format(" %s", refinedParameter));
238       if (error > tolerance) {
239         logger.info(format("  Failed: %10.6f", error));
240       } else {
241         logger.info(format("  Passed: %10.6f", error));
242       }
243       StringBuilder numeric = new StringBuilder("  Numeric:  ");
244       StringBuilder analytic = new StringBuilder("  Analytic: ");
245       for (int i = 0; i < numParams; i++) {
246         numeric.append(format(" %10.6f", grad[i]));
247         analytic.append(format(" %10.6f", g[index + i]));
248       }
249       logger.info(numeric.toString());
250       logger.info(analytic.toString());
251     }
252     return this;
253   }
254 
255   @Override
256   public List<Potential> getPotentials() {
257     List<Potential> potentials = getPotentialsFromAssemblies(molecularAssemblies);
258     if (xrayEnergy != null) {
259       potentials.add(xrayEnergy);
260     }
261     return potentials;
262   }
263 
264 }