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-2026.
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.refine;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.potential.bonded.Atom;
42  import ffx.potential.bonded.Bond;
43  import ffx.potential.bonded.MSNode;
44  import ffx.potential.bonded.Molecule;
45  import ffx.potential.bonded.Polymer;
46  import ffx.potential.bonded.Residue;
47  import org.apache.commons.configuration2.CompositeConfiguration;
48  
49  import java.util.ArrayList;
50  import java.util.IdentityHashMap;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.logging.Logger;
54  
55  import static ffx.numerics.math.ScalarMath.b2u;
56  import static java.lang.String.format;
57  
58  /**
59   * RefinementModel class.
60   *
61   * @author Timothy D. Fenn
62   * @since 1.0
63   */
64  public class RefinementModel {
65  
66    private static final Logger logger = Logger.getLogger(RefinementModel.class.getName());
67  
68    /**
69     * An array of MolecularAssembly objects representing the different molecular assemblies
70     * being processed.
71     */
72    private final MolecularAssembly[] molecularAssemblies;
73  
74    /**
75     * The {@code RefinementMode} defines the approach used for refining model parameters.
76     */
77    private RefinementMode refinementMode;
78  
79    /**
80     * Add a 6-parameter anisotropic b-factor to each heavy atom.
81     */
82    private final boolean addAnisou;
83  
84    /**
85     * Refine b-factors by residue, where every atom in a residue has
86     * the same b-factor.
87     */
88    private final boolean byResidue;
89  
90    /**
91     * If byResidue is true, then nResiduePerBFactor groups bonded residues
92     * together into residue groups.
93     */
94    private final int nResiduePerBFactor;
95  
96    /**
97     * If true, hydrogen atom b-factors are taken from their heavy atom.
98     * This is also enforced using byResidue b-factor refinement (i.e., this flag is redundant).
99     */
100   private final boolean ridingHydrogen;
101 
102   /**
103    * B-factor mass for extended Lagrangian.
104    */
105   private final double bMass;
106 
107   /**
108    * A boolean flag that determines whether the molecular occupancies are subject
109    * to refinement.
110    */
111   private final boolean refineMolOcc;
112 
113   /**
114    * If true, H/D occupancy will each be set to 0.5.
115    */
116   private final boolean resetHDOccupancy;
117 
118   /**
119    * If true, H/D pairs bound to the same heavy atom share an occupancy parameter.
120    */
121   private final boolean constrainHydrogenOccupancy;
122 
123   /**
124    * Occupancy mass for extended Lagrangian.
125    */
126   private final double occMass;
127 
128   /**
129    * All atoms that scatter are included in this array, including inactive atoms whose
130    * atomic coordinates are frozen.
131    */
132   private final Atom[] scatteringAtoms;
133 
134   /**
135    * This list has the same Atom instances as the refinedCoordinates in a defined order.
136    */
137   private final List<Atom> coordinateAtomList = new ArrayList<>();
138 
139   /**
140    * All atomic coordinates that are being refined (inactive atoms are excluded).
141    */
142   private final Map<Atom, RefinedCoordinates> refinedCoordinates;
143 
144   /**
145    * This list provides a defined order for the Atom instances in the refinedBFactors map.
146    */
147   private final List<Atom> bFactorAtomList = new ArrayList<>();
148 
149   /**
150    * All active b-factors that are being refined.
151    */
152   private final Map<Atom, RefinedBFactor> refinedBFactors;
153 
154   /**
155    * B-factors restraint list.
156    */
157   private final List<Atom[]> bFactorRestraints = new ArrayList<>();
158 
159   /**
160    * This list has the same Atom instances as the refinedOccupancies in a defined order.
161    */
162   private final List<Atom> occupancyAtomList = new ArrayList<>();
163 
164   /**
165    * All occupancies that are being refined.
166    */
167   private final Map<Atom, RefinedOccupancy> refinedOccupancies;
168 
169   /**
170    * A list that holds all the refined parameters that are instances of {@code RefinedParameter},
171    */
172   private final List<RefinedParameter> allParametersList;
173 
174   /**
175    * Each List should contain a Residue instance from each MolecularAssemblies (e.g., A, B and C).
176    */
177   private final List<List<Residue>> altResidues;
178 
179   /**
180    * Each List should contain a Molecule instance from each MolecularAssemblies (e.g., A, B and C).
181    */
182   private final List<List<Molecule>> altMolecules;
183 
184   /**
185    * Constructor for RefinementModel.
186    *
187    * @param molecularAssemblies an array of {@link ffx.potential.MolecularAssembly} objects.
188    */
189   public RefinementModel(MolecularAssembly[] molecularAssemblies) {
190     this(RefinementMode.COORDINATES_AND_BFACTORS_AND_OCCUPANCIES, molecularAssemblies);
191   }
192 
193   /**
194    * Constructor for RefinementModel.
195    *
196    * @param refinementMode      The refinement mode in use.
197    * @param molecularAssemblies an array of {@link ffx.potential.MolecularAssembly} objects.
198    */
199   public RefinementModel(RefinementMode refinementMode, MolecularAssembly[] molecularAssemblies) {
200 
201     this.refinementMode = refinementMode;
202     this.molecularAssemblies = molecularAssemblies;
203     MolecularAssembly rootAssembly = molecularAssemblies[0];
204 
205     // Load b-factor refinement properties.
206     CompositeConfiguration properties = rootAssembly.getProperties();
207     addAnisou = properties.getBoolean("add-anisou", false);
208     byResidue = properties.getBoolean("residue-bfactor", false);
209     nResiduePerBFactor = properties.getInt("n-residue-bfactor", 1);
210     ridingHydrogen = properties.getBoolean("riding-hydrogen-bfactor", true);
211     bMass = properties.getDouble("bfactor-mass", 5.0);
212     occMass = properties.getDouble("occupancy-mass", 10.0);
213     resetHDOccupancy = properties.getBoolean("reset-hd-occupancy", false);
214     constrainHydrogenOccupancy = properties.getBoolean("constrain-hydrogen-occupancy", true);
215 
216     // Load occupancy refinement properties
217     refineMolOcc = properties.getBoolean("refine-mol-occ", false);
218 
219     // Add anisotropic temperature factors
220     if (addAnisou) {
221       addAnisotropicBFactors();
222     }
223 
224     // Regularize active atoms to be consistent with the b-factor refinement strategy.
225     if (refinementMode.includesBFactors()) {
226       regularizeActiveAtoms();
227     }
228 
229     // Collect the atoms that scatter and atoms whose coordinates will be refined.
230     List<Atom> scatteringList = new ArrayList<>();
231     refinedCoordinates = createCoordinateModel(scatteringList);
232     scatteringAtoms = scatteringList.toArray(new Atom[0]);
233 
234     // Collect atoms whose b-factors will be refined.
235     refinedBFactors = createBFactorModel();
236 
237     // Collect atoms whose occupancy will be refined.
238     altResidues = new ArrayList<>();
239     altMolecules = new ArrayList<>();
240     refinedOccupancies = createOccupancyModel();
241 
242     // Collect all refined parameters given the current RefinementMode
243     allParametersList = new ArrayList<>();
244     setRefinementMode(refinementMode);
245   }
246 
247   /**
248    * Sets the refinement mode and adjusts the refined parameters based on the provided mode.
249    * Clears the current list of refined parameters and populates it with the relevant parameters
250    * (coordinates, B-factors, or occupancies) if they are included in the specified refinement mode.
251    * Finally, updates the parameter indices.
252    *
253    * @param mode the refinement mode to set, which determines the types of parameters
254    *             to include (e.g., coordinates, B-factors, occupancies)
255    */
256   public void setRefinementMode(RefinementMode mode) {
257     this.refinementMode = mode;
258     allParametersList.clear();
259     /*
260      * The parameter maps do not maintain a defined order of the key-value pairs.
261      * For this reason, the parameter lists are iterated over to create the overall list.
262      */
263     if (refinementMode.includesCoordinates()) {
264       for (Atom atom : coordinateAtomList) {
265         allParametersList.add(refinedCoordinates.get(atom));
266       }
267     }
268     if (refinementMode.includesBFactors()) {
269       for (Atom atom : bFactorAtomList) {
270         allParametersList.add(refinedBFactors.get(atom));
271       }
272       collectBFactorRestraints();
273     } else {
274       bFactorRestraints.clear();
275     }
276     if (refinementMode.includesOccupancies()) {
277       for (Atom atom : occupancyAtomList) {
278         allParametersList.add(refinedOccupancies.get(atom));
279       }
280     }
281     setParameterIndices();
282   }
283 
284   /**
285    * Getter for the field <code>totalAtomArray</code>.
286    *
287    * @return the totalAtomArray
288    */
289   public Atom[] getScatteringAtoms() {
290     return scatteringAtoms;
291   }
292 
293   /**
294    * Getter for the field <code>activeAtomArray</code>.
295    *
296    * @return the activeAtomArray
297    */
298   public Atom[] getActiveAtoms() {
299     return coordinateAtomList.toArray(new Atom[0]);
300   }
301 
302   /**
303    * Getter for the field <code>altMolecules</code>.
304    *
305    * @return the altMolecules
306    */
307   public List<List<Molecule>> getAltMolecules() {
308     return altMolecules;
309   }
310 
311   /**
312    * Getter for the field <code>altResidues</code>.
313    *
314    * @return the altResidues
315    */
316   public List<List<Residue>> getAltResidues() {
317     return altResidues;
318   }
319 
320   /**
321    * Retrieves the array of MolecularAssembly objects.
322    *
323    * @return An array of MolecularAssembly objects.
324    */
325   public MolecularAssembly[] getMolecularAssemblies() {
326     return molecularAssemblies;
327   }
328 
329   /**
330    * List of Atom pairs that define B-factor restraints. There is a covalent bond between each pair.
331    *
332    * @return The bond list.
333    */
334   public List<Atom[]> getBFactorRestraints() {
335     return bFactorRestraints;
336   }
337 
338   /**
339    * Adds the coordinate gradient from an alternate conformer to the overall coordinate gradient. Each
340    * active atom from the alternate conformer stores its XYZ gradient and its index into the overall
341    * refinement gradient array.
342    *
343    * @param assembly The index of the molecular assembly to retrieve active atoms from.
344    * @param gradient The overall gradient array where the computed gradient data is aggregated.
345    */
346   public void addAssemblyGradient(int assembly, double[] gradient) {
347     MolecularAssembly molecularAssembly = molecularAssemblies[assembly];
348     Atom[] activeAtoms = molecularAssembly.getActiveAtomArray();
349     double[] xyz = new double[3];
350     for (Atom a : activeAtoms) {
351       int index = a.getXrayCoordIndex() * 3;
352       a.getXYZGradient(xyz);
353       gradient[index] += xyz[0];
354       gradient[index + 1] += xyz[1];
355       gradient[index + 2] += xyz[2];
356     }
357   }
358 
359   /**
360    * Provides a string representation of the refinement model, including details
361    * about the number of atoms, active atoms, atoms in use, and the refinement variables.
362    *
363    * @return A string describing the refinement model, including the total
364    * number of atoms, the number of atoms currently being used,
365    * the number of active atoms, and the number of refinement variables
366    * categorized by XYZ coordinates, B-Factors, and occupancies.
367    */
368   public String toString() {
369     int nAtoms = scatteringAtoms.length;
370     int nActive = coordinateAtomList.size();
371     // Count the number of scatteringAtoms in use.
372     int nUse = 0;
373     for (Atom a : scatteringAtoms) {
374       if (a.getUse()) {
375         nUse++;
376       }
377     }
378     int nXYZ = getNumCoordParameters();
379     int nBFactors = getNumBFactorParameters();
380     int nOccupancies = getNumOccupancyParameters();
381     int n = nXYZ + nBFactors + nOccupancies;
382 
383     StringBuilder sb = new StringBuilder("\n Refinement Model\n");
384     sb.append(format("  Number of atoms:        %d\n", nAtoms));
385     sb.append(format("  Atoms being used:       %d\n", nUse));
386     sb.append(format("  Number of active atoms: %d\n", nActive));
387     sb.append(format("  Number of variables:    %d (nXYZ %d, nB %d, nOcc %d)\n",
388         n, nXYZ, nBFactors, nOccupancies));
389 
390     return sb.toString();
391   }
392 
393   /**
394    * Retrieves the current refinement mode set in the object.
395    *
396    * @return the refinement mode of the object as a RefinementMode enum.
397    */
398   public RefinementMode getRefinementMode() {
399     return refinementMode;
400   }
401 
402   /**
403    * Retrieves a list of refined parameters.
404    *
405    * @return a list containing the refined parameters.
406    */
407   public List<RefinedParameter> getRefinedParameters() {
408     return allParametersList;
409   }
410 
411   /**
412    * Calculates the total number of parameters to be refined based on the specified refinement mode.
413    * The parameters may include atomic coordinates, B-factors, and occupancies depending on the mode.
414    *
415    * @return The total number of parameters that need to be refined based on the specified mode.
416    */
417   public int getNumParameters() {
418     return getNumCoordParameters()
419         + getNumBFactorParameters()
420         + getNumOccupancyParameters();
421   }
422 
423   /**
424    * Calculates the number of coordinate parameters to be refined based on the specified refinement mode.
425    * If the mode includes coordinates, the number of parameters is determined by the size of the coordinate atom list.
426    *
427    * @return The number of coordinate parameters that need to be refined. Returns 0 if the mode does not include coordinates.
428    */
429   public int getNumCoordParameters() {
430     // Coordinates
431     if (refinementMode.includesCoordinates()) {
432       return coordinateAtomList.size() * 3;
433     }
434     return 0;
435   }
436 
437   /**
438    * Calculates the total number of B-factor parameters based on the specified refinement mode.
439    *
440    * @return the total number of B-factor parameters, considering whether they are anisotropic or isotropic.
441    */
442   public int getNumBFactorParameters() {
443     int num = 0;
444     if (refinementMode.includesBFactors()) {
445       for (RefinedBFactor bFactor : refinedBFactors.values()) {
446         num += bFactor.getNumberOfParameters();
447       }
448     }
449     return num;
450   }
451 
452   /**
453    * Calculates the number of occupancy parameters based on the provided refinement mode.
454    *
455    * @return the number of occupancy parameters if occupancies are included; otherwise, returns 0
456    */
457   public int getNumOccupancyParameters() {
458     if (refinementMode.includesOccupancies()) {
459       return occupancyAtomList.size();
460     }
461     return 0;
462   }
463 
464   /**
465    * Counts and returns the total number of ANISOU records present.
466    *
467    * @return the number of ANISOU records as an integer
468    */
469   public int getNumANISOU() {
470     int numANISOU = 0;
471     for (RefinedBFactor bFactor : refinedBFactors.values()) {
472       if (bFactor.isAnisou()) {
473         numANISOU++;
474       }
475     }
476     return numANISOU;
477   }
478 
479   /**
480    * Get parameter values and store them into the provided array based on the refinement mode.
481    * This method extracts and sets coordinates, B-factors, and occupancies for the atoms
482    * depending on the specified refinement mode.
483    *
484    * @param x The array into which the parameter values are loaded. The array should be large
485    *          enough to accommodate all required parameters based on the refinement mode.
486    */
487   public void getParameters(double[] x) {
488     for (RefinedParameter parameter : allParametersList) {
489       parameter.getParameters(x);
490     }
491   }
492 
493   /**
494    * Set parameter values into the refinement model based on the provided refinement mode.
495    * The method updates coordinates, B-factors, and occupancies for atoms, depending on what is
496    * specified by the refinement mode.
497    *
498    * @param x An array of doubles containing the new parameter values. The values should be ordered as
499    *          required by the refinement mode: first coordinates, then B-factors, and finally occupancies,
500    *          if applicable.
501    */
502   public void setParameters(double[] x) {
503     for (RefinedParameter parameter : allParametersList) {
504       parameter.setParameters(x);
505     }
506   }
507 
508   /**
509    * Get parameter velocities and store them into the provided array based on the refinement mode.
510    * This method extracts and sets coordinates, B-factors, and occupancies for the atoms
511    * depending on the specified refinement mode.
512    *
513    * @param x The array into which the parameter velocities are loaded. The array should be large
514    *          enough to accommodate all required parameter velocities based on the refinement mode.
515    */
516   public void getVelocity(double[] x) {
517     for (RefinedParameter parameter : allParametersList) {
518       parameter.getVelocity(x);
519     }
520   }
521 
522   /**
523    * Set parameter velocities into the refinement model based on the provided refinement mode.
524    * The method updates coordinates, B-factors, and occupancies for atoms, depending on what is
525    * specified by the refinement mode.
526    *
527    * @param x An array of doubles containing the new parameter velocities. The values should be ordered as
528    *          required by the refinement mode: first coordinates, then B-factors, and finally occupancies,
529    *          if applicable.
530    */
531   public void setVelocity(double[] x) {
532     for (RefinedParameter parameter : allParametersList) {
533       parameter.setVelocity(x);
534     }
535   }
536 
537   /**
538    * Retrieves acceleration values for all refined parameters
539    * in the list using the provided array.
540    *
541    * @param x an array of doubles to store acceleration values for each parameter
542    */
543   public void getAcceleration(double[] x) {
544     for (RefinedParameter parameter : allParametersList) {
545       parameter.getAcceleration(x);
546     }
547   }
548 
549   /**
550    * Sets the acceleration values for all parameters in the parameters list.
551    *
552    * @param x an array of double values representing the acceleration to be set for each parameter
553    */
554   public void setAcceleration(double[] x) {
555     for (RefinedParameter parameter : allParametersList) {
556       parameter.setAcceleration(x);
557     }
558   }
559 
560   /**
561    * Retrieves previous acceleration values for all refined parameters
562    * in the list using the provided array.
563    *
564    * @param x an array of doubles to store previous acceleration values for each parameter
565    */
566   public void getPreviousAcceleration(double[] x) {
567     for (RefinedParameter parameter : allParametersList) {
568       parameter.getPreviousAcceleration(x);
569     }
570   }
571 
572   /**
573    * Sets the previous acceleration values for all parameters in the parameters list.
574    *
575    * @param x an array of double values representing the previous acceleration to be set for each parameter
576    */
577   public void setPreviousAcceleration(double[] x) {
578     for (RefinedParameter parameter : allParametersList) {
579       parameter.setPreviousAcceleration(x);
580     }
581   }
582 
583   /**
584    * Populates the provided array with mass values retrieved from all refined parameters.
585    *
586    * @param mass an array where the mass values will be stored.
587    */
588   public void getMass(double[] mass) {
589     for (RefinedParameter parameter : allParametersList) {
590       if (parameter instanceof RefinedCoordinates) {
591         parameter.getMass(mass, 5.0);
592       } else if (parameter instanceof RefinedBFactor) {
593         parameter.getMass(mass, bMass);
594       } else if (parameter instanceof RefinedOccupancy) {
595         parameter.getMass(mass, occMass);
596       }
597     }
598   }
599 
600   /**
601    * Loads the optimization scale factors into all refined parameters.
602    *
603    * @param optimizationScaling an array of scale factors to be applied to each refined parameter
604    */
605   public void loadOptimizationScaling(double[] optimizationScaling) {
606     for (RefinedParameter parameter : allParametersList) {
607       parameter.setOptimizationScaling(optimizationScaling);
608     }
609   }
610 
611   /**
612    * Zero out the gradient for all atoms being refined.
613    */
614   public void zeroGradient() {
615     for (RefinedParameter parameter : allParametersList) {
616       parameter.zeroGradient();
617     }
618   }
619 
620   /**
621    * Loads the gradient values into the respective refined parameters based on the current refinement mode.
622    *
623    * @param gradient an array of double values representing the gradient to be loaded.
624    */
625   public void getGradient(double[] gradient) {
626     for (RefinedParameter parameter : allParametersList) {
627       parameter.getGradient(gradient);
628     }
629   }
630 
631   /**
632    * Creates a coordinate refinement model by identifying scattering and active atoms
633    * from the provided molecular assemblies and defining coordinate constraints for
634    * specific atoms.
635    *
636    * @param scatteringList The list of atoms in the scattering model.
637    * @return A map where the keys represent atoms with constrained coordinates, and the values
638    * correspond to the indices of their paired atoms in the active atom array.
639    */
640   private Map<Atom, RefinedCoordinates> createCoordinateModel(List<Atom> scatteringList) {
641     logger.fine("\n Creating Coordinate Refinement Model\n");
642 
643     MolecularAssembly rootAssembly = molecularAssemblies[0];
644     // The keys are references to Atom instances.
645     Map<Atom, RefinedCoordinates> coordinateMap = new IdentityHashMap<>();
646 
647     // Loop over all atoms in the root MolecularAssembly.
648     Atom[] atomList = rootAssembly.getAtomArray();
649     for (Atom a : atomList) {
650       // All atoms from the root molecular assembly are added to the scattering list.
651       scatteringList.add(a);
652       if (a.isActive()) {
653         // Active atoms can have their coordinates refined.
654         a.setXrayCoordIndex(coordinateAtomList.size());
655         coordinateAtomList.add(a);
656         coordinateMap.put(a, new RefinedCoordinates(a));
657         logger.fine(" Active: " + a);
658       }
659     }
660 
661     // Add scattering atoms from other topologies and create coordinate constraints for deuterium atoms.
662     for (int i = 1; i < molecularAssemblies.length; i++) {
663       MolecularAssembly molecularAssembly = molecularAssemblies[i];
664       atomList = molecularAssembly.getAtomArray();
665       for (Atom a : atomList) {
666         Character altLoc = a.getAltLoc();
667         Atom rootAtom = rootAssembly.findAtom(a, false);
668         Atom deuteriumMatch = rootAssembly.findAtom(a, true);
669         if (rootAtom != null && rootAtom.getAltLoc().equals(altLoc)) {
670           // This atom is identical to an atom in Conformation A and does not scatter.
671           if (rootAtom.isActive()) {
672             // The coordinates are constrained to match those of the atom in the root topology.
673             RefinedCoordinates refinedCoordinates = coordinateMap.get(rootAtom);
674             refinedCoordinates.addConstrainedAtom(a);
675             a.setXrayCoordIndex(rootAtom.getXrayCoordIndex());
676           } else {
677             // Ensure paired atoms active status concords.
678             a.setActive(false);
679           }
680         } else if (deuteriumMatch != null) {
681           // This is an H/D pair.
682           scatteringList.add(a);
683           if (deuteriumMatch.isActive()) {
684             RefinedCoordinates refinedCoordinates = coordinateMap.get(deuteriumMatch);
685             refinedCoordinates.addConstrainedAtomThatScatters(a);
686             a.setXrayCoordIndex(deuteriumMatch.getXrayCoordIndex());
687           } else {
688             // Ensure paired atoms active status concords.
689             a.setActive(false);
690           }
691         } else {
692           // This atom is part of an alternate conformation not found in the root topology.
693           scatteringList.add(a);
694           if (a.isActive()) {
695             a.setXrayCoordIndex(coordinateAtomList.size());
696             coordinateAtomList.add(a);
697             coordinateMap.put(a, new RefinedCoordinates(a));
698           }
699         }
700       }
701     }
702 
703     return coordinateMap;
704   }
705 
706   /**
707    * Creates a b-factor refinement model by identifying active atoms whose b-factors
708    * should be refined based on the provided molecular assemblies and
709    * defining b-factor constraints.
710    *
711    * @return A map where the keys represent atoms with constrained b-factors, and the values
712    * correspond to the indices of their paired atoms in the b-factor array.
713    */
714   private Map<Atom, RefinedBFactor> createBFactorModel() {
715     logger.fine("\n Creating B-Factor Refinement Model\n");
716     MolecularAssembly rootAssembly = molecularAssemblies[0];
717     Map<Atom, RefinedBFactor> bFactorMap = new IdentityHashMap<>();
718 
719     if (byResidue) {
720       // Collect residue b-factors for the root topology.
721       Polymer[] polymers = rootAssembly.getChains();
722       for (Polymer polymer : polymers) {
723         List<Residue> residues = polymer.getResidues();
724         Atom heavyAtom = null;
725         RefinedBFactor currentRefinedBFactor = null;
726         for (int j = 0; j < residues.size(); j++) {
727           Residue residue = residues.get(j);
728           if (j % nResiduePerBFactor == 0 || heavyAtom == null) {
729             heavyAtom = residue.getFirstActiveHeavyAtom();
730             if (heavyAtom == null) {
731               // This residue is inactive.
732               continue;
733             }
734             bFactorAtomList.add(heavyAtom);
735             currentRefinedBFactor = new RefinedBFactor(heavyAtom);
736             bFactorMap.put(heavyAtom, currentRefinedBFactor);
737           }
738           // The rest of the atoms in this residue are constrained.
739           for (Atom a : residue.getAtomList()) {
740             if (a != heavyAtom) {
741               currentRefinedBFactor.addConstrainedAtomThatScatters(a);
742             }
743           }
744         }
745       }
746       List<MSNode> molecules = rootAssembly.getNodeList(true);
747       for (MSNode m : molecules) {
748         Atom heavyAtom = m.getFirstActiveHeavyAtom();
749         if (heavyAtom == null) {
750           // This molecule is inactive.
751           continue;
752         }
753         bFactorAtomList.add(heavyAtom);
754         RefinedBFactor currentRefinedBFactor = new RefinedBFactor(heavyAtom);
755         bFactorMap.put(heavyAtom, currentRefinedBFactor);
756         // The rest of the atoms in this residue are constrained.
757         for (Atom a : m.getAtomList()) {
758           if (a != heavyAtom) {
759             currentRefinedBFactor.addConstrainedAtomThatScatters(a);
760           }
761         }
762       }
763 
764       // Residue-based b-factors should generally not be used with alternative conformations.
765       for (int i = 1; i < molecularAssemblies.length; i++) {
766         MolecularAssembly molecularAssembly = molecularAssemblies[i];
767         polymers = molecularAssembly.getChains();
768         for (int j = 0; j < polymers.length; j++) {
769           Polymer polymer = polymers[j];
770           List<Residue> residues = polymer.getResidues();
771           for (int k = 0; k < residues.size(); k++) {
772             Residue residue = residues.get(k);
773             Residue rootResidue = rootAssembly.getResidue(j, k);
774             if (rootResidue == null) {
775               logger.severe(format(" Residue %s not found in the root conformation.", residue));
776               return null;
777             }
778             Atom rootAtom = rootResidue.getFirstActiveHeavyAtom();
779             if (rootAtom == null) {
780               // This residue is not active.
781               continue;
782             }
783             RefinedBFactor refinedBFactor = bFactorMap.get(rootAtom);
784             // The B-factors in this residue are constrained.
785             for (Atom a : residue.getAtomList()) {
786               Character altLoc = a.getAltLoc();
787               if (!altLoc.equals(rootAtom.getAltLoc())) {
788                 refinedBFactor.addConstrainedAtomThatScatters(a);
789               } else {
790                 refinedBFactor.addConstrainedAtom(a);
791               }
792             }
793           }
794         }
795 
796         molecules = molecularAssemblies[i].getNodeList(true);
797         for (MSNode m : molecules) {
798           Atom heavyAtom = m.getFirstActiveHeavyAtom();
799           if (heavyAtom == null) {
800             // This molecule is inactive.
801             continue;
802           }
803           Character altLoc = heavyAtom.getAltLoc();
804           if (!altLoc.equals(' ') && !altLoc.equals('A')) {
805             bFactorAtomList.add(heavyAtom);
806             RefinedBFactor refinedBFactor = new RefinedBFactor(heavyAtom);
807             bFactorMap.put(heavyAtom, refinedBFactor);
808             // The rest of the atoms in this molecule are constrained.
809             for (Atom a : m.getAtomList()) {
810               if (a != heavyAtom) {
811                 refinedBFactor.addConstrainedAtomThatScatters(a);
812               }
813             }
814           }
815         }
816       }
817     } else if (ridingHydrogen) {
818       // Hydrogen will use the B-Factor of their heavy atom.
819       // Add heavy atoms and deuterium get unique b-factors
820       for (Atom atom : coordinateAtomList) {
821         if (atom.isHydrogen() && !atom.isDeuterium()) {
822           continue;
823         }
824         bFactorAtomList.add(atom);
825         RefinedBFactor refinedBFactor = new RefinedBFactor(atom);
826         bFactorMap.put(atom, refinedBFactor);
827         // Non-scattering atoms constrained to this B-Factor
828         RefinedCoordinates refinedCoords = refinedCoordinates.get(atom);
829         for (Atom a : refinedCoords.constrainedAtoms) {
830           refinedBFactor.addConstrainedAtom(a);
831         }
832         // Scattering atoms constrained to this B-factor
833         for (Atom a : refinedCoords.constrainedAtomsThatScatter) {
834           refinedBFactor.addConstrainedAtomThatScatters(a);
835         }
836       }
837       // Add hydrogen constrained to their heavy atom.
838       for (Atom atom : coordinateAtomList) {
839         if (atom.isHydrogen() && !atom.isDeuterium()) {
840           Atom heavy = atom.getBonds().getFirst().get1_2(atom);
841           if (!heavy.isActive()) {
842             // If the heavy atom is not active, then do not refine this hydrogen b-factor.
843             continue;
844           }
845           if (bFactorMap.containsKey(heavy)) {
846             RefinedBFactor refinedBFactor = bFactorMap.get(heavy);
847             refinedBFactor.addConstrainedAtomThatScatters(atom);
848             continue;
849           }
850           logger.info(" Could not locate a heavy atom B-factor for: " + atom);
851         }
852       }
853     } else {
854       // No special constraints.
855       // The b-factors of all active atoms are refined.
856       for (Atom atom : coordinateAtomList) {
857         bFactorAtomList.add(atom);
858         RefinedBFactor refinedBFactor = new RefinedBFactor(atom);
859         bFactorMap.put(atom, refinedBFactor);
860         // Non-scattering Atoms constrained to this BFactor
861         RefinedCoordinates refinedCoords = refinedCoordinates.get(atom);
862         for (Atom a : refinedCoords.constrainedAtoms) {
863           refinedBFactor.addConstrainedAtom(a);
864         }
865         // Scattering Atoms constrained to this B-factor
866         // for (Atom a : refinedCoords.constrainedAtomsThatScatter) {
867         //  refinedBFactor.addConstrainedAtomThatScatters(a);
868         // }
869       }
870     }
871 
872     return bFactorMap;
873   }
874 
875   /**
876    * Create a list of Bond restraints for B-factors being refined.
877    */
878   private void collectBFactorRestraints() {
879     bFactorRestraints.clear();
880     MolecularAssembly rootAssembly = molecularAssemblies[0];
881     // Add each bond from the root assembly if at least one atom of the bond is active.
882     List<Bond> rootBonds = rootAssembly.getBondList();
883     for (Bond bond : rootBonds) {
884       Atom a1 = bond.getAtom(0);
885       Atom a2 = bond.getAtom(1);
886       if (!a1.isActive() && !a2.isActive()) {
887         continue;
888       }
889       bFactorRestraints.add(new Atom[]{a1, a2});
890     }
891 
892     // Add each bond from alternate conformers is included if at least one atom is active and
893     // at least one atom is from the alternate location.
894     for (int i = 1; i < molecularAssemblies.length; i++) {
895       MolecularAssembly molecularAssembly = molecularAssemblies[i];
896       Character altLoc = molecularAssembly.getAlternateLocation();
897       List<Bond> bonds = molecularAssembly.getBondList();
898       for (Bond bond : bonds) {
899         Atom a1 = bond.getAtom(0);
900         Atom a2 = bond.getAtom(1);
901         // One atom must be active.
902         if (!a1.isActive() && !a2.isActive()) {
903           continue;
904         }
905         // Both atoms are part of the alternate conformer.
906         if (a1.getAltLoc().equals(altLoc) && a2.getAltLoc().equals(altLoc)) {
907           bFactorRestraints.add(new Atom[]{a1, a2});
908         } else if (a1.getAltLoc().equals(altLoc) && !a2.getAltLoc().equals(altLoc)) {
909           // Atom 1 is part of the alternate conformer.
910           a2 = rootAssembly.findAtom(a2);
911           if (a2 != null) {
912             bFactorRestraints.add(new Atom[]{a1, a2});
913           }
914         } else if (!a1.getAltLoc().equals(altLoc) && a2.getAltLoc().equals(altLoc)) {
915           // Atom 2 is part of the alternate conformer.
916           a1 = rootAssembly.findAtom(a1);
917           if (a1 != null) {
918             bFactorRestraints.add(new Atom[]{a1, a2});
919           }
920         }
921       }
922     }
923   }
924 
925   /**
926    * Creates an occupancy model by identifying and refining atoms within residues or molecules
927    * that have alternate conformations or less-than-full occupancies. The method looks through
928    * residues and molecules in molecular assemblies, evaluates their constituent atoms, and groups
929    * those with alternate conformers into refined occupancy objects for further analysis or refinement.
930    * <p>
931    * Alternate residues and molecules are tracked separately in internal structures, and any atoms
932    * that scatter due to constraints are also linked appropriately.
933    *
934    * @return A map of atoms to their corresponding refined occupancy objects. For H/D occupancy
935    * refinement, keys are heavy atoms when hydrogen occupancies are constrained, and primary H/D
936    * atoms otherwise. These objects encapsulate information about atoms with alternate
937    * conformations or partial occupancies and their constrained scattering atoms.
938    */
939   private Map<Atom, RefinedOccupancy> createOccupancyModel() {
940     logger.fine("\n Creating Occupancy Refinement Model\n");
941     Map<Atom, RefinedOccupancy> refinedOccupancies = new IdentityHashMap<>();
942 
943     boolean refineDeuterium = false;
944     for (MolecularAssembly molecularAssembly : molecularAssemblies) {
945       if (molecularAssembly.hasDeuterium()) {
946         refineDeuterium = true;
947         break;
948       }
949     }
950 
951     if (refineDeuterium) {
952       // Find polymer hydrogen / deuterium with occupancy less than one.
953       MolecularAssembly rootAssembly = molecularAssemblies[0];
954       Polymer[] polymers = rootAssembly.getChains();
955       if (polymers != null) {
956         for (Polymer polymer : polymers) {
957           List<Residue> residues = polymer.getResidues();
958           for (Residue residue : residues) {
959             List<Atom> atoms = residue.getAtomList();
960             for (Atom atom : atoms) {
961               if (atom.isActive() && atom.isHydrogen()) {
962                 double occupancy = atom.getOccupancy();
963                 if (occupancy < 1.0) {
964                   Atom occupancyKey = atom;
965                   if (constrainHydrogenOccupancy) {
966                     occupancyKey = atom.getBonds().getFirst().get1_2(atom);
967                   }
968                   if (refinedOccupancies.containsKey(occupancyKey)) {
969                     RefinedOccupancy refinedOccupancy = refinedOccupancies.get(occupancyKey);
970                     refinedOccupancy.addConstrainedAtomThatScatters(atom);
971                   } else {
972                     RefinedOccupancy refinedOccupancy = new RefinedOccupancy(atom);
973                     refinedOccupancies.put(occupancyKey, refinedOccupancy);
974                     occupancyAtomList.add(occupancyKey);
975                   }
976                 }
977               }
978             }
979           }
980         }
981       }
982       // Find matching hydrogen / deuterium in the 2nd Assembly.
983       if (molecularAssemblies.length > 1) {
984         MolecularAssembly molecularAssembly = molecularAssemblies[1];
985         for (Atom occupancyKey : occupancyAtomList) {
986           RefinedOccupancy refinedOccupancy = refinedOccupancies.get(occupancyKey);
987           // Collect the H/D atoms from conformation A.
988           List<Atom> atoms = new ArrayList<>(refinedOccupancy.constrainedAtomsThatScatter);
989           atoms.add(refinedOccupancy.atom);
990           for (Atom atom : atoms) {
991             // Find the matching atom in conformation B.
992             Atom match = molecularAssembly.findAtom(atom, true);
993             if (match != null) {
994               double o1 = atom.getOccupancy();
995               double o2 = match.getOccupancy();
996               if (resetHDOccupancy) {
997                 logger.info(" Reset Occupancy for H/D Pair to 0.5/0.5:");
998                 atom.setOccupancy(0.5);
999                 match.setOccupancy(0.5);
1000                 logger.info(format(" %s: %6.3f", atom, atom.getOccupancy()));
1001                 logger.info(format(" %s: %6.3f", match, match.getOccupancy()));
1002               } else if (o1 + o2 != 1.0) {
1003                 logger.info(" Occupancy Sum for H/D Pair is not 1.0:");
1004                 logger.info(format(" %s: %6.3f", atom, atom.getOccupancy()));
1005                 logger.info(format(" %s: %6.3f", match, match.getOccupancy()));
1006                 double delta = (1.0 - o1 - o2) / 2.0;
1007                 atom.setOccupancy(o1 + delta);
1008                 match.setOccupancy(o2 + delta);
1009                 logger.info(" Occupancy Sum for H/D Pair adjusted to 1.0:");
1010                 logger.info(format(" %s: %6.3f", atom, atom.getOccupancy()));
1011                 logger.info(format(" %s: %6.3f", match, match.getOccupancy()));
1012               }
1013               refinedOccupancy.addConstrainedAtomThatScattersComplement(match);
1014             }
1015           }
1016         }
1017       }
1018     } else {
1019       // Find Residues with alternate conformers.
1020       Polymer[] polymers = molecularAssemblies[0].getChains();
1021       if (polymers != null && polymers.length > 0) {
1022         for (int i = 0; i < polymers.length; i++) {
1023           List<Residue> residues = polymers[i].getResidues();
1024           for (int j = 0; j < residues.size(); j++) {
1025             List<Residue> list = getResidueConformers(i, j);
1026             if (list != null && !list.isEmpty()) {
1027               altResidues.add(list);
1028               for (Residue residue : list) {
1029                 List<Atom> atomList = residue.getAtomList();
1030                 RefinedOccupancy refinedOccupancy = null;
1031                 Atom refinedAtom = null;
1032                 for (Atom a : atomList) {
1033                   Character altLoc = a.getAltLoc();
1034                   double occupancy = a.getOccupancy();
1035                   if (!altLoc.equals(' ') || occupancy < 1.0) {
1036                     occupancyAtomList.add(a);
1037                     refinedOccupancy = new RefinedOccupancy(a);
1038                     refinedOccupancies.put(a, refinedOccupancy);
1039                     // logger.info(" Occupancy: " + a);
1040                     refinedAtom = a;
1041                     break;
1042                   }
1043                 }
1044                 if (refinedAtom != null) {
1045                   for (Atom a : atomList) {
1046                     if (a == refinedAtom) {
1047                       continue;
1048                     }
1049                     Character altLoc = a.getAltLoc();
1050                     double occupancy = a.getOccupancy();
1051                     if (!altLoc.equals(' ') || occupancy < 1.0) {
1052                       refinedOccupancy.addConstrainedAtomThatScatters(a);
1053                       // logger.info("  Constrained Occupancy: " + a);
1054                     }
1055                   }
1056                 }
1057               }
1058             }
1059           }
1060         }
1061       }
1062     }
1063 
1064     // Find Molecules with non-zero occupancies.
1065     if (refineMolOcc) {
1066       List<MSNode> molecules = molecularAssemblies[0].getMolecules();
1067       if (molecules != null && !molecules.isEmpty()) {
1068         for (int i = 0; i < molecules.size(); i++) {
1069           List<Molecule> list = getMoleculeConformers(i);
1070           if (list != null && !list.isEmpty()) {
1071             altMolecules.add(list);
1072             for (Molecule molecule : list) {
1073               List<Atom> atomList = molecule.getAtomList();
1074               RefinedOccupancy refinedOccupancy = null;
1075               Atom refinedAtom = null;
1076               for (Atom a : atomList) {
1077                 Character altLoc = a.getAltLoc();
1078                 double occupancy = a.getOccupancy();
1079                 if (!altLoc.equals(' ') || occupancy < 1.0) {
1080                   occupancyAtomList.add(a);
1081                   refinedOccupancy = new RefinedOccupancy(a);
1082                   refinedOccupancies.put(a, refinedOccupancy);
1083                   logger.info(" Refined Occupancy: " + a);
1084                   refinedAtom = a;
1085                   break;
1086                 }
1087               }
1088               if (refinedAtom != null) {
1089                 for (Atom a : atomList) {
1090                   if (a == refinedAtom) {
1091                     continue;
1092                   }
1093                   Character altLoc = a.getAltLoc();
1094                   double occupancy = a.getOccupancy();
1095                   if (!altLoc.equals(' ') || occupancy < 1.0) {
1096                     refinedOccupancy.addConstrainedAtomThatScatters(a);
1097                     logger.info("  Constrained Occupancy: " + a);
1098                   }
1099                 }
1100               }
1101             }
1102           }
1103         }
1104       }
1105     }
1106 
1107     return refinedOccupancies;
1108   }
1109 
1110   /**
1111    * Find residue-based alternate conformers.
1112    *
1113    * @param polymerID Polymer ID.
1114    * @param resID     The residue ID.
1115    * @return The constrained residues.
1116    */
1117   private List<Residue> getResidueConformers(int polymerID, int resID) {
1118     if (molecularAssemblies.length < 2) {
1119       return null;
1120     }
1121 
1122     double totalOccupancy = 0.0;
1123     List<Residue> residues = new ArrayList<>();
1124     // Check the residue from Conformer A.
1125     Residue residue = molecularAssemblies[0].getResidue(polymerID, resID);
1126     for (Atom a : residue.getAtomList()) {
1127       if (!a.getUse()) {
1128         continue;
1129       }
1130       Character altLoc = a.getAltLoc();
1131       double occupancy = a.getOccupancy();
1132       if (!altLoc.equals(' ') || occupancy < 1.0) {
1133         // Include this residue.
1134         logger.fine(format(" %s %c %5.3f", residue, altLoc, occupancy));
1135         totalOccupancy = occupancy;
1136         residues.add(residue);
1137         break;
1138       }
1139     }
1140     // No altLoc found for this residue.
1141     if (residues.isEmpty()) {
1142       return null;
1143     }
1144 
1145     // Find this residue in the other conformers.
1146     int numConformers = molecularAssemblies.length;
1147     for (int i = 1; i < numConformers; i++) {
1148       residue = molecularAssemblies[i].getResidue(polymerID, resID);
1149       for (Atom a : residue.getAtomList()) {
1150         if (!a.getUse()) {
1151           continue;
1152         }
1153         Character altLoc = a.getAltLoc();
1154         if (!altLoc.equals(' ') && !altLoc.equals('A')) {
1155           double occupancy = a.getOccupancy();
1156           totalOccupancy += occupancy;
1157           // Include this residue.
1158           residues.add(residue);
1159           logger.fine(format(" %s %c %5.3f", residue, altLoc, occupancy));
1160           break;
1161         }
1162       }
1163     }
1164 
1165     logger.fine("  Total occupancy: " + totalOccupancy);
1166     return residues;
1167   }
1168 
1169   /**
1170    * Find molecule-based alternate conformers.
1171    *
1172    * @param moleculeID The molecule ID.
1173    * @return The constrained molecules.
1174    */
1175   private List<Molecule> getMoleculeConformers(int moleculeID) {
1176     List<Molecule> molecules = new ArrayList<>();
1177     double totalOccupancy = 0.0;
1178     // Check the molecule from Conformer A.
1179     List<MSNode> molList = molecularAssemblies[0].getMolecules();
1180     if (molList != null && !molList.isEmpty()) {
1181       Molecule molecule = (Molecule) molList.get(moleculeID);
1182       for (Atom a : molecule.getAtomList()) {
1183         if (!a.getUse()) {
1184           continue;
1185         }
1186         Character altLoc = a.getAltLoc();
1187         double occupancy = a.getOccupancy();
1188         if (!altLoc.equals(' ') || occupancy < 1.0) {
1189           // Include this residue.
1190           totalOccupancy = occupancy;
1191           molecules.add(molecule);
1192           logger.fine(format(" %s %c %5.3f", molecule, altLoc, occupancy));
1193           break;
1194         }
1195       }
1196     }
1197 
1198     // No altLoc found for this residue.
1199     if (molecules.isEmpty()) {
1200       return null;
1201     }
1202 
1203     // Find this molecule in the other conformers.
1204     int numConformers = molecularAssemblies.length;
1205     for (int i = 1; i < numConformers; i++) {
1206       molList = molecularAssemblies[i].getMolecules();
1207       Molecule molecule = (Molecule) molList.get(moleculeID);
1208       for (Atom a : molecule.getAtomList()) {
1209         if (!a.getUse()) {
1210           continue;
1211         }
1212         Character altLoc = a.getAltLoc();
1213         if (!altLoc.equals(' ') && !altLoc.equals('A')) {
1214           // Include this residue.
1215           double occupancy = a.getOccupancy();
1216           totalOccupancy += occupancy;
1217           molecules.add(molecule);
1218           logger.fine(format(" %s %c %5.3f", molecule, altLoc, occupancy));
1219           break;
1220         }
1221       }
1222     }
1223 
1224     logger.fine("  Total occupancy: " + totalOccupancy);
1225     return molecules;
1226   }
1227 
1228   /**
1229    * Sets the parameter indices for the current refinement mode.
1230    * <p>
1231    * This method iterates over collections of refined parameters grouped by
1232    * coordinates, B-factors and occupancies to assign an incremental index to each parameter.
1233    * The indices are stored within the respective refined parameter objects.
1234    */
1235   private void setParameterIndices() {
1236     int index = 0;
1237     for (RefinedParameter parameter : allParametersList) {
1238       parameter.setIndex(index);
1239       index += parameter.getNumberOfParameters();
1240     }
1241   }
1242 
1243   /**
1244    * Adjusts the active status of atoms within each molecular assembly.
1245    * For riding hydrogen b-factor refinement, hydrogen atoms adopt the active status of
1246    * their associated heavy atom. For group-based b-factor refinement, if any heavy atom
1247    * in a residue or molecule is active, all its atoms are made active. Otherwise, all
1248    * atoms are inactive.
1249    */
1250   private void regularizeActiveAtoms() {
1251     for (MolecularAssembly molecularAssembly : molecularAssemblies) {
1252       // For riding hydrogen b-factor refinement, hydrogen atoms will adopt the active status of their
1253       // heavy atom.
1254       if (ridingHydrogen) {
1255         for (Atom atom : molecularAssembly.getAtomList()) {
1256           if (atom.isHydrogen()) {
1257             Atom other = atom.getBonds().getFirst().get1_2(atom);
1258             atom.setActive(other.isActive());
1259           }
1260         }
1261       }
1262       // For group-based b-factor refinement, if one heavy atom of a residue or molecule is active, then
1263       // all atoms will be active.
1264       if (byResidue) {
1265         List<MSNode> nodeList = molecularAssembly.getNodeList();
1266         for (MSNode node : nodeList) {
1267           Atom activeAtom = node.getFirstActiveHeavyAtom();
1268           if (activeAtom != null) {
1269             for (Atom atom : node.getAtomList()) {
1270               atom.setActive(true);
1271             }
1272           } else {
1273             // No active heavy atom -- set hydrogen to inactive.
1274             for (Atom atom : node.getAtomList()) {
1275               atom.setActive(false);
1276             }
1277           }
1278         }
1279       }
1280     }
1281   }
1282 
1283   /**
1284    * Adds anisotropic B-factors (ANISOU) to active heavy atoms in the provided molecular
1285    * assemblies that currently lack them.
1286    * Anisotropic B-factors for newly added atoms are initialized isotropically.
1287    */
1288   private void addAnisotropicBFactors() {
1289     if (addAnisou) {
1290       for (MolecularAssembly molecularAssembly : molecularAssemblies) {
1291         int count = 0;
1292         List<Atom> atomList = molecularAssembly.getAtomList();
1293         for (Atom a : atomList) {
1294           // Add an Anisou to each active heavy atom that lacks one.
1295           if (a.isHeavy() && a.isActive() && a.getAnisou(null) == null) {
1296             double[] anisou = new double[6];
1297             double u = b2u(a.getTempFactor());
1298             anisou[0] = u;
1299             anisou[1] = u;
1300             anisou[2] = u;
1301             anisou[3] = 0.0;
1302             anisou[4] = 0.0;
1303             anisou[5] = 0.0;
1304             a.setAnisou(anisou);
1305             count++;
1306           }
1307         }
1308         if (count > 0) {
1309           Character c = molecularAssembly.getAlternateLocation();
1310           logger.info(format(" %d anisotropic B-factors were added to conformer %c.", count, c));
1311         }
1312       }
1313     }
1314   }
1315 
1316 }