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;
39  
40  import ffx.potential.MolecularAssembly;
41  import ffx.potential.bonded.Atom;
42  import ffx.potential.bonded.MSNode;
43  import ffx.potential.bonded.Molecule;
44  import ffx.potential.bonded.Residue;
45  
46  import java.util.ArrayList;
47  import java.util.List;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  import static java.lang.String.format;
52  
53  /**
54   * RefinementModel class.
55   *
56   * @author Timothy D. Fenn
57   * @since 1.0
58   */
59  public class RefinementModel {
60  
61    private static final Logger logger = Logger.getLogger(RefinementModel.class.getName());
62    /** An atom list. */
63    private final List<Atom> totalAtomList;
64    /** An atom array. */
65    private final Atom[] totalAtomArray;
66    /** An atom list. */
67    private final List<Atom> activeAtomList;
68    /** An atom array. */
69    private final Atom[] activeAtomArray;
70    /** Map between atom in different molecular assemblies. */
71    private final List<List<Integer>> xIndex;
72  
73    private final List<List<Residue>> altResidues;
74    private final List<List<Molecule>> altMolecules;
75  
76    /**
77     * Constructor for RefinementModel.
78     *
79     * @param molecularAssemblies an array of {@link ffx.potential.MolecularAssembly} objects.
80     */
81    public RefinementModel(MolecularAssembly[] molecularAssemblies) {
82      this(molecularAssemblies, false);
83    }
84  
85    /**
86     * Constructor for RefinementModel.
87     *
88     * @param molecularAssemblies an array of {@link ffx.potential.MolecularAssembly} objects.
89     * @param refineMolOcc a boolean.
90     */
91    @SuppressWarnings("unchecked")
92    public RefinementModel(MolecularAssembly[] molecularAssemblies, boolean refineMolOcc) {
93      List<Atom> atomList;
94  
95      // Build alternate conformer list for occupancy refinement (if necessary).
96      altResidues = new ArrayList<>();
97      altMolecules = new ArrayList<>();
98      List<MSNode> nodeList0 = molecularAssemblies[0].getNodeList();
99      List<Residue> tempResidues = null;
100     List<Molecule> tempMolecules = null;
101     boolean alternateConformer;
102 
103     // By residue/molecule.
104     for (int i = 0; i < nodeList0.size(); i++) {
105       alternateConformer = false;
106       MSNode iNode = nodeList0.get(i);
107 
108       // First set up alternate residue restraint list.
109       for (Atom a : iNode.getAtomList()) {
110         if (!a.getUse()) {
111           continue;
112         }
113         if (a.getAltLoc() == null) {
114           logger.severe(format(
115                   " Atom %s has a null alternate location. Likely cause: attempting X-ray refinement from a .xyz file", a));
116         }
117         if (!a.getAltLoc().equals(' ') || a.getOccupancy() < 1.0) {
118           if (iNode instanceof Residue) {
119             tempResidues = new ArrayList<>();
120             tempResidues.add((Residue) iNode);
121             altResidues.add(tempResidues);
122             alternateConformer = true;
123             break;
124           } else if (iNode instanceof Molecule) {
125             if (refineMolOcc) {
126               tempMolecules = new ArrayList<>();
127               tempMolecules.add((Molecule) iNode);
128               altMolecules.add(tempMolecules);
129             }
130             alternateConformer = true;
131             break;
132           }
133         }
134       }
135 
136       // Configure alternate conformers
137       if (alternateConformer) {
138         for (int j = 1; j < molecularAssemblies.length; j++) {
139           List<MSNode> nlist = molecularAssemblies[j].getNodeList();
140           MSNode node = nlist.get(i);
141           for (Atom a : node.getAtomList()) {
142             if (!a.getUse()) {
143               continue;
144             }
145             if (!a.getAltLoc().equals(' ') && !a.getAltLoc().equals('A')) {
146               if (node instanceof Residue) {
147                 if (tempResidues != null) {
148                   tempResidues.add((Residue) node);
149                 }
150                 break;
151               } else if (node instanceof Molecule) {
152                 if (tempMolecules != null) {
153                   tempMolecules.add((Molecule) node);
154                 }
155                 break;
156               }
157             }
158           }
159         }
160       }
161     }
162 
163     // For mapping between atoms between different molecular assemblies.
164     xIndex = new ArrayList<>(molecularAssemblies.length);
165     for (int i = 0; i < molecularAssemblies.length; i++) {
166       xIndex.add(new ArrayList<>());
167     }
168 
169     // Create the atomList to be used for SF calculations.
170     totalAtomList = new ArrayList<>();
171 
172     // Create the activeAtomList (i.e. atoms that can move).
173     activeAtomList = new ArrayList<>();
174 
175     // Root list.
176     atomList = molecularAssemblies[0].getAtomList();
177     int index = 0;
178     for (Atom a : atomList) {
179       if (!a.getUse()) {
180         continue;
181       }
182       totalAtomList.add(a);
183       if (a.isActive()) {
184         activeAtomList.add(a);
185         a.setFormFactorIndex(index);
186         xIndex.getFirst().add(index);
187         index++;
188       }
189     }
190 
191     // Now add cross-references to root and any alternate atoms not in root
192     for (int i = 1; i < molecularAssemblies.length; i++) {
193       atomList = molecularAssemblies[i].getAtomList();
194       for (Atom a : atomList) {
195         if (!a.getUse()) {
196           continue;
197         }
198         Atom root = molecularAssemblies[0].findAtom(a, false);
199         Atom deuteriumMatch = molecularAssemblies[0].findAtom(a, true);
200         if (root != null && root.getAltLoc().equals(a.getAltLoc())) {
201           if (a.isActive()) {
202             xIndex.get(i).add(root.getFormFactorIndex());
203             a.setFormFactorIndex(root.getFormFactorIndex());
204           }
205         } else if (deuteriumMatch != null) {
206           if (a.isActive()) {
207             xIndex.get(i).add(deuteriumMatch.getFormFactorIndex());
208             a.setFormFactorIndex(deuteriumMatch.getFormFactorIndex());
209           }
210         } else {
211           totalAtomList.add(a);
212           if (a.isActive()) {
213             activeAtomList.add(a);
214             a.setFormFactorIndex(index);
215             xIndex.get(i).add(index);
216             index++;
217           }
218         }
219       }
220     }
221 
222     totalAtomArray = totalAtomList.toArray(new Atom[0]);
223     activeAtomArray = activeAtomList.toArray(new Atom[0]);
224 
225     /*
226      Make sure the occupancy values make sense, otherwise print warnings
227      (since this could destabilize the refinement, should we error out?)
228     */
229     for (List<Residue> list : altResidues) {
230       double tocc = 0.0;
231       for (Residue r : list) {
232         for (Atom a : r.getAtomList()) {
233           if (a.getOccupancy() < 1.0 || a.getOccupancy() > 1.0) {
234             tocc += a.getOccupancy();
235             break;
236           }
237         }
238       }
239       if (list.size() == 1) {
240         Residue r = list.getFirst();
241         logger.log(Level.INFO,
242             "  Residue {0} is a single conformer with non-unity occupancy.\n  Occupancy will be refined independently.\n",
243             r.getChainID() + " " + r);
244       } else if (tocc < 1.0 || tocc > 1.0) {
245         Residue r = list.getFirst();
246         logger.log(Level.INFO,
247             "  Residue {0} occupancy does not sum to 1.0.\n  This should be fixed or checked due to possible instability in refinement.\n",
248             r.getChainID() + " " + r);
249       }
250     }
251   }
252 
253   /**
254    * Getter for the field <code>activeAtomArray</code>.
255    *
256    * @return the activeAtomArray
257    */
258   public Atom[] getActiveAtomArray() {
259     return activeAtomArray;
260   }
261 
262   /**
263    * Getter for the field <code>activeAtomList</code>.
264    *
265    * @return the activeAtomList
266    */
267   public List<Atom> getActiveAtomList() {
268     return activeAtomList;
269   }
270 
271   /**
272    * Getter for the field <code>altMolecules</code>.
273    *
274    * @return the altMolecules
275    */
276   public List<List<Molecule>> getAltMolecules() {
277     return altMolecules;
278   }
279 
280   /**
281    * Getter for the field <code>altResidues</code>.
282    *
283    * @return the altResidues
284    */
285   public List<List<Residue>> getAltResidues() {
286     return altResidues;
287   }
288 
289   /**
290    * Getter for the field <code>totalAtomArray</code>.
291    *
292    * @return the totalAtomArray
293    */
294   public Atom[] getTotalAtomArray() {
295     return totalAtomArray;
296   }
297 
298   /**
299    * Getter for the field <code>xIndex</code>.
300    *
301    * @return the xIndex
302    */
303   List<List<Integer>> getxIndex() {
304     return xIndex;
305   }
306 
307   /**
308    * Getter for the field <code>totalAtomList</code>.
309    *
310    * @return the totalAtomList
311    */
312   List<Atom> getTotalAtomList() {
313     return totalAtomList;
314   }
315 }