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;
39  
40  import ffx.crystal.Crystal;
41  import ffx.crystal.HKL;
42  import ffx.crystal.ReflectionList;
43  import ffx.crystal.ReflectionSpline;
44  import ffx.numerics.OptimizationInterface;
45  import ffx.numerics.math.ComplexNumber;
46  
47  import java.util.logging.Logger;
48  
49  import static java.lang.Double.isNaN;
50  import static java.lang.String.format;
51  import static java.util.Arrays.fill;
52  import static org.apache.commons.math3.util.FastMath.abs;
53  import static org.apache.commons.math3.util.FastMath.pow;
54  import static org.apache.commons.math3.util.FastMath.sqrt;
55  
56  /**
57   * Fit structure factors using spline coefficients
58   *
59   * @author Timothy D. Fenn<br>
60   * @see <a href="http://dx.doi.org/10.1107/S0021889802013420" target="_blank"> K. Cowtan, J. Appl.
61   * Cryst. (2002). 35, 655-663</a>
62   * @since 1.0
63   */
64  public class SplineEnergy implements OptimizationInterface {
65  
66    private static final Logger logger = Logger.getLogger(SplineEnergy.class.getName());
67    private final ReflectionList reflectionList;
68    private final ReflectionSpline spline;
69    private final int nParams;
70    private final SplineType type;
71    private final Crystal crystal;
72    private final DiffractionRefinementData refinementData;
73    private final double[][] fc;
74    private final double[][] fo;
75    private final ComplexNumber fct = new ComplexNumber();
76    private double[] optimizationScaling = null;
77    private double totalEnergy;
78  
79    /**
80     * Constructor for SplineEnergy.
81     *
82     * @param reflectionList a {@link ffx.crystal.ReflectionList} object.
83     * @param refinementData a {@link ffx.xray.DiffractionRefinementData} object.
84     * @param nParams        an int.
85     * @param type           an int.
86     */
87    SplineEnergy(
88        ReflectionList reflectionList,
89        DiffractionRefinementData refinementData,
90        int nParams,
91        SplineType type) {
92      this.reflectionList = reflectionList;
93      this.crystal = reflectionList.crystal;
94      this.refinementData = refinementData;
95      this.type = type;
96      this.fc = refinementData.fc;
97      this.fo = refinementData.fSigF;
98  
99      // initialize params
100     this.spline = new ReflectionSpline(reflectionList, nParams);
101     this.nParams = nParams;
102   }
103 
104   /**
105    * {@inheritDoc}
106    */
107   @Override
108   public boolean destroy() {
109     // Should be handled upstream.
110     return true;
111   }
112 
113   /**
114    * {@inheritDoc}
115    */
116   @Override
117   public double energy(double[] x) {
118     unscaleCoordinates(x);
119     double sum = target(x, null, false, false);
120     scaleCoordinates(x);
121     return sum;
122   }
123 
124   /**
125    * {@inheritDoc}
126    */
127   @Override
128   public double energyAndGradient(double[] x, double[] g) {
129     unscaleCoordinates(x);
130     double sum = target(x, g, true, false);
131     scaleCoordinatesAndGradient(x, g);
132     return sum;
133   }
134 
135   /**
136    * {@inheritDoc}
137    */
138   @Override
139   public double[] getCoordinates(double[] parameters) {
140     throw new UnsupportedOperationException("Not supported yet.");
141   }
142 
143   /**
144    * {@inheritDoc}
145    */
146   @Override
147   public void setCoordinates(double[] parameters) {
148     throw new UnsupportedOperationException("Not supported yet.");
149   }
150 
151   /**
152    * {@inheritDoc}
153    */
154   @Override
155   public int getNumberOfVariables() {
156     throw new UnsupportedOperationException("Not supported yet.");
157   }
158 
159   /**
160    * {@inheritDoc}
161    */
162   @Override
163   public double[] getScaling() {
164     return optimizationScaling;
165   }
166 
167   /**
168    * {@inheritDoc}
169    */
170   @Override
171   public void setScaling(double[] scaling) {
172     if (scaling != null && scaling.length == nParams) {
173       optimizationScaling = scaling;
174     } else {
175       optimizationScaling = null;
176     }
177   }
178 
179   /**
180    * {@inheritDoc}
181    */
182   @Override
183   public double getTotalEnergy() {
184     return totalEnergy;
185   }
186 
187   /**
188    * target
189    *
190    * @param x        an array of double.
191    * @param g        an array of double.
192    * @param gradient a boolean.
193    * @param print    a boolean.
194    * @return a double.
195    */
196   public double target(double[] x, double[] g, boolean gradient, boolean print) {
197 
198     double r = 0.0;
199     double rf = 0.0;
200     double rfree = 0.0;
201     double rfreef = 0.0;
202     double sum = 0.0;
203     double sumfo = 1.0;
204 
205     // Zero out the gradient.
206     if (gradient) {
207       fill(g, 0.0);
208     }
209 
210     for (HKL ih : reflectionList.hklList) {
211       int i = ih.getIndex();
212       if (isNaN(fc[i][0]) || isNaN(fo[i][0]) || fo[i][1] <= 0.0) {
213         continue;
214       }
215 
216       if (type == SplineType.FOTOESQ && fo[i][0] <= 0.0) {
217         continue;
218       }
219 
220       double eps = ih.getEpsilon();
221       double s = crystal.invressq(ih);
222       // Spline setup
223       double fh = spline.f(s, x);
224       refinementData.getFcTotIP(i, fct);
225 
226       double d2 = 0.0;
227       double dr = 0.0;
228       double w = 0.0;
229       switch (type) {
230         case SplineType.FOFC:
231           w = 1.0;
232           double f1 = refinementData.getF(i);
233           double f2 = fct.abs();
234           double d = f1 - fh * f2;
235           d2 = d * d;
236           dr = -2.0 * f2 * d;
237           sumfo += f1 * f1;
238           break;
239         case SplineType.F1F2:
240           w = 2.0 / ih.epsilonc();
241           double ieps = 1.0 / eps;
242           f1 = pow(fct.abs(), 2.0) * ieps;
243           f2 = pow(refinementData.getF(i), 2) * ieps;
244           d = fh * f1 - f2;
245           d2 = d * d / f1;
246           dr = 2.0 * d;
247           break;
248         case SplineType.FCTOESQ:
249           w = 2.0 / ih.epsilonc();
250           f1 = pow(fct.abs() / sqrt(eps), 2);
251           d = f1 * fh - 1.0;
252           d2 = d * d / f1;
253           dr = 2.0 * d;
254           break;
255         case SplineType.FOTOESQ:
256           w = 2.0 / ih.epsilonc();
257           f1 = pow(refinementData.getF(i) / sqrt(eps), 2);
258           d = f1 * fh - 1.0;
259           d2 = d * d / f1;
260           dr = 2.0 * d;
261           break;
262       }
263 
264       sum += w * d2;
265 
266       double afo = abs(fo[i][0]);
267       double afh = abs(fh * fct.abs());
268       if (refinementData.isFreeR(i)) {
269         rfree += abs(afo - afh);
270         rfreef += afo;
271       } else {
272         r += abs(afo - afh);
273         rf += afo;
274       }
275 
276       if (gradient) {
277         int i0 = spline.i0();
278         int i1 = spline.i1();
279         int i2 = spline.i2();
280         double g0 = spline.dfi0();
281         double g1 = spline.dfi1();
282         double g2 = spline.dfi2();
283 
284         g[i0] += w * dr * g0;
285         g[i1] += w * dr * g1;
286         g[i2] += w * dr * g2;
287       }
288     }
289 
290     if (gradient && type == SplineType.FOFC) {
291       double isumfo = 1.0 / sumfo;
292       for (int i = 0; i < g.length; i++) {
293         g[i] *= isumfo;
294       }
295     }
296 
297     if (print) {
298       StringBuilder sb = new StringBuilder("\n");
299       sb.append(" Computed Potential Energy\n");
300       sb.append(format("   residual:  %8.3f\n", sum / sumfo));
301       if (type == SplineType.FOFC || type == SplineType.F1F2) {
302         sb.append(
303             format(
304                 "   R:  %8.3f  Rfree:  %8.3f\n", (r / rf) * 100.0, (rfree / rfreef) * 100.0));
305       }
306       sb.append("x: ");
307       for (double x1 : x) {
308         sb.append(format("%8g ", x1));
309       }
310       sb.append("\ng: ");
311       for (double v : g) {
312         sb.append(format("%8g ", v));
313       }
314       sb.append("\n");
315       logger.info(sb.toString());
316     }
317 
318     totalEnergy = sum / sumfo;
319     return sum / sumfo;
320   }
321 
322   /**
323    * The SplineType enum represents types of splines that can be used in X-ray
324    * diffraction energy calculations within the context of the SplineEnergy class.
325    * Each constant corresponds to a specific mathematical or computational
326    * formulation of a spline used for handling structure factor data.
327    * <p>
328    * FOFC     Spline Fo to Fc
329    * F1F2     Spline F1 to F2
330    * FCTOESQ  Spline Fc to ESQ
331    * FOTOESQ  Spline Fo to ESQ
332    */
333   public enum SplineType {
334     FOFC,     // Spline Fo to Fc
335     F1F2,     // F1F2: Spline F1 to F2
336     FCTOESQ,  // Spline Fc to ESQ
337     FOTOESQ;   // Spline Fo to ESQ
338 
339     public String toString() {
340       return switch (this) {
341         case FOFC -> "Fo to Fc";
342         case F1F2 -> "F1 to F2";
343         case FCTOESQ -> "Fc to Esq";
344         case FOTOESQ -> "Fo to Esq";
345       };
346     }
347   }
348 }