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.Logger;
45  
46  import static ffx.numerics.math.DoubleMath.length2;
47  import static ffx.numerics.math.DoubleMath.sub;
48  import static ffx.numerics.math.MatrixMath.mat3Determinant;
49  import static ffx.numerics.math.MatrixMath.mat3Inverse;
50  import static ffx.numerics.math.MatrixMath.mat3Mat3Multiply;
51  import static ffx.numerics.math.MatrixMath.vec3Mat3;
52  import static ffx.numerics.math.ScalarMath.b2u;
53  import static ffx.numerics.math.ScalarMath.quadForm;
54  import static ffx.numerics.math.ScalarMath.u2b;
55  import static java.lang.String.format;
56  import static java.lang.System.arraycopy;
57  import static org.apache.commons.math3.util.FastMath.exp;
58  import static org.apache.commons.math3.util.FastMath.pow;
59  import static org.apache.commons.math3.util.FastMath.sqrt;
60  
61  /**
62   * Neutron scattering form factor.
63   *
64   * @author Timothy D. Fenn
65   * @since 1.0
66   */
67  public final class NeutronFormFactor implements FormFactor {
68  
69      private static final Logger logger = Logger.getLogger(NeutronFormFactor.class.getName());
70  
71      /**
72       * The scattering atom.
73       */
74      private final Atom atom;
75      /**
76       * Current atomic coordinates of this atom.
77       */
78      private final double[] xyz = new double[3];
79      /**
80       * The location, relative to the atomic center, where the neutron form factor is being evaluated.
81       */
82      private final double[] dxyz = new double[3];
83      /**
84       * Neutron scattering length.
85       */
86      private final double[] a = new double[2];
87      /**
88       * Real portion of the neutron scattering length normalized by the determinant of the U matrix.
89       */
90      private double ainv;
91      /**
92       * Anisotropic U matrix with B_add included.
93       */
94      private final double[][] u = new double[3][3];
95      /**
96       * Inverse of U.
97       */
98      private final double[][] uInv = new double[3][3];
99      /**
100      * Determinant of the inverse U matrix to the 1/3 power.
101      */
102     private double binv;
103     /**
104      * Work array for atomic coordinate gradient.
105      */
106     private final double[] resv = new double[3];
107     /**
108      * If true, anisotropic B-factor is present.
109      */
110     private boolean hasAnisou;
111     /**
112      * Anisotropic B-factor.
113      */
114     private double[] uaniso = null;
115     /**
116      * Work array for anisotropic B-factor gradient.
117      */
118     private final double[] dU = new double[6];
119     /**
120      * Chain-rule term for anisotropic B-factor gradient.
121      */
122     private final double[][] jmat = new double[3][3];
123     /**
124      * U version of B_add.
125      */
126     private double uadd;
127     /**
128      * Occupancy of this atom.
129      */
130     private double occ;
131 
132     /**
133      * Constructor for NeutronFormFactor.
134      *
135      * @param atom a {@link ffx.potential.bonded.Atom} object.
136      * @param badd a double.
137      */
138     public NeutronFormFactor(Atom atom, double badd) {
139         this(atom, badd, atom.getXYZ(null));
140     }
141 
142     /**
143      * Constructor for NeutronFormFactor.
144      *
145      * @param atom a {@link ffx.potential.bonded.Atom} object.
146      * @param badd a double.
147      * @param xyz  an array of double.
148      */
149     public NeutronFormFactor(Atom atom, double badd, double[] xyz) {
150         this.atom = atom;
151         this.uadd = b2u(badd);
152 
153         String key;
154         if (atom.getAtomicNumber() == 1) {
155             if (atom.isDeuterium()) {
156                 key = atom.getAtomicNumber() + "_2";
157             } else {
158                 key = atom.getAtomicNumber() + "_1";
159             }
160         } else {
161             key = "" + atom.getAtomicNumber();
162         }
163 
164         NeutronScatteringParameters parameters = NeutronScatteringParameters.getFormFactor(key);
165         double[] ffactor = parameters.formFactor();
166         arraycopy(ffactor, 0, a, 0, ffactor.length);
167         if (a[1] != 0.0) {
168             logger.severe(" Complex neutron form factor method not supported");
169         }
170 
171         occ = atom.getOccupancy();
172 
173         if (occ <= 0.0) {
174             StringBuilder sb = new StringBuilder();
175             sb.append(" Zero occupancy: ").append(atom);
176             logger.fine(sb.toString());
177         }
178 
179         update(xyz, uadd);
180     }
181 
182     /**
183      * Returns the string representation of the NeutronFormFactor object.
184      * The string includes details about the associated atom and its neutron
185      * scattering magnitude.
186      *
187      * @return A formatted string containing the atom's information and its
188      * neutron scattering magnitude.
189      */
190     @Override
191     public String toString() {
192         return atom.toString() + format(" Neutron mag: %9.6f", a[0]);
193     }
194 
195     /**
196      * f
197      *
198      * @param hkl a {@link ffx.crystal.HKL} object.
199      * @return a double.
200      */
201     public double f(HKL hkl) {
202         double sum = a[0] * exp(-twoPI2 * hkl.quadForm(u));
203         return occ * sum;
204     }
205 
206     /**
207      * {@inheritDoc}
208      */
209     @Override
210     public double rho(double f, double lambda, double[] xyz) {
211         // Compute the vector to the location of interest.
212         sub(this.xyz, xyz, xyz);
213 
214         // Check if this is beyond the form factor cutoff.
215         if (length2(xyz) > atom.getFormFactorWidth2()) {
216             return f;
217         }
218 
219         double sum = ainv * exp(-0.5 * quadForm(xyz, uInv));
220         return f + (lambda * occ * inverseTwoPI32 * sum);
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     public void rhoGrad(double[] xyz, double dfc, RefinementMode refinementMode) {
228         // Compute the vector to the location of interest.
229         sub(this.xyz, xyz, dxyz);
230 
231         // Check if this is beyond the form factor cutoff.
232         double r2 = length2(dxyz);
233         if (r2 > atom.getFormFactorWidth2()) {
234             return;
235         }
236 
237         double aex = ainv * exp(-0.5 * quadForm(dxyz, uInv));
238 
239         if (refinementMode.includesCoordinates()) {
240             vec3Mat3(dxyz, uInv, resv);
241             double scale = aex * dfc * occ * inverseTwoPI32;
242             double dex = -scale * resv[0];
243             double dey = -scale * resv[1];
244             double dez = -scale * resv[2];
245             atom.addToXYZGradient(dex, dey, dez);
246         }
247 
248         if (refinementMode.includesOccupancies()) {
249             atom.addToOccupancyGradient(dfc * inverseTwoPI32 * aex);
250         }
251 
252         if (refinementMode.includesBFactors()) {
253             double scale = 0.5 * aex * dfc * occ * inverseTwoPI32;
254             double deb = scale * (r2 * binv * binv - 3.0 * binv);
255             atom.addToTempFactorGradient(b2u(deb));
256             if (hasAnisou) {
257                 // U11
258                 mat3Mat3Multiply(uInv, dUdU11, jmat);
259                 mat3Mat3Multiply(jmat, uInv, jmat);
260                 dU[0] = scale * (quadForm(dxyz, jmat) - uInv[0][0]);
261                 // U22
262                 mat3Mat3Multiply(uInv, dUdU22, jmat);
263                 mat3Mat3Multiply(jmat, uInv, jmat);
264                 dU[1] = scale * (quadForm(dxyz, jmat) - uInv[1][1]);
265                 // U33
266                 mat3Mat3Multiply(uInv, dUdU33, jmat);
267                 mat3Mat3Multiply(jmat, uInv, jmat);
268                 dU[2] = scale * (quadForm(dxyz, jmat) - uInv[2][2]);
269                 // U12
270                 mat3Mat3Multiply(uInv, dUdU12, jmat);
271                 mat3Mat3Multiply(jmat, uInv, jmat);
272                 dU[3] = scale * (quadForm(dxyz, jmat) - uInv[0][1] * 2.0);
273                 // U13
274                 mat3Mat3Multiply(uInv, dUdU13, jmat);
275                 mat3Mat3Multiply(jmat, uInv, jmat);
276                 dU[4] = scale * (quadForm(dxyz, jmat) - uInv[0][2] * 2.0);
277                 // U23
278                 mat3Mat3Multiply(uInv, dUdU23, jmat);
279                 mat3Mat3Multiply(jmat, uInv, jmat);
280                 dU[5] = scale * (quadForm(dxyz, jmat) - uInv[1][2] * 2.0);
281                 // Store the anisotropic B-factor gradient.
282                 atom.addToAnisouGradient(dU);
283             }
284         }
285     }
286 
287     /**
288      * {@inheritDoc}
289      */
290     @Override
291     public void update(double[] xyz) {
292         update(xyz, u2b(uadd));
293     }
294 
295     /**
296      * {@inheritDoc}
297      */
298     @Override
299     public void update(double[] xyz, double badd) {
300         this.xyz[0] = xyz[0];
301         this.xyz[1] = xyz[1];
302         this.xyz[2] = xyz[2];
303         double biso = atom.getTempFactor();
304         uadd = b2u(badd);
305         occ = atom.getOccupancy();
306 
307         // Check occ is valid.
308         if (occ < 0.0) {
309             StringBuilder sb = new StringBuilder();
310             sb.append(" Negative occupancy for atom: ").append(atom).append("\n");
311             sb.append(" Resetting to 0.0\n");
312             logger.warning(sb.toString());
313             occ = 0.0;
314             atom.setOccupancy(0.0);
315         }
316 
317         // Check if anisou changed.
318         if (atom.getAnisou(null) == null) {
319             if (uaniso == null) {
320                 uaniso = new double[6];
321             }
322             hasAnisou = false;
323         } else {
324             hasAnisou = true;
325         }
326 
327         if (hasAnisou) {
328             // first check the ANISOU is valid
329             uaniso = atom.getAnisou(null);
330             double det = mat3Determinant(uaniso);
331             if (det <= 1e-14) {
332                 StringBuilder sb = new StringBuilder();
333                 sb.append(" Non-positive definite ANISOU for atom: ").append(atom).append("\n");
334                 sb.append(" Resetting ANISOU based on isotropic B: (").append(biso).append(")\n");
335                 logger.warning(sb.toString());
336                 double val = b2u(biso);
337                 uaniso[0] = val;
338                 uaniso[1] = val;
339                 uaniso[2] = val;
340                 uaniso[3] = 0.0;
341                 uaniso[4] = 0.0;
342                 uaniso[5] = 0.0;
343                 atom.setAnisou(uaniso);
344             }
345         } else {
346             if (biso < 0.0) {
347                 StringBuilder sb = new StringBuilder();
348                 sb.append(" Negative B factor for atom: ").append(atom).append("\n");
349                 sb.append(" Resetting B to 1.0\n");
350                 logger.warning(sb.toString());
351                 atom.setTempFactor(1.0);
352                 double val = b2u(1.0);
353                 uaniso[0] = val;
354                 uaniso[1] = val;
355                 uaniso[2] = val;
356             } else {
357                 double val = b2u(biso);
358                 uaniso[0] = val;
359                 uaniso[1] = val;
360                 uaniso[2] = val;
361             }
362             uaniso[3] = 0.0;
363             uaniso[4] = 0.0;
364             uaniso[5] = 0.0;
365         }
366 
367         u[0][0] = uaniso[0] + uadd;
368         u[1][1] = uaniso[1] + uadd;
369         u[2][2] = uaniso[2] + uadd;
370         u[0][1] = uaniso[3];
371         u[1][0] = uaniso[3];
372         u[0][2] = uaniso[4];
373         u[2][0] = uaniso[4];
374         u[1][2] = uaniso[5];
375         u[2][1] = uaniso[5];
376         mat3Inverse(u, uInv);
377 
378         double det = mat3Determinant(u);
379         ainv = a[0] / sqrt(det);
380         det = mat3Determinant(uInv);
381         binv = pow(det, oneThird);
382     }
383 }