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.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    * Form a Joint between two residues.
209    *
210    * @param residue1 The first Residue.
211    * @param residue2 The second Residue.
212    * @param forceField The ForceField to use for the Joint.
213    * @return a {@link ffx.potential.bonded.Joint} object.
214    */
215   public Joint createJoint(Residue residue1, Residue residue2, ForceField forceField) {
216     Joint joint = null;
217     double[] da = new double[3];
218     double[] db = new double[3];
219     for (Enumeration<TreeNode> e = residue1.getAtomNode().children(); e.hasMoreElements(); ) {
220       Atom a1 = (Atom) e.nextElement();
221       a1.getXYZ(da);
222       for (Enumeration<TreeNode> e2 = residue2.getAtomNode().children(); e2.hasMoreElements(); ) {
223         Atom a2 = (Atom) e2.nextElement();
224         a2.getXYZ(db);
225         double d1 = DoubleMath.dist(da, db);
226         double d2 = Bond.BUFF + a1.getVDWR() / 2 + a2.getVDWR() / 2;
227         if (d1 < d2) {
228           Bond b = new Bond(a1, a2);
229           Joint newJoint = createJoint(b, residue1, residue2, forceField);
230           if (joint != null) {
231             joint.merge(newJoint);
232           } else {
233             joint = newJoint;
234           }
235         }
236       }
237     }
238     return joint;
239   }
240 
241   /**
242    * {@inheritDoc}
243    *
244    * <p>Overridden equals method.
245    */
246   @Override
247   public boolean equals(Object o) {
248     if (this == o) {
249       return true;
250     }
251     if (o == null || getClass() != o.getClass()) {
252       return false;
253     }
254     Polymer polymer = (Polymer) o;
255     return polymerNumber == polymer.polymerNumber && Objects.equals(getName(), polymer.getName());
256   }
257 
258   /**
259    * {@inheritDoc}
260    *
261    * <p>Finalize should be called after all the Residues have been added to the Polymer. This method
262    * in turn calls the Finalize method of each Residue, then forms Joints between adjacent Residues
263    * in the Polymer
264    */
265   @Override
266   public void finalize(boolean finalizeGroups, ForceField forceField) {
267     List<MSNode> residues = getAtomNodeList();
268     setFinalized(false);
269 
270     // Finalize the residues in the Polymer
271     if (finalizeGroups) {
272       for (MSNode node : residues) {
273         Residue residue = (Residue) node;
274         residue.finalize(true, forceField);
275       }
276     }
277     // Join the residues in the Polymer
278     if (link) {
279       Residue residue = getFirstResidue();
280       if (residue.residueType == Residue.ResidueType.AA) {
281         getAtomNode().setName("Amino Acids " + "(" + residues.size() + ")");
282       } else if (residue.residueType == Residue.ResidueType.NA) {
283         getAtomNode().setName("Nucleic Acids " + "(" + residues.size() + ")");
284       } else {
285         getAtomNode().setName("Residues " + "(" + residues.size() + ")");
286       }
287 
288       Joint j;
289       MSNode joints = getTermNode();
290       joints.removeAllChildren();
291       List<Atom> atoms = getAtomList();
292       for (Atom a : atoms) {
293         if (a.getNumBonds() > 0) {
294           for (Bond b : a.getBonds()) {
295             if (!b.sameGroup() && b.getParent() == null) {
296               Residue r1 = a.getMSNode(Residue.class);
297               Residue r2 = b.get1_2(a).getMSNode(Residue.class);
298               j = createJoint(b, r1, r2, forceField);
299               joints.add(j);
300             }
301           }
302         }
303       }
304 
305       if (residue.residueType == Residue.ResidueType.AA) {
306         getTermNode().setName("Peptide Bonds " + "(" + joints.getChildCount() + ")");
307       } else {
308         getTermNode().setName("Linkages " + "(" + joints.getChildCount() + ")");
309       }
310 
311     } else {
312       getAtomNode().setName("Sub-Groups " + "(" + residues.size() + ")");
313       if (getTermNode().getParent() != null) {
314         removeChild(getTermNode());
315       }
316     }
317     removeLeaves();
318     setFinalized(true);
319   }
320 
321   /**
322    * Getter for the field <code>chainID</code>.
323    *
324    * @return a {@link java.lang.Character} object.
325    */
326   public Character getChainID() {
327     return chainID;
328   }
329 
330   /**
331    * getFirstResidue
332    *
333    * @return a {@link ffx.potential.bonded.Residue} object.
334    */
335   public Residue getFirstResidue() {
336     MSNode atomNode = getAtomNode();
337     if (atomNode == null) {
338       return null;
339     }
340     return (Residue) atomNode.getChildAt(0);
341   }
342 
343   /**
344    * Getter for the field <code>link</code>.
345    *
346    * @return a boolean.
347    */
348   public boolean getLink() {
349     return link;
350   }
351 
352   /**
353    * Setter for the field <code>link</code>.
354    *
355    * @param link a boolean.
356    */
357   public void setLink(boolean link) {
358     this.link = link;
359   }
360 
361   /**
362    * Get lists of the phi and psi torsions.
363    *
364    * @return A List of Dihedral objects representing the Phi/Psi angles of the Polymer.
365    */
366   public List<List<Torsion>> getPhiPsiList() {
367     List<List<Torsion>> phiPsi = new ArrayList<>();
368     List<Torsion> phi = new ArrayList<>();
369     List<Torsion> psi = new ArrayList<>();
370     phiPsi.add(phi);
371     phiPsi.add(psi);
372     for (Residue residue : this.getResidues()) {
373       for (Torsion torsion : residue.getTorsionList()) {
374         Atom[] atoms = torsion.atoms;
375         StringBuilder s = new StringBuilder(atoms[0].getName());
376         for (int i = 1; i < 4; i++) {
377           s.append("-").append(atoms[i].getName());
378         }
379         // Phi
380         if (s.toString().equals("C-N-CA-C") || s.toString().equals("C-CA-N-C")) {
381           phi.add(torsion);
382         } // Psi
383         else if (s.toString().equals("N-C-CA-N") || s.toString().equals("N-CA-C-N")) {
384           psi.add(torsion);
385         }
386       }
387     }
388     return phiPsi;
389   }
390 
391   /**
392    * getResidue
393    *
394    * @param resNum The residue number.
395    * @return a {@link ffx.potential.bonded.Residue} object.
396    */
397   public Residue getResidue(int resNum) {
398     if (resNum > 0 && getAtomNode().getChildCount() >= resNum) {
399       Residue r = (Residue) getAtomNode().getChildAt(resNum - 1);
400       if (r.getResidueNumber() == resNum) {
401         return r;
402       }
403     }
404     // Fall back for non-ordered children
405     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
406       Residue r = (Residue) e.nextElement();
407       if (r.getResidueNumber() == resNum) {
408         return r;
409       }
410     }
411     return null;
412   }
413 
414   /**
415    * getResidue
416    *
417    * @param resName a {@link java.lang.String} object.
418    * @param resNum The residue number.
419    * @param create a boolean.
420    * @return a {@link ffx.potential.bonded.Residue} object.
421    */
422   public Residue getResidue(String resName, int resNum, boolean create) {
423     return getResidue(resName, resNum, create, Residue.ResidueType.UNK);
424   }
425 
426   /**
427    * getResidue
428    *
429    * @param resName The residue name.
430    * @param resNum The residue number.
431    * @param create If true, create the residue if it does not exist.
432    * @param defaultRT Default ResidueType if it cannot be assigned.
433    * @return a {@link ffx.potential.bonded.Residue} object.
434    */
435   public Residue getResidue(String resName, int resNum, boolean create, ResidueType defaultRT) {
436     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
437       Residue r = (Residue) e.nextElement();
438       if (r.getResidueNumber() == resNum && r.getName().equalsIgnoreCase(resName)) {
439         return r;
440       } else if (resName.equals(AminoAcidUtils.AminoAcid3.UNK.name())
441           && resNum == r.getResidueNumber()) {
442         return r;
443       }
444     }
445     if (!create) {
446       return null;
447     }
448     Residue residue = null;
449     resName = resName.toUpperCase();
450     if (resName.length() == 1) {
451       try {
452         NucleicAcidUtils.NucleicAcid1.valueOf(resName);
453         residue = new Residue(resName, resNum, Residue.ResidueType.NA, chainID, getName());
454       } catch (Exception e) {
455         try {
456           AminoAcidUtils.AminoAcid1.valueOf(resName);
457           residue = new Residue(resName, resNum, Residue.ResidueType.AA, chainID, getName());
458         } catch (Exception ex) {
459           //
460         }
461       }
462     } else if (resName.length() >= 2) {
463       try {
464         NucleicAcidUtils.NucleicAcid3.valueOf(resName);
465         residue = new Residue(resName, resNum, Residue.ResidueType.NA, chainID, getName());
466       } catch (Exception e) {
467         try {
468           AminoAcidUtils.AminoAcid3.valueOf(resName);
469           residue = new Residue(resName, resNum, Residue.ResidueType.AA, chainID, getName());
470         } catch (Exception ex) {
471           //
472         }
473       }
474     }
475     if (residue == null) {
476       residue = new Residue(resName, resNum, defaultRT, chainID, getName());
477     }
478     addMSNode(residue);
479     return residue;
480   }
481 
482   /**
483    * getResidues
484    *
485    * @return a {@link java.util.List} object.
486    */
487   public List<Residue> getResidues() {
488     List<Residue> residues = new ArrayList<>();
489     for (Enumeration<TreeNode> e = getAtomNode().children(); e.hasMoreElements(); ) {
490       Residue r = (Residue) e.nextElement();
491       residues.add(r);
492     }
493     return residues;
494   }
495 
496   /** {@inheritDoc} */
497   @Override
498   public int hashCode() {
499     return Objects.hash(polymerNumber, getName());
500   }
501 
502   /** {@inheritDoc} */
503   @Override
504   public void setColor(RendererCache.ColorModel newColorModel, Color3f color, Material mat) {
505     // If coloring by Polymer, pass this Polymer's color
506     if (newColorModel == RendererCache.ColorModel.POLYMER) {
507       int index = polymerNumber % 10;
508       color = polymerColor.get(index);
509       mat = RendererCache.materialFactory(color);
510     }
511     for (MSNode node : getAtomNodeList()) {
512       MSGroup atomGroup = (MSGroup) node;
513       atomGroup.setColor(newColorModel, color, mat);
514     }
515     for (Enumeration<TreeNode> e = getTermNode().children(); e.hasMoreElements(); ) {
516       Joint joint = (Joint) e.nextElement();
517       joint.setColor(newColorModel);
518     }
519   }
520 
521   /** {@inheritDoc} */
522   @Override
523   public void setView(RendererCache.ViewModel newViewModel, List<BranchGroup> newShapes) {
524     for (MSNode node : getAtomNodeList()) {
525       MSGroup atomGroup = (MSGroup) node;
526       atomGroup.setView(newViewModel, newShapes);
527     }
528     for (Enumeration<TreeNode> e = getTermNode().children(); e.hasMoreElements(); ) {
529       Joint joint = (Joint) e.nextElement();
530       joint.setView(newViewModel, newShapes);
531     }
532   }
533 }