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 }