View Javadoc
1   package ffx.xray.refine;
2   
3   import ffx.potential.bonded.Atom;
4   
5   import java.util.logging.Logger;
6   
7   import static ffx.numerics.math.MatrixMath.determinant3;
8   import static ffx.numerics.math.ScalarMath.b2u;
9   import static ffx.numerics.math.ScalarMath.u2b;
10  import static java.lang.String.format;
11  
12  /**
13   * The RefinedBfactor class defines an object for handling the refinement
14   * of B-factors (either isotropic or anisotropic) for a specific atom.
15   * It allows the setting and retrieval of atomic B-factors and anisotropic
16   * U-matrix values. Additionally, it supports grouping atoms to maintain
17   * constrained B-factor refinements across the group.
18   */
19  public class RefinedBFactor extends RefinedParameter {
20  
21    private static final Logger logger = Logger.getLogger(RefinedBFactor.class.getName());
22  
23    private static final double BFACTOR_SCALE = 12.0;
24    private static final double ANISOU_SCALE = 1200.0;
25  
26    /**
27     * Indicates whether the atom associated with this instance has anisotropic
28     * displacement parameters (ANISOU data) available.
29     * <p>
30     * If true, the atom has ANISOU data, which provides a more detailed
31     * representation of atomic displacement using a 3x3 covariance matrix of atomic
32     * positional uncertainty. If false, the atom only has an isotropic displacement
33     * parameter (B-factor), which assumes uniform displacement in all directions.
34     * <p>
35     * This field is final and immutable, being determined at the creation of the
36     * RefinableBfactor instance based on the presence of anisotropic data for the
37     * corresponding atom.
38     */
39    private final boolean isAnisou;
40  
41    /**
42     * Constructor for the RefinableBfactor class.
43     * Initializes the RefinableBfactor instance by associating it with the given atom
44     * and determining if the atom contains anisotropic B-factor data.
45     *
46     * @param atom the atom associated with the RefinableBfactor instance. This atom
47     *             is checked for anisotropic B-factor data during initialization.
48     */
49    public RefinedBFactor(Atom atom) {
50      super(atom);
51      double[] anisou = this.atom.getAnisou(null);
52      this.isAnisou = anisou != null;
53    }
54  
55    /**
56     * Adds the specified atom to the list of constrained atoms while applying specific constraints.
57     * This method sets the atom to active, assigns its temperature factor based on the associated
58     * atom, and, if applicable, copies its anisotropic displacement parameters (ANISOU).
59     *
60     * @param atom the atom to be constrained and updated with relevant parameters such as the
61     *             temperature factor and anisotropic displacement data (if available).
62     */
63    @Override
64    public void addConstrainedAtom(Atom atom) {
65      // Apply the constraint.
66      atom.setActive(true);
67      atom.setTempFactor(this.atom.getTempFactor());
68      if (isAnisou) {
69        atom.setAnisou(this.atom.getAnisou(null));
70      }
71      constrainedAtoms.add(atom);
72    }
73  
74    /**
75     * Adds the given atom to the list of constrained atoms that scatter in structural models.
76     * Sets the atom as active, assigns its temperature factor based on the associated atom,
77     * and if applicable, propagates its anisotropic B-factor data (ANISOU).
78     *
79     * @param atom the atom to be constrained and included in the scattering group.
80     */
81    @Override
82    public void addConstrainedAtomThatScatters(Atom atom) {
83      // Apply the constraint.
84      atom.setActive(true);
85      atom.setTempFactor(this.atom.getTempFactor());
86      if (isAnisou) {
87        atom.setAnisou(this.atom.getAnisou(null));
88      }
89      constrainedAtomsThatScatter.add(atom);
90    }
91  
92    /**
93     * Retrieves the number of parameters associated with this instance,
94     * based on whether the atom contains anisotropic B-factor data.
95     *
96     * @return 6 if anisotropic B-factor data is present; otherwise, 1.
97     */
98    @Override
99    public int getNumberOfParameters() {
100     if (isAnisou) {
101       return 6;
102     }
103     return 1;
104   }
105 
106   /**
107    * Determines whether the atom associated with this RefinableBfactor instance contains
108    * anisotropic B-factor data.
109    *
110    * @return true if the atom contains anisotropic B-factor data, false otherwise.
111    */
112   public boolean isAnisou() {
113     return isAnisou;
114   }
115 
116   /**
117    * Sets the B-factor value for the associated atom and all constrained atoms.
118    * The B-factor, also referred to as the temperature factor, is a value associated
119    * with the atomic displacement or mobility in structural models.
120    *
121    * @param bFactor the B-factor value to be assigned to the associated atom and all constrained atoms
122    */
123   public void setBFactor(double bFactor) {
124     atom.setTempFactor(bFactor);
125     double[] anisou = new double[6];
126     if (isAnisou) {
127       double u = b2u(bFactor);
128       anisou[0] = u;
129       anisou[1] = u;
130       anisou[2] = u;
131       atom.setAnisou(anisou);
132     }
133     for (Atom a : constrainedAtoms) {
134       a.setTempFactor(bFactor);
135       if (isAnisou && !a.isHydrogen()) {
136         a.setAnisou(anisou);
137       }
138     }
139     for (Atom a : constrainedAtomsThatScatter) {
140       a.setTempFactor(bFactor);
141       if (isAnisou && !a.isHydrogen()) {
142         a.setAnisou(anisou);
143       }
144     }
145   }
146 
147   /**
148    * Sets the anisotropic displacement parameters (ANISOU) for the associated atom.
149    * This method adjusts the temperature factor and ANISOU parameters based on the
150    * input values, while ensuring that the determinant of the ANISOU matrix is
151    * appropriately handled. If the determinant is negative, default values are used.
152    * Additionally, sets the temperature factor and ANISOU for constrained atoms.
153    *
154    * @param anisou an array of six double values representing the ANISOU matrix parameters.
155    *               These values define anisotropic atomic displacement.
156    */
157   public void setAnisou(double[] anisou) {
158     if (!isAnisou) {
159       return;
160     }
161     double det = determinant3(anisou);
162     if (det > 0.0) {
163       atom.setAnisou(anisou);
164       det = Math.pow(det, 1.0 / 3.0);
165       atom.setTempFactor(u2b(det));
166     } else {
167       atom.setTempFactor(1.0);
168       double u = b2u(1.0);
169       anisou[0] = u;
170       anisou[1] = u;
171       anisou[2] = u;
172       anisou[3] = 0.0;
173       anisou[4] = 0.0;
174       anisou[5] = 0.0;
175       atom.setAnisou(anisou);
176       logger.info(" Negative ANISOU: " + atom);
177     }
178     double bfactor = atom.getTempFactor();
179     for (Atom a : constrainedAtoms) {
180       a.setTempFactor(bfactor);
181       if (!a.isHydrogen()) {
182         a.setAnisou(anisou);
183       }
184     }
185     for (Atom a : constrainedAtomsThatScatter) {
186       a.setTempFactor(bfactor);
187       if (!a.isHydrogen()) {
188         a.setAnisou(anisou);
189       }
190     }
191   }
192 
193   /**
194    * Loads the parameters for the atom based on whether it has anisotropic data (ANISOU) or isotropic
195    * temperature factor (B-factor). ANISOU data consists of six parameters, while isotropic data is
196    * represented by a single parameter.
197    *
198    * @param parameters an array of doubles to populate with the relevant parameters.
199    */
200   @Override
201   public void getParameters(double[] parameters) {
202     if (isAnisou) {
203       double[] anisou = new double[6];
204       atom.getAnisou(anisou);
205       parameters[index] = anisou[0];
206       parameters[index + 1] = anisou[1];
207       parameters[index + 2] = anisou[2];
208       parameters[index + 3] = anisou[3];
209       parameters[index + 4] = anisou[4];
210       parameters[index + 5] = anisou[5];
211     } else {
212       parameters[index] = atom.getTempFactor();
213     }
214   }
215 
216   /**
217    * Stores parameter values for the associated atom, determining whether to store
218    * anisotropic displacement parameters (ANISOU) or isotropic B-factor values.
219    * If the atom contains anisotropic data, six ANISOU parameters are stored;
220    * otherwise, a single B-factor value is set.
221    *
222    * @param parameters an array of doubles containing all refined parameters.
223    */
224   @Override
225   public void setParameters(double[] parameters) {
226     if (isAnisou) {
227       double[] anisou = new double[6];
228       anisou[0] = parameters[index];
229       anisou[1] = parameters[index + 1];
230       anisou[2] = parameters[index + 2];
231       anisou[3] = parameters[index + 3];
232       anisou[4] = parameters[index + 4];
233       anisou[5] = parameters[index + 5];
234       setAnisou(anisou);
235     } else {
236       setBFactor(parameters[index]);
237     }
238   }
239 
240   /**
241    * Populates the provided array with velocity values for the associated atom.
242    * Depending on whether the atom has anisotropic B-factor data, either six ANISOU velocity
243    * components or a single isotropic B-factor velocity will be stored.
244    *
245    * @param velocity an array of double values to be updated with velocities.
246    *                   If the associated atom has anisotropic B-factor data, six
247    *                   velocity values (ANISOU components) are stored. Otherwise,
248    *                   a single velocity value (isotropic B-factor velocity) is stored.
249    */
250   @Override
251   public void getVelocity(double[] velocity) {
252     if (isAnisou) {
253       double[] anisou = new double[6];
254       atom.getAnisouVelocity(anisou);
255       velocity[index] = anisou[0];
256       velocity[index + 1] = anisou[1];
257       velocity[index + 2] = anisou[2];
258       velocity[index + 3] = anisou[3];
259       velocity[index + 4] = anisou[4];
260       velocity[index + 5] = anisou[5];
261     } else {
262       velocity[index] = atom.getTempFactorVelocity();
263     }
264   }
265 
266   /**
267    * Stores parameter values for the associated atom, determining whether to store
268    * anisotropic displacement parameters (ANISOU) or isotropic B-factor values.
269    * If the atom contains anisotropic data, six ANISOU parameters are stored;
270    * otherwise, a single B-factor value is set.
271    *
272    * @param velocity an array of doubles containing all refined parameters.
273    */
274   @Override
275   public void setVelocity(double[] velocity) {
276     if (isAnisou) {
277       double[] anisou = new double[6];
278       anisou[0] = velocity[index];
279       anisou[1] = velocity[index + 1];
280       anisou[2] = velocity[index + 2];
281       anisou[3] = velocity[index + 3];
282       anisou[4] = velocity[index + 4];
283       anisou[5] = velocity[index + 5];
284       atom.setAnisouVelocity(anisou);
285     } else {
286       atom.setTempFactorVelocity(velocity[index]);
287     }
288   }
289 
290   /**
291    * Populates the provided array with acceleration values for the associated atom.
292    * If the atom contains anisotropic B-factor data (ANISOU), six acceleration components
293    * are stored. Otherwise, a single isotropic acceleration value is stored.
294    *
295    * @param acceleration an array of double values to be updated with acceleration data.
296    *                      If the associated atom has anisotropic B-factor data, six values
297    *                      (ANISOU components) are stored. Otherwise, a single isotropic
298    *                      acceleration value is stored.
299    */
300   @Override
301   public void getAcceleration(double[] acceleration) {
302     if (isAnisou) {
303       double[] anisou = new double[6];
304       atom.getAnisouAcceleration(anisou);
305       acceleration[index] = anisou[0];
306       acceleration[index + 1] = anisou[1];
307       acceleration[index + 2] = anisou[2];
308       acceleration[index + 3] = anisou[3];
309       acceleration[index + 4] = anisou[4];
310       acceleration[index + 5] = anisou[5];
311     } else {
312       acceleration[index] = atom.getTempFactorAcceleration();
313     }
314   }
315 
316   /**
317    * Sets the acceleration for the atom based on the input array.
318    * The behavior changes depending on whether the atom is anisotropic or not.
319    *
320    * @param acceleration an array of doubles representing acceleration values.
321    *                      If the atom is anisotropic, six values are extracted
322    *                      to set anisotropic acceleration. Otherwise, a single
323    *                      value is used to set the isotropic acceleration.
324    */
325   @Override
326   public void setAcceleration(double[] acceleration) {
327     if (isAnisou) {
328       double[] anisou = new double[6];
329       anisou[0] = acceleration[index];
330       anisou[1] = acceleration[index + 1];
331       anisou[2] = acceleration[index + 2];
332       anisou[3] = acceleration[index + 3];
333       anisou[4] = acceleration[index + 4];
334       anisou[5] = acceleration[index + 5];
335       atom.setAnisouAcceleration(anisou);
336     } else {
337       atom.setTempFactorAcceleration(acceleration[index]);
338     }
339   }
340 
341   /**
342    * Populates the provided array with previous acceleration values for the associated atom.
343    * If the atom contains anisotropic B-factor data (ANISOU), six acceleration components
344    * are stored. Otherwise, a single isotropic acceleration value is stored.
345    *
346    * @param previousAcceleration an array of double values to be updated with previous acceleration data.
347    *                      If the associated atom has anisotropic B-factor data, six values
348    *                      (ANISOU components) are stored. Otherwise, a single isotropic
349    *                      acceleration value is stored.
350    */
351   @Override
352   public void getPreviousAcceleration(double[] previousAcceleration) {
353     if (isAnisou) {
354       double[] anisou = new double[6];
355       atom.getAnisouPreviousAcceleration(anisou);
356       previousAcceleration[index] = anisou[0];
357       previousAcceleration[index + 1] = anisou[1];
358       previousAcceleration[index + 2] = anisou[2];
359       previousAcceleration[index + 3] = anisou[3];
360       previousAcceleration[index + 4] = anisou[4];
361       previousAcceleration[index + 5] = anisou[5];
362     } else {
363       previousAcceleration[index] = atom.getTempFactorPreviousAcceleration();
364     }
365   }
366 
367   /**
368    * Sets the previous acceleration for the atom based on the input array.
369    * The behavior changes depending on whether the atom is anisotropic or not.
370    *
371    * @param previousAcceleration an array of doubles representing previous acceleration values.
372    *                      If the atom is anisotropic, six values are extracted
373    *                      to set anisotropic acceleration. Otherwise, a single
374    *                      value is used to set the isotropic acceleration.
375    */
376   @Override
377   public void setPreviousAcceleration(double[] previousAcceleration) {
378     if (isAnisou) {
379       double[] anisou = new double[6];
380       anisou[0] = previousAcceleration[index];
381       anisou[1] = previousAcceleration[index + 1];
382       anisou[2] = previousAcceleration[index + 2];
383       anisou[3] = previousAcceleration[index + 3];
384       anisou[4] = previousAcceleration[index + 4];
385       anisou[5] = previousAcceleration[index + 5];
386       atom.setAnisouPreviousAcceleration(anisou);
387     } else {
388       atom.setTempFactorPreviousAcceleration(previousAcceleration[index]);
389     }
390   }
391 
392   /**
393    * Sets the scaling factors for optimization parameters based on whether the atom contains
394    * anisotropic B-factor data. If the atom is identified as having anisotropic displacement
395    * data, six scaling factors are set. Otherwise, a single scaling factor is applied.
396    *
397    * @param optimizationScaling an array of doubles representing the scaling factors for optimization
398    *                            parameters. This array will be updated with the appropriate scaling
399    *                            values based on the isotropic or anisotropic B-factor data.
400    */
401   @Override
402   public void setOptimizationScaling(double[] optimizationScaling) {
403     if (isAnisou) {
404       optimizationScaling[index] = ANISOU_SCALE;
405       optimizationScaling[index + 1] = ANISOU_SCALE;
406       optimizationScaling[index + 2] = ANISOU_SCALE;
407       optimizationScaling[index + 3] = ANISOU_SCALE;
408       optimizationScaling[index + 4] = ANISOU_SCALE;
409       optimizationScaling[index + 5] = ANISOU_SCALE;
410     } else {
411       optimizationScaling[index] = BFACTOR_SCALE;
412     }
413   }
414 
415   /**
416    * The zero the B-factor gradient.
417    */
418   @Override
419   public void zeroGradient() {
420     if (!isAnisou) {
421       atom.setTempFactorGradient(0.0);
422       for (Atom a : constrainedAtoms) {
423         a.setTempFactorGradient(0.0);
424       }
425       for (Atom a : constrainedAtomsThatScatter) {
426         a.setTempFactorGradient(0.0);
427       }
428     } else {
429       double[] anisouGrad = new double[6];
430       atom.setTempFactorGradient(0.0);
431       atom.setAnisouGradient(anisouGrad);
432       for (Atom a : constrainedAtoms) {
433         a.setTempFactorGradient(0.0);
434         if (!a.isHydrogen()) {
435           a.setAnisouGradient(anisouGrad);
436         }
437       }
438       for (Atom a : constrainedAtomsThatScatter) {
439         a.setTempFactorGradient(0.0);
440         if (!a.isHydrogen()) {
441           a.setAnisouGradient(anisouGrad);
442         }
443       }
444     }
445   }
446 
447   /**
448    * Adds the gradient contributions for the associated atom and all constrained atoms
449    * to the provided gradient array. This method accounts for isotropic or anisotropic
450    * B-factor (temperature factor) data depending on the current state of the `isAnisou`
451    * field. If the atom has anisotropic B-factor data, the gradient is updated using
452    * the corresponding ANISOU components. Otherwise, isotropic B-factor gradients are
453    * used.
454    *
455    * @param gradient an array of doubles representing the gradient values to be updated.
456    */
457   @Override
458   public void getGradient(double[] gradient) {
459     if (!isAnisou) {
460       double bfactor = atom.getTempFactorGradient();
461       gradient[index] = bfactor;
462       for (Atom a : constrainedAtomsThatScatter) {
463         bfactor = a.getTempFactorGradient();
464         gradient[index] += bfactor;
465       }
466     } else {
467       double[] anisou = new double[6];
468       atom.getAnisouGradient(anisou);
469       gradient[index] = anisou[0];
470       gradient[index + 1] = anisou[1];
471       gradient[index + 2] = anisou[2];
472       gradient[index + 3] = anisou[3];
473       gradient[index + 4] = anisou[4];
474       gradient[index + 5] = anisou[5];
475       for (Atom a : constrainedAtomsThatScatter) {
476         if (!a.isHydrogen()) {
477           a.getAnisouGradient(anisou);
478           gradient[index] += anisou[0];
479           gradient[index + 1] += anisou[1];
480           gradient[index + 2] += anisou[2];
481           gradient[index + 3] += anisou[3];
482           gradient[index + 4] += anisou[4];
483           gradient[index + 5] += anisou[5];
484         } else {
485           double bfactor = a.getTempFactorGradient();
486           double u = b2u(bfactor);
487           gradient[index] += u;
488           gradient[index + 1] += u;
489           gradient[index + 2] += u;
490           // No off-diagonal
491         }
492       }
493     }
494   }
495 
496   /**
497    * Populates the provided array with mass values based on the anisotropic or isotropic
498    * nature of the associated atom. If the atom contains anisotropic B-factor data,
499    * values are assigned in six positions. Otherwise, only one value is assigned.
500    *
501    * @param mass an array of double values where the mass information will be stored.
502    */
503   @Override
504   public void getMass(double[] mass, double defaultMass) {
505     if (isAnisou) {
506       mass[index] = defaultMass;
507       mass[index + 1] = defaultMass;
508       mass[index + 2] = defaultMass;
509       mass[index + 3] = defaultMass;
510       mass[index + 4] = defaultMass;
511       mass[index + 5] = defaultMass;
512     } else {
513       mass[index] = defaultMass;
514     }
515   }
516 
517   @Override
518   public String toString() {
519     if (isAnisou) {
520       double[] anisou = new double[6];
521       atom.getAnisou(anisou);
522       StringBuilder sb = new StringBuilder(" Anisotropic B-factor:");
523       for (int i = 0; i < 6; i++) {
524         sb.append(format("  %10.6f", anisou[i]));
525       }
526       return sb.toString();
527     } else {
528       return format(" B-factor:  %10.6f", atom.getTempFactor());
529     }
530   }
531 }