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.scatter;
39  
40  import ffx.crystal.HKL;
41  import ffx.potential.bonded.Atom;
42  import ffx.xray.refine.RefinementMode;
43  
44  import java.util.logging.Level;
45  import java.util.logging.Logger;
46  
47  import static ffx.numerics.math.DoubleMath.length2;
48  import static ffx.numerics.math.DoubleMath.sub;
49  import static ffx.numerics.math.MatrixMath.mat3Determinant;
50  import static ffx.numerics.math.MatrixMath.mat3Inverse;
51  import static ffx.numerics.math.MatrixMath.mat3Mat3Multiply;
52  import static ffx.numerics.math.MatrixMath.vec3Mat3;
53  import static ffx.numerics.math.ScalarMath.b2u;
54  import static ffx.numerics.math.ScalarMath.quadForm;
55  import static ffx.numerics.math.ScalarMath.u2b;
56  import static ffx.xray.scatter.XRayScatteringParameters.getFormFactor;
57  import static java.lang.String.format;
58  import static org.apache.commons.math3.util.FastMath.exp;
59  import static org.apache.commons.math3.util.FastMath.pow;
60  import static org.apache.commons.math3.util.FastMath.sqrt;
61  
62  /**
63   * This implementation uses either coefficients from Su and Coppens or 3 coefficient parameters derived
64   * from CCTBX.
65   *
66   * @author Timothy D. Fenn<br>
67   * @see <a href="http://www.iucr.org/resources/commissions/crystallographic-computing/newsletters/3"
68   * target="_blank"> R. W. Grosse-Kunstleve, N. K. Sauter and P. D. Adams. Newsletter of the IUCr
69   * Commission on Crystallographic Computing. (2004). 3, 22-31.</a>
70   * @see <a href="http://dx.doi.org/10.1107/S0907444909022707" target="_blank"> M. J. Schnieders, T.
71   * D. Fenn, V. S. Pande and A. T. Brunger, Acta Cryst. (2009). D65 952-965.</a>
72   * @since 1.0
73   */
74  public final class XRayFormFactor implements FormFactor {
75  
76      private static final Logger logger = Logger.getLogger(XRayFormFactor.class.getName());
77  
78      private final Atom atom;
79      private final double[] xyz = new double[3];
80      private final double[] dxyz = new double[3];
81      private final double[] resv = new double[3];
82      private final double[] a = new double[6];
83      private final double[] b = new double[6];
84      private final double[] ainv = new double[6];
85      private final double[] binv = new double[6];
86      private final double[] gradu = new double[6];
87      private final double[][][] u = new double[6][3][3];
88      private final double[][][] uInv = new double[6][3][3];
89      private final double[][] jmat = new double[3][3];
90      private final int nGaussians;
91      private boolean hasAnisou;
92      private double[] anisou = null;
93      private double uAdd;
94      private double occupancy;
95  
96      /**
97       * Constructor for XRayFormFactor.
98       *
99       * @param atom  a {@link ffx.potential.bonded.Atom} object.
100      * @param use3G a boolean.
101      * @param badd  a double.
102      */
103     public XRayFormFactor(Atom atom, boolean use3G, double badd) {
104         this(atom, use3G, badd, atom.getXYZ(null));
105     }
106 
107     /**
108      * Constructor for XRayFormFactor.
109      *
110      * @param atom  a {@link ffx.potential.bonded.Atom} object.
111      * @param use3G a boolean.
112      * @param badd  a double.
113      * @param xyz   an array of double.
114      */
115     public XRayFormFactor(Atom atom, boolean use3G, double badd, double[] xyz) {
116         this.atom = atom;
117         this.uAdd = b2u(badd);
118 
119         // Assign scattering factors.
120         int charge = 0;
121         if (atom.getMultipoleType() != null) {
122             charge = (int) atom.getMultipoleType().getCharge();
123         }
124 
125         XRayScatteringParameters parameters = getFormFactor(atom.getAtomicNumber(), charge, use3G);
126         double[][] formFactor = parameters.formFactor();
127 
128         int i;
129         for (i = 0; i < formFactor[1].length; i++) {
130             if (formFactor[1][i] < 0.01) {
131                 break;
132             }
133             a[i] = formFactor[1][i];
134             b[i] = formFactor[2][i];
135         }
136         nGaussians = i;
137         assert (nGaussians > 0);
138         occupancy = atom.getOccupancy();
139 
140         if (occupancy <= 0.0 && logger.isLoggable(Level.FINE)) {
141             logger.log(Level.FINE, " Zero occupancy for atom: {0}", atom.toString());
142         }
143 
144         update(xyz, uAdd);
145     }
146 
147     /**
148      * f
149      *
150      * @param hkl a {@link ffx.crystal.HKL} object.
151      * @return a double.
152      */
153     public double f(HKL hkl) {
154         return fN(hkl, nGaussians);
155     }
156 
157     /**
158      * f_n
159      *
160      * @param hkl        a {@link ffx.crystal.HKL} object.
161      * @param nGaussians a int.
162      * @return a double.
163      */
164     public double fN(HKL hkl, int nGaussians) {
165         double sum = 0.0;
166 
167         for (int i = 0; i < nGaussians; i++) {
168             sum += a[i] * exp(-twoPI2 * hkl.quadForm(u[i]));
169         }
170         return occupancy * sum;
171     }
172 
173     /**
174      * {@inheritDoc}
175      */
176     @Override
177     public double rho(double f, double lambda, double[] xyz) {
178         return rhoN(f, lambda, xyz, nGaussians);
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     public void rhoGrad(double[] xyz, double dfc, RefinementMode refinementMode) {
186         rhoGradN(xyz, nGaussians, dfc, refinementMode);
187     }
188 
189     /**
190      * {@inheritDoc}
191      */
192     @Override
193     public void update(double[] xyz) {
194         update(xyz, u2b(uAdd));
195     }
196 
197     /**
198      * {@inheritDoc}
199      */
200     @Override
201     public void update(double[] xyz, double bAdd) {
202         this.xyz[0] = xyz[0];
203         this.xyz[1] = xyz[1];
204         this.xyz[2] = xyz[2];
205         uAdd = b2u(bAdd);
206         occupancy = atom.getOccupancy();
207         double bIso = atom.getTempFactor();
208 
209         // check occ is valid
210         if (occupancy < 0.0) {
211             logger.warning(format(" %s negative occupancy reset to 0.0.", atom));
212             occupancy = 0.0;
213             atom.setOccupancy(0.0);
214         }
215 
216         // check if anisou changed
217         if (atom.getAnisou(null) == null) {
218             if (anisou == null) {
219                 anisou = new double[6];
220             }
221             hasAnisou = false;
222         } else {
223             hasAnisou = true;
224         }
225 
226         if (hasAnisou) {
227             // Check that the ANISOU is valid.
228             anisou = atom.getAnisou(null);
229             double det = mat3Determinant(anisou);
230 
231             if (det <= 1e-14) {
232                 String message = format(" %s non-positive definite ANISOU\n  Reset to isotropic B: %6.2f", atom, bIso);
233                 logger.warning(message);
234 
235                 double uIso = b2u(bIso);
236                 anisou[0] = uIso;
237                 anisou[1] = uIso;
238                 anisou[2] = uIso;
239                 anisou[3] = 0.0;
240                 anisou[4] = 0.0;
241                 anisou[5] = 0.0;
242                 atom.setAnisou(anisou);
243             }
244         } else {
245             if (bIso < 0.0) {
246                 StringBuilder sb = new StringBuilder();
247                 sb.append(" Negative B factor for atom: ").append(atom);
248                 sb.append("\n Resetting B to 5.0\n");
249                 logger.warning(sb.toString());
250                 bIso = 5.0;
251                 atom.setTempFactor(5.0);
252             }
253             double uIso = b2u(bIso);
254             anisou[0] = uIso;
255             anisou[1] = uIso;
256             anisou[2] = uIso;
257             anisou[3] = 0.0;
258             anisou[4] = 0.0;
259             anisou[5] = 0.0;
260         }
261 
262         for (int i = 0; i < nGaussians; i++) {
263             double uIso = b2u(b[i]);
264             u[i][0][0] = anisou[0] + uIso + uAdd;
265             u[i][1][1] = anisou[1] + uIso + uAdd;
266             u[i][2][2] = anisou[2] + uIso + uAdd;
267             u[i][0][1] = anisou[3];
268             u[i][1][0] = anisou[3];
269             u[i][0][2] = anisou[4];
270             u[i][2][0] = anisou[4];
271             u[i][1][2] = anisou[5];
272             u[i][2][1] = anisou[5];
273             mat3Inverse(u[i], uInv[i]);
274             double det = mat3Determinant(u[i]);
275             ainv[i] = a[i] / sqrt(det);
276             det = mat3Determinant(uInv[i]);
277             binv[i] = pow(det, oneThird);
278         }
279     }
280 
281     /**
282      * rho_n
283      *
284      * @param f          a double.
285      * @param lambda     a double.
286      * @param xyz        an array of double.
287      * @param nGaussians a int.
288      * @return a double.
289      */
290     private double rhoN(double f, double lambda, double[] xyz, int nGaussians) {
291         assert (nGaussians > 0 && nGaussians <= this.nGaussians);
292         sub(this.xyz, xyz, xyz);
293 
294         // Compare r^2 to form factor width^2 to avoid expensive sqrt.
295         if (length2(xyz) > atom.getFormFactorWidth2()) {
296             return f;
297         }
298 
299         double sum = 0.0;
300         for (int i = 0; i < nGaussians; i++) {
301             sum += ainv[i] * exp(-0.5 * quadForm(xyz, uInv[i]));
302         }
303         return f + (lambda * occupancy * inverseTwoPI32 * sum);
304     }
305 
306     /**
307      * rho_grad_n
308      *
309      * @param xyz            an array of double.
310      * @param nGaussians     a int.
311      * @param dfc            a double.
312      * @param refinementMode a {@link RefinementMode} object.
313      */
314     private void rhoGradN(double[] xyz, int nGaussians, double dfc, RefinementMode refinementMode) {
315         assert (nGaussians > 0 && nGaussians <= this.nGaussians);
316         sub(this.xyz, xyz, dxyz);
317         double r2 = length2(dxyz);
318 
319         // Compare r^2 to form factor width^2 to avoid expensive sqrt.
320         if (r2 > atom.getFormFactorWidth2()) {
321             return;
322         }
323 
324         boolean refineXYZ = refinementMode.includesCoordinates();
325         boolean refineBFactor = refinementMode.includesBFactors();
326         boolean refineOccupancy = refinementMode.includesOccupancies();
327 
328         for (int i = 0; i < nGaussians; i++) {
329             double aex = ainv[i] * exp(-0.5 * quadForm(dxyz, uInv[i]));
330 
331             if (refineXYZ) {
332                 vec3Mat3(dxyz, uInv[i], resv);
333                 double scale = aex * dfc * occupancy * inverseTwoPI32;
334                 double dex = -scale * resv[0];
335                 double dey = -scale * resv[1];
336                 double dez = -scale * resv[2];
337                 atom.addToXYZGradient(dex, dey, dez);
338             }
339 
340             if (refineOccupancy) {
341                 atom.addToOccupancyGradient(dfc * inverseTwoPI32 * aex);
342             }
343 
344             if (refineBFactor) {
345                 double scale = 0.5 * aex * dfc * occupancy * inverseTwoPI32;
346                 double deb = scale * (r2 * binv[i] * binv[i] - 3.0 * binv[i]);
347                 atom.addToTempFactorGradient(b2u(deb));
348                 if (hasAnisou) {
349                     // U11
350                     mat3Mat3Multiply(uInv[i], dUdU11, jmat);
351                     mat3Mat3Multiply(jmat, uInv[i], jmat);
352                     gradu[0] = scale * (quadForm(dxyz, jmat) - uInv[i][0][0]);
353                     // U22
354                     mat3Mat3Multiply(uInv[i], dUdU22, jmat);
355                     mat3Mat3Multiply(jmat, uInv[i], jmat);
356                     gradu[1] = scale * (quadForm(dxyz, jmat) - uInv[i][1][1]);
357                     // U33
358                     mat3Mat3Multiply(uInv[i], dUdU33, jmat);
359                     mat3Mat3Multiply(jmat, uInv[i], jmat);
360                     gradu[2] = scale * (quadForm(dxyz, jmat) - uInv[i][2][2]);
361                     // U12
362                     mat3Mat3Multiply(uInv[i], dUdU12, jmat);
363                     mat3Mat3Multiply(jmat, uInv[i], jmat);
364                     gradu[3] = scale * (quadForm(dxyz, jmat) - uInv[i][0][1] * 2.0);
365                     // U13
366                     mat3Mat3Multiply(uInv[i], dUdU13, jmat);
367                     mat3Mat3Multiply(jmat, uInv[i], jmat);
368                     gradu[4] = scale * (quadForm(dxyz, jmat) - uInv[i][0][2] * 2.0);
369                     // U23
370                     mat3Mat3Multiply(uInv[i], dUdU23, jmat);
371                     mat3Mat3Multiply(jmat, uInv[i], jmat);
372                     gradu[5] = scale * (quadForm(dxyz, jmat) - uInv[i][1][2] * 2.0);
373                     // Store the anisotropic B-factor gradient.
374                     atom.addToAnisouGradient(gradu);
375                 }
376             }
377         }
378     }
379 }