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