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