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-2024.
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.potential.bonded;
39  
40  import ffx.numerics.math.DoubleMath;
41  import ffx.potential.bonded.Residue.ResidueType;
42  import ffx.potential.parameters.ForceField;
43  
44  import java.io.Serial;
45  import java.util.ArrayList;
46  import java.util.Enumeration;
47  import java.util.HashMap;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.Objects;
51  import javax.swing.tree.TreeNode;
52  import org.jogamp.java3d.BranchGroup;
53  import org.jogamp.java3d.Material;
54  import org.jogamp.vecmath.Color3f;
55  
56  /**
57   * The Polymer class encapsulates a peptide or nucleotide chain.
58   *
59   * @author Michael J. Schnieders
60   * @since 1.0
61   */
62  public class Polymer extends MSGroup {
63  
64    @Serial
65    private static final long serialVersionUID = 1L;
66  
67    /** Constant <code>polymerColor</code> */
68    private static final Map<Integer, Color3f> polymerColor = new HashMap<>();
69    /** Polymer count. */
70    private static int count = 0;
71  
72    static {
73      polymerColor.put(0, RendererCache.RED);
74      polymerColor.put(1, RendererCache.ORANGE);
75      polymerColor.put(2, RendererCache.YELLOW);
76      polymerColor.put(3, RendererCache.GREEN);
77      polymerColor.put(4, RendererCache.BLUE);
78      polymerColor.put(5, RendererCache.MAGENTA);
79      polymerColor.put(6, RendererCache.CYAN);
80      polymerColor.put(7, RendererCache.WHITE);
81      polymerColor.put(8, RendererCache.GRAY);
82      polymerColor.put(9, RendererCache.PINK);
83    }
84  
85    public static final String CHAIN_IDS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
86  
87    /** Flag to indicate the residues in the polymer should be joined. */
88    private boolean link = false;
89    /** The number of this Polymer. */
90    private final int polymerNumber;
91    /** The ChainID of this Polymer. */
92    private Character chainID;
93  
94    /**
95     * Polymer constructor.
96     *
97     * @param chainID Possibly redundant PDB chainID.
98     * @param segID Unique identifier from A-Z,0-9, then 1A-1Z,10-19, etc.
99     */
100   public Polymer(Character chainID, String segID) {
101     super(segID);
102     this.chainID = chainID;
103     this.polymerNumber = ++count;
104   }
105 
106   /**
107    * Polymer constructor.
108    *
109    * @param chainID Possibly redundant PDB chainID.
110    * @param segID Unique identifier from A-Z,0-9, then 1A-1Z,10-19, etc.
111    * @param link a boolean.
112    */
113   public Polymer(Character chainID, String segID, boolean link) {
114     this(chainID, segID);
115     this.link = link;
116   }
117 
118   /**
119    * Polymer Constructor.
120    *
121    * @param segID A unique identifier from A-Z,0-9, then 1A-1Z,10-19, etc.
122    * @param residues Represents a MSNode where the Polymer's residues have been attached.
123    * @param chainID a {@link java.lang.Character} object.
124    */
125   public Polymer(Character chainID, String segID, MSNode residues) {
126     super(segID, residues);
127     this.chainID = chainID;
128     polymerNumber = ++count;
129   }
130 
131   /**
132    * {@inheritDoc}
133    *
134    * <p>A generic method for adding a MSNode to the Polymer.
135    */
136   @Override
137   public MSNode addMSNode(MSNode msNode) {
138     assert (msNode instanceof Residue);
139 
140     Residue residue = (Residue) msNode;
141     int resNumber = residue.getResidueNumber();
142 
143     MSNode residueNode = getAtomNode();
144     int n = residueNode.getChildCount();
145     int childIndex = n;
146 
147     for (int i = 0; i < n; i++) {
148       Residue current = (Residue) residueNode.getChildAt(i);
149       if (current.getResidueNumber() > resNumber) {
150         childIndex = i;
151         break;
152       }
153     }
154 
155     residueNode.insert(residue, childIndex);
156 
157     residue.setChainID(chainID);
158 
159     return msNode;
160   }
161 
162   /**
163    * Set the Polymer chainID. The residues that form the polymer are also updated.
164    *
165    * @param chainID The chainID to use.
166    */
167   public void setChainID(Character chainID) {
168     this.chainID = chainID;
169     for (Residue residue : getResidues()) {
170       residue.setChainID(chainID);
171     }
172   }
173 
174   /**
175    * Set the Polymer segID. The residues that form the polymer are also updated.
176    *
177    * @param segID The segID to use.
178    */
179   public void setSegID(String segID) {
180     setName(segID);
181     for (Residue residue : getResidues()) {
182       residue.setSegID(segID);
183     }
184   }
185 
186   /**
187    * addMultiResidue.
188    *
189    * @param multiResidue a {@link ffx.potential.bonded.MultiResidue} object.
190    */
191   public void addMultiResidue(MultiResidue multiResidue) {
192     Residue residue = multiResidue.getActive();
193     MSNode residueNode = getAtomNode();
194     int index = residueNode.getIndex(residue);
195 
196     if (index < 0) {
197       System.err.println(
198           "WARNING!  Polymer::addMultiResidue did not find a corresponding Residue on Polymer.");
199       residueNode.add(multiResidue);
200     } else {
201       residue.removeFromParent();
202       residueNode.insert(multiResidue, index);
203       multiResidue.add(residue);
204     }
205   }
206 
207   /**
208    * {@inheritDoc}
209    *
210    * <p>Joiner joins Moieties m1 and m2 and returns the Geometry objects formed in a Joint.
211    */
212   public Joint createJoint(Residue residue1, Residue residue2, ForceField forceField) {
213     Joint joint = null;
214     double[] da = new double[3];
215     double[] db = new double[3];
216     for (Enumeration<TreeNode> e = residue1.getAtomNode().children(); e.hasMoreElements(); ) {
217       Atom a1 = (Atom) e.nextElement();
218       a1.getXYZ(da);
219       for (Enumeration<TreeNode> e2 = residue2.getAtomNode().children(); e2.hasMoreElements(); ) {
220         Atom a2 = (Atom) e2.nextElement();
221         a2.getXYZ(db);
222         double d1 = DoubleMath.dist(da, db);
223         double d2 = Bond.BUFF + a1.getVDWR() / 2 + a2.getVDWR() / 2;
224         if (d1 < d2) {
225           Bond b = new Bond(a1, a2);
226           Joint newJoint = createJoint(b, residue1, residue2, forceField);
227           if (joint != null) {
228             joint.merge(newJoint);
229           } else {
230             joint = newJoint;
231           }
232         }
233       }
234     }
235     return joint;
236   }
237 
238   /**
239    * {@inheritDoc}
240    *
241    * <p>Overridden equals method.
242    */
243   @Override
244   public boolean equals(Object o) {
245     if (this == o) {
246       return true;
247     }
248     if (o == null || getClass() != o.getClass()) {
249       return false;
250     }
251     Polymer polymer = (Polymer) o;
252     return polymerNumber == polymer.polymerNumber && Objects.equals(getName(), polymer.getName());
253   }
254 
255   /**
256    * {@inheritDoc}
257    *
258    * <p>Finalize should be called after all the Residues have been added to the Polymer. This method
259    * in turn calls the Finalize method of each Residue, then forms Joints between adjacent Residues
260    * in the Polymer
261    */
262   @Override
263   public void finalize(boolean finalizeGroups, ForceField forceField) {
264     List<MSNode> residues = getAtomNodeList();
265     setFinalized(false);
266 
267     // Finalize the residues in the Polymer
268     if (finalizeGroups) {
269       for (MSNode node : residues) {
270         Residue residue = (Residue) node;
271         residue.finalize(true, forceField);
272       }
273     }
274     // Join the residues in the Polymer
275     if (link) {
276       Residue residue = getFirstResidue();
277       if (residue.residueType == Residue.ResidueType.AA) {
278         getAtomNode().setName("Amino Acids " + "(" + residues.size() + ")");
279       } else if (residue.residueType == Residue.ResidueType.NA) {
280         getAtomNode().setName("Nucleic Acids " + "(" + residues.size() + ")");
281       } else {
282         getAtomNode().setName("Residues " + "(" + residues.size() + ")");
283       }
284 
285       Joint j;
286       MSNode joints = getTermNode();
287       joints.removeAllChildren();
288       List<Atom> atoms = getAtomList();
289       for (Atom a : atoms) {
290         if (a.getNumBonds() > 0) {
291           for (Bond b : a.getBonds()) {
292             if (!b.sameGroup() && b.getParent() == null) {
293               Residue r1 = a.getMSNode(Residue.class);
294               Residue r2 = b.get1_2(a).getMSNode(Residue.class);
295               j = createJoint(b, r1, r2, forceField);
296               joints.add(j);
297             }
298           }
299         }
300       }
301 
302       if (residue.residueType == Residue.ResidueType.AA) {
303         getTermNode().setName("Peptide Bonds " + "(" + joints.getChildCount() + ")");
304       } else {
305         getTermNode().setName("Linkages " + "(" + joints.getChildCount() + ")");
306       }
307 
308     } else {
309       getAtomNode().setName("Sub-Groups " + "(" + residues.size() + ")");
310       if (getTermNode().getParent() != null) {
311         removeChild(getTermNode());
312       }
313     }
314     removeLeaves();
315     setFinalized(true);
316   }
317 
318   /**
319    * Getter for the field <code>chainID</code>.
320    *
321    * @return a {@link java.lang.Character} object.
322    */
323   public Character getChainID() {
324     return chainID;
325   }
326 
327   /**
328    * getFirstResidue
329    *
330    * @return a {@link ffx.potential.bonded.Residue} object.
331    */
332   public Residue getFirstResidue() {
333     MSNode atomNode = getAtomNode();
334     if (atomNode == null) {
335       return null;
336     }
337     return (Residue) atomNode.getChildAt(0);
338   }
339 
340   /**
341    * Getter for the field <code>link</code>.
342    *
343    * @return a boolean.
344    */
345   public boolean getLink() {
346     return link;
347   }
348 
349   /**
350    * Setter for the field <code>link</code>.
351    *
352    * @param link a boolean.
353    */
354   public void setLink(boolean link) {
355     this.link = link;
356   }
357 
358   /**
359    * Get lists of the phi and psi torsions.
360    *
361    * @return A List of Dihedral objects representing the Phi/Psi angles of the Polymer.
362    */
363   public List<List<Torsion>> getPhiPsiList() {
364     List<List<Torsion>> phiPsi = new ArrayList<>();
365     List<Torsion> phi = new ArrayList<>();
366     List<Torsion> psi = new ArrayList<>();
367     phiPsi.add(phi);
368     phiPsi.add(psi);
369     for (Residue residue : this.getResidues()) {
370       for (Torsion torsion : residue.getTorsionList()) {
371         Atom[] atoms = torsion.atoms;
372         StringBuilder s = new StringBuilder(atoms[0].getName());
373         for (int i = 1; i < 4; i++) {
374           s.append("-").append(atoms[i].getName());
375         }
376         // Phi
377         if (s.toString().equals("C-N-CA-C") || s.toString().equals("C-CA-N-C")) {
378           phi.add(torsion);
379         } // Psi
380         else if (s.toString().equals("N-C-CA-N") || s.toString().equals("N-CA-C-N")) {
381           psi.add(torsion);
382         }
383       }
384     }
385     return phiPsi;
386   }
387 
388   /**
389    * getResidue
390    *
391    * @param resNum The residue number.
392    * @return a {@link ffx.potential.bonded.Residue} object.
393    */
394   public Residue getResidue(int resNum) {
395     if (resNum > 0 && getAtomNode().getChildCount() >= resNum) {
396       Residue r = (Residue) getAtomNode().getChildAt(resNum - 1);
397       if (r.getResidueNumber() == resNum) {
398         return r;
399       }
400     }
401     // Fall back for non-ordered children
402     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
403       Residue r = (Residue) e.nextElement();
404       if (r.getResidueNumber() == resNum) {
405         return r;
406       }
407     }
408     return null;
409   }
410 
411   /**
412    * getResidue
413    *
414    * @param resName a {@link java.lang.String} object.
415    * @param resNum The residue number.
416    * @param create a boolean.
417    * @return a {@link ffx.potential.bonded.Residue} object.
418    */
419   public Residue getResidue(String resName, int resNum, boolean create) {
420     return getResidue(resName, resNum, create, Residue.ResidueType.UNK);
421   }
422 
423   /**
424    * getResidue
425    *
426    * @param resName The residue name.
427    * @param resNum The residue number.
428    * @param create If true, create the residue if it does not exist.
429    * @param defaultRT Default ResidueType if it cannot be assigned.
430    * @return a {@link ffx.potential.bonded.Residue} object.
431    */
432   public Residue getResidue(String resName, int resNum, boolean create, ResidueType defaultRT) {
433     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
434       Residue r = (Residue) e.nextElement();
435       if (r.getResidueNumber() == resNum && r.getName().equalsIgnoreCase(resName)) {
436         return r;
437       } else if (resName.equals(AminoAcidUtils.AminoAcid3.UNK.name())
438           && resNum == r.getResidueNumber()) {
439         return r;
440       }
441     }
442     if (!create) {
443       return null;
444     }
445     Residue residue = null;
446     resName = resName.toUpperCase();
447     if (resName.length() == 1) {
448       try {
449         NucleicAcidUtils.NucleicAcid1.valueOf(resName);
450         residue = new Residue(resName, resNum, Residue.ResidueType.NA, chainID, getName());
451       } catch (Exception e) {
452         try {
453           AminoAcidUtils.AminoAcid1.valueOf(resName);
454           residue = new Residue(resName, resNum, Residue.ResidueType.AA, chainID, getName());
455         } catch (Exception ex) {
456           //
457         }
458       }
459     } else if (resName.length() >= 2) {
460       try {
461         NucleicAcidUtils.NucleicAcid3.valueOf(resName);
462         residue = new Residue(resName, resNum, Residue.ResidueType.NA, chainID, getName());
463       } catch (Exception e) {
464         try {
465           AminoAcidUtils.AminoAcid3.valueOf(resName);
466           residue = new Residue(resName, resNum, Residue.ResidueType.AA, chainID, getName());
467         } catch (Exception ex) {
468           //
469         }
470       }
471     }
472     if (residue == null) {
473       residue = new Residue(resName, resNum, defaultRT, chainID, getName());
474     }
475     addMSNode(residue);
476     return residue;
477   }
478 
479   /**
480    * getResidues
481    *
482    * @return a {@link java.util.List} object.
483    */
484   public List<Residue> getResidues() {
485     List<Residue> residues = new ArrayList<>();
486     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
487       Residue r = (Residue) e.nextElement();
488       residues.add(r);
489     }
490     return residues;
491   }
492 
493   /** {@inheritDoc} */
494   @Override
495   public int hashCode() {
496     return Objects.hash(polymerNumber, getName());
497   }
498 
499   /** {@inheritDoc} */
500   @Override
501   public void setColor(RendererCache.ColorModel newColorModel, Color3f color, Material mat) {
502     // If coloring by Polymer, pass this Polymer's color
503     if (newColorModel == RendererCache.ColorModel.POLYMER) {
504       int index = polymerNumber % 10;
505       color = polymerColor.get(index);
506       mat = RendererCache.materialFactory(color);
507     }
508     for (MSNode node : getAtomNodeList()) {
509       MSGroup atomGroup = (MSGroup) node;
510       atomGroup.setColor(newColorModel, color, mat);
511     }
512     for (Enumeration<TreeNode> e = getTermNode().children(); e.hasMoreElements(); ) {
513       Joint joint = (Joint) e.nextElement();
514       joint.setColor(newColorModel);
515     }
516   }
517 
518   /** {@inheritDoc} */
519   @Override
520   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
521     for (MSNode node : getAtomNodeList()) {
522       MSGroup atomGroup = (MSGroup) node;
523       atomGroup.setView(newViewModel, newShapes);
524     }
525     for (Enumeration<TreeNode> e = getTermNode().children(); e.hasMoreElements(); ) {
526       Joint joint = (Joint) e.nextElement();
527       joint.setView(newViewModel, newShapes);
528     }
529   }
530 }