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 static ffx.potential.bonded.AminoAcidUtils.AA1toAA3;
41  import static ffx.potential.bonded.NucleicAcidUtils.NA1toNA3;
42  import static java.lang.System.arraycopy;
43  
44  import ffx.potential.bonded.AminoAcidUtils.AminoAcid1;
45  import ffx.potential.bonded.AminoAcidUtils.AminoAcid3;
46  import ffx.potential.bonded.NucleicAcidUtils.NucleicAcid1;
47  import ffx.potential.bonded.NucleicAcidUtils.NucleicAcid3;
48  import ffx.potential.parameters.ForceField;
49  import ffx.potential.parameters.TitrationUtils;
50  import java.util.ArrayList;
51  import java.util.Comparator;
52  import java.util.HashMap;
53  import java.util.List;
54  import java.util.Objects;
55  import java.util.logging.Level;
56  import java.util.logging.Logger;
57  import org.jogamp.java3d.Canvas3D;
58  import org.jogamp.java3d.J3DGraphics2D;
59  import org.jogamp.java3d.Material;
60  import org.jogamp.java3d.Node;
61  import org.jogamp.vecmath.Color3f;
62  import org.jogamp.vecmath.Point2d;
63  import org.jogamp.vecmath.Point3d;
64  
65  /**
66   * The Residue class represents individual amino acids or nucleic acid bases.
67   *
68   * @author Michael J. Schnieders
69   * @author Jacob M. Litman
70   * @since 1.0
71   */
72  public class Residue extends MSGroup implements Comparable<Residue> {
73  
74    private static final Logger logger = Logger.getLogger(Residue.class.getName());
75    /** Compare residues first on seg ID, then residue number, then residue type, then name. */
76    private static final Comparator<Residue> resComparator =
77        Comparator.comparing(Residue::getSegID)
78            .thenComparingInt(Residue::getResidueNumber)
79            .thenComparing(Residue::getResidueType)
80            .thenComparing(Residue::getName);
81  
82    /** Constant <code>NA3Color</code> */
83    private static final HashMap<NucleicAcid3, Color3f> NA3Color = new HashMap<>();
84    /** Constant <code>AA3Color</code> */
85    private static final HashMap<AminoAcid3, Color3f> AA3Color = new HashMap<>();
86    /** Constant <code>SSTypeColor</code> */
87    private static final HashMap<SSType, Color3f> SSTypeColor = new HashMap<>();
88    /** Constant <code>Ramachandran="new String[17]"</code> */
89    public static String[] Ramachandran = new String[17];
90  
91    private static final Point3d point3d = new Point3d();
92    private static final Point2d point2d = new Point2d();
93  
94    static {
95      NA3Color.put(NucleicAcid3.ADE, RendererCache.RED);
96      NA3Color.put(NucleicAcid3.CYT, RendererCache.MAGENTA);
97      NA3Color.put(NucleicAcid3.GUA, RendererCache.BLUE);
98      NA3Color.put(NucleicAcid3.URI, RendererCache.YELLOW);
99      NA3Color.put(NucleicAcid3.DAD, RendererCache.RED);
100     NA3Color.put(NucleicAcid3.DCY, RendererCache.MAGENTA);
101     NA3Color.put(NucleicAcid3.DGU, RendererCache.BLUE);
102     NA3Color.put(NucleicAcid3.DTY, RendererCache.ORANGE);
103     NA3Color.put(NucleicAcid3.MP1, RendererCache.GREEN);
104     NA3Color.put(NucleicAcid3.DP2, RendererCache.GREEN);
105     NA3Color.put(NucleicAcid3.TP3, RendererCache.GREEN);
106     NA3Color.put(NucleicAcid3.UNK, RendererCache.CYAN);
107 
108     AA3Color.put(AminoAcid3.ALA, RendererCache.GRAY);
109     AA3Color.put(AminoAcid3.ARG, RendererCache.BLUE);
110     AA3Color.put(AminoAcid3.ASN, RendererCache.BLUE);
111     AA3Color.put(AminoAcid3.ASP, RendererCache.RED);
112     AA3Color.put(AminoAcid3.CYS, RendererCache.YELLOW);
113     AA3Color.put(AminoAcid3.GLN, RendererCache.BLUE);
114     AA3Color.put(AminoAcid3.GLU, RendererCache.RED);
115     AA3Color.put(AminoAcid3.GLH, RendererCache.RED);
116     AA3Color.put(AminoAcid3.GLD, RendererCache.RED);
117     AA3Color.put(AminoAcid3.GLY, RendererCache.GRAY);
118     AA3Color.put(AminoAcid3.ILE, RendererCache.GRAY);
119     AA3Color.put(AminoAcid3.LEU, RendererCache.GRAY);
120     AA3Color.put(AminoAcid3.LYS, RendererCache.BLUE);
121     AA3Color.put(AminoAcid3.MET, RendererCache.YELLOW);
122     AA3Color.put(AminoAcid3.PHE, RendererCache.GREEN);
123     AA3Color.put(AminoAcid3.PRO, RendererCache.ORANGE);
124     AA3Color.put(AminoAcid3.SER, RendererCache.BLUE);
125     AA3Color.put(AminoAcid3.THR, RendererCache.BLUE);
126     AA3Color.put(AminoAcid3.TRP, RendererCache.GREEN);
127     AA3Color.put(AminoAcid3.TYR, RendererCache.GREEN);
128     AA3Color.put(AminoAcid3.VAL, RendererCache.GRAY);
129     AA3Color.put(AminoAcid3.HIS, RendererCache.BLUE);
130     AA3Color.put(AminoAcid3.HIE, RendererCache.BLUE);
131     AA3Color.put(AminoAcid3.HID, RendererCache.BLUE);
132     AA3Color.put(AminoAcid3.ORN, RendererCache.ORANGE);
133     AA3Color.put(AminoAcid3.AIB, RendererCache.ORANGE);
134     AA3Color.put(AminoAcid3.PCA, RendererCache.ORANGE);
135     AA3Color.put(AminoAcid3.FOR, RendererCache.RED);
136     AA3Color.put(AminoAcid3.ACE, RendererCache.RED);
137     AA3Color.put(AminoAcid3.NH2, RendererCache.BLUE);
138     AA3Color.put(AminoAcid3.NME, RendererCache.BLUE);
139     AA3Color.put(AminoAcid3.UNK, RendererCache.MAGENTA);
140 
141     SSTypeColor.put(SSType.NONE, RendererCache.WHITE);
142     SSTypeColor.put(SSType.SHEET, RendererCache.PINK);
143     SSTypeColor.put(SSType.HELIX, RendererCache.BLUE);
144     SSTypeColor.put(SSType.TURN, RendererCache.YELLOW);
145 
146     Ramachandran[0] = "Default (Extended)       [-135.0  135.0]";
147     Ramachandran[1] = "Alpha Helix (R)          [ -57.0  -47.0]";
148     Ramachandran[2] = "Alpha Helix (L)          [  57.0   47.0]";
149     Ramachandran[3] = "3-10 Helix               [ -49.0  -26.0]";
150     Ramachandran[4] = "Pi Helix                 [ -57.0  -70.0]";
151     Ramachandran[5] = "Polyproline II Helix     [ -79.0  149.0]";
152     Ramachandran[6] = "Parallel Beta Strand     [-119.0  113.0]";
153     Ramachandran[7] = "Antiparallel Beta Strand [-139.0  135.0]";
154     Ramachandran[8] = "Beta-Hairpin 2' (i+1)    [  90.0 -170.0]";
155     Ramachandran[9] = "Beta-Hairpin 2' (i+2)    [ -80.0  -10.0]";
156     Ramachandran[10] = "Beta-Hairpin 1' (i+1)    [  57.0   47.0]";
157     Ramachandran[11] = "Beta-Hairpin 1' (i+2)    [  57.0   47.0]";
158     Ramachandran[12] = "Beta-Hairpin 1  (i+1)    [ -57.0  -47.0]";
159     Ramachandran[13] = "Beta-Hairpin 1  (i+2)    [ -57.0  -47.0]";
160     Ramachandran[14] = "Beta-Hairpin 1  (i+3)    [  90.0 -170.0]";
161     Ramachandran[15] = "Beta-Hairpin 3' (i+1)    [  57.0   47.0]";
162     Ramachandran[16] = "Beta-Hairpin 3' (i+2)    [ -80.0  -10.0]";
163   }
164 
165   /** Residue type. */
166   ResidueType residueType;
167   /** The residue number of this residue in a chain. */
168   private int resNumber;
169   /** Possibly redundant PDB chain ID. */
170   private Character chainID;
171   /** Unique segID. */
172   private String segID;
173   /** The rotamers for this residue. */
174   private Rotamer[] rotamers = null;
175   /** The current rotamer in use. */
176   private Rotamer currentRotamer = null;
177   /** Short string describing this residue. */
178   private String shortString = null;
179   /** 3-letter amino acid code. */
180   private AminoAcid3 aa;
181   /** 3-letter nucleic acid code. */
182   private NucleicAcid3 na;
183   /**
184    * If this is set, then ASP, GLU, LYS and HIS Rotamers will be titratable.
185    */
186   private TitrationUtils titrationUtils = null;
187 
188   /**
189    * These arrays store default coordinates for certain atoms in nucleic acid Residues. C1', O4', and
190    * C4' are the critical sugar atoms off which every other atom is drawn when applyRotamer is
191    * called. The backbone corrections, however, move these atoms, so they must be reverted to their
192    * original coordinates each time applyRotamer is called.
193    *
194    * <p>O3' North and South coordinates are technically non-essential, as they could be derived from
195    * C1', O4', C4', and a given sugar pucker, however, it is much less computationally expensive to
196    * calculate them once and then store them.
197    *
198    * <p>TODO: Add O3' coordinates for the DNA C3'-exo configuration.
199    */
200   private double[] O3sNorthCoords, O3sSouthCoords, C1sCoords, O4sCoords, C4sCoords;
201 
202   /**
203    * Default Constructor where num is this Residue's position in the Polymer.
204    *
205    * @param num a int.
206    * @param rt a {@link ResidueType} object.
207    */
208   public Residue(int num, ResidueType rt) {
209     super();
210     resNumber = num;
211     residueType = rt;
212     assignResidueType();
213   }
214 
215   /**
216    * Constructor for Residue.
217    *
218    * @param name a {@link java.lang.String} object.
219    * @param rt a {@link ResidueType} object.
220    */
221   public Residue(String name, ResidueType rt) {
222     super(name);
223     residueType = rt;
224     assignResidueType();
225   }
226 
227   /**
228    * Name is the residue's 3 letter abbreviation and num is its position in the Polymer.
229    *
230    * @param name a {@link java.lang.String} object.
231    * @param num a int.
232    * @param rt a {@link ResidueType} object.
233    */
234   public Residue(String name, int num, ResidueType rt) {
235     this(name, rt);
236     resNumber = num;
237   }
238 
239   /**
240    * Name is the residue's 3 letter abbreviation and num is its position in the Polymer.
241    *
242    * @param name a {@link java.lang.String} object.
243    * @param resNumber a int.
244    * @param rt a {@link ResidueType} object.
245    * @param chainID a {@link java.lang.Character} object.
246    * @param segID a {@link java.lang.String} object.
247    */
248   public Residue(String name, int resNumber, ResidueType rt, Character chainID, String segID) {
249     this(name, rt);
250     this.resNumber = resNumber;
251     this.chainID = chainID;
252     this.segID = segID;
253   }
254 
255   /**
256    * As above, with atoms being a FNode with this Residue's atoms as child nodes
257    *
258    * @param name a {@link java.lang.String} object.
259    * @param num a int.
260    * @param atoms a {@link ffx.potential.bonded.MSNode} object.
261    * @param rt a {@link ResidueType} object.
262    * @param forceField the ForceField to use when created bonded terms.
263    */
264   public Residue(String name, int num, MSNode atoms, ResidueType rt, ForceField forceField) {
265     super(name, atoms);
266     resNumber = num;
267     residueType = rt;
268     assignResidueType();
269     finalize(true, forceField);
270   }
271 
272   @Override
273   public void setName(String name) {
274     super.setName(name);
275     if (residueType != null) {
276       assignResidueType();
277     }
278     for (Atom atom : getAtomList()) {
279       atom.setResName(name);
280     }
281   }
282 
283   /**
284    * {@inheritDoc}
285    *
286    * <p>Allows adding Atoms to the Residue.
287    */
288   @Override
289   public MSNode addMSNode(MSNode o) {
290     Atom currentAtom = null;
291     if (o instanceof Atom) {
292       Atom newAtom = (Atom) o;
293       Character newAlt = newAtom.getAltLoc();
294       MSNode atoms = getAtomNode();
295       currentAtom = (Atom) atoms.contains(newAtom);
296       if (currentAtom == null) {
297         currentAtom = newAtom;
298         currentAtom.setResName(getName());
299         currentAtom.setResidueNumber(resNumber);
300         atoms.add(newAtom);
301         setFinalized(false);
302       } else {
303         // Allow overwriting of the root alternate conformer (' ' or 'A').
304         Character currentAlt = currentAtom.getAltLoc();
305         if (currentAlt.equals(' ') || currentAlt.equals('A')) {
306           if (!newAlt.equals(' ') && !newAlt.equals('A')) {
307             newAtom.setXyzIndex(currentAtom.getXyzIndex());
308             atoms.remove(currentAtom);
309             currentAtom = newAtom;
310 
311             currentAtom.setResName(getName());
312             currentAtom.setResidueNumber(resNumber);
313 
314             atoms.add(currentAtom);
315             setFinalized(false);
316           }
317         }
318       }
319     } else {
320       logger.warning("Only an Atom can be added to a Residue.");
321     }
322     return currentAtom;
323   }
324 
325   @Override
326   public int compareTo(Residue o) {
327     if (this.equals(o)) {
328       return 0;
329     }
330     return resComparator.compare(this, o);
331   }
332 
333   /** {@inheritDoc} */
334   @Override
335   public void drawLabel(Canvas3D canvas, J3DGraphics2D g2d, Node node) {
336     if (RendererCache.labelResidues) {
337       double[] d = getCenter();
338       point3d.x = d[0];
339       point3d.y = d[1];
340       point3d.z = d[2];
341       RendererCache.getScreenCoordinate(canvas, node, point3d, point2d);
342       g2d.drawString(getName(), (float) point2d.x, (float) point2d.y);
343     }
344     if (RendererCache.labelAtoms) {
345       super.drawLabel(canvas, g2d, node);
346     }
347   }
348 
349   /** {@inheritDoc} */
350   @Override
351   public boolean equals(Object o) {
352     if (this == o) {
353       return true;
354     }
355     if (o == null || getClass() != o.getClass()) {
356       return false;
357     }
358     Residue residue = (Residue) o;
359     return Objects.equals(segID, residue.segID)
360         && Objects.equals(getResidueNumber(), residue.getResidueNumber())
361         && residueType == residue.residueType
362         && Objects.equals(getName(), residue.getName());
363   }
364 
365   /**
366    * {@inheritDoc}
367    *
368    * <p>The Finalize method should be called once all atoms have been added to the Residue. Geometry
369    * objects (Bonds, Angles, etc) are then formed, followed by a determination of under-constrained
370    * (Dangeling) atoms.
371    */
372   @Override
373   public void finalize(boolean finalizeGeometry, ForceField forceField) {
374     setFinalized(false);
375     getAtomNode().setName("Atoms (" + getAtomList().size() + ")");
376     if (finalizeGeometry) {
377       assignBondedTerms(forceField);
378       removeLeaves();
379     }
380     setCenter(getMultiScaleCenter(false));
381     setFinalized(true);
382   }
383 
384   public void setTitrationUtils(TitrationUtils titrationUtils) {
385     this.titrationUtils = titrationUtils;
386   }
387 
388   public TitrationUtils getTitrationUtils() {
389     return titrationUtils;
390   }
391 
392   /**
393    * getAminoAcid3.
394    *
395    * @return a {@link AminoAcid3} object.
396    */
397   public AminoAcid3 getAminoAcid3() {
398     if (this.residueType != ResidueType.AA) {
399       throw new IllegalArgumentException(
400           String.format(" This residue is " + "not an amino acid: %s", this));
401     }
402     return aa;
403   }
404 
405   /**
406    * Returns a list of backbone atoms; for our purposes, nucleic acid backbone atoms are those of the
407    * nucleobase.
408    *
409    * @return List of backbone (or nucleobase) atoms.
410    */
411   public List<Atom> getBackboneAtoms() {
412     List<Atom> atoms = getAtomList();
413     List<Atom> ret;
414     switch (residueType) {
415       case NA:
416         ret = new ArrayList<>(atoms);
417         for (Atom atom : atoms) {
418           String name = atom.getName().toUpperCase();
419           if (name.contains("'")
420               || name.equals("P")
421               || name.startsWith("OP")
422               || name.equals("H5T")
423               || name.equals("H3T")) {
424             ret.remove(atom);
425           }
426         }
427         return ret;
428       case AA:
429         ret = new ArrayList<>();
430         tryAddAtom(ret, "N");
431         tryAddAtom(ret, "CA");
432         tryAddAtom(ret, "C");
433         tryAddAtom(ret, "O");
434         tryAddAtom(ret, "OXT"); // C-terminal residues
435         tryAddAtom(ret, "OT2"); // Probably alternate name for OXT.
436         tryAddAtom(ret, "H1"); // N-terminal residues
437         tryAddAtom(ret, "H2");
438         tryAddAtom(ret, "H3");
439         tryAddAtom(ret, "H");
440         tryAddAtom(ret, "HA");
441         tryAddAtom(ret, "HA2"); // Glycines
442         tryAddAtom(ret, "HA3");
443         return ret;
444       default:
445         return new ArrayList<>(1); // Return empty list.
446     }
447   }
448 
449   /**
450    * Returns this Residues Parent Polymer name.
451    *
452    * @return a {@link java.lang.Character} object.
453    */
454   public Character getChainID() {
455     return chainID;
456   }
457 
458   /**
459    * Setter for the field <code>chainID</code>.
460    *
461    * @param c a {@link java.lang.Character} object.
462    */
463   public void setChainID(Character c) {
464     chainID = c;
465 
466     for (Atom atom : getAtomList()) {
467       atom.setChainID(c);
468     }
469   }
470 
471   /**
472    * Setter for the field <code>segID</code>.
473    *
474    * @param segID The segID to use.
475    */
476   public void setSegID(String segID) {
477     this.segID = segID;
478 
479     for (Atom atom : getAtomList()) {
480       atom.setSegID(segID);
481     }
482   }
483 
484   /**
485    * Returns the Residue bonded to this Residue at this Residue's 3' or C-terminal end. Any use of
486    * this method to add Residues to a sliding window or similar MUST not add that residue if that
487    * residue has no Rotamers, as several algorithms (such as the distance matrix) assume that all
488    * Residues being optimized have Rotamers.
489    *
490    * @return The next Residue.
491    */
492   public Residue getNextResidue() {
493     switch (residueType) {
494       case AA: {
495         Atom carbon = (Atom) getAtomNode("C");
496         if (carbon == null) {
497           return null;
498         }
499         List<Bond> bonds = carbon.getBonds();
500         for (Bond b : bonds) {
501           Atom other = b.get1_2(carbon);
502           if (other.getName().equalsIgnoreCase("N")) {
503             return (Residue) other.getParent().getParent();
504           }
505         }
506         break;
507       }
508       case NA:
509         Atom oxygen = (Atom) getAtomNode("O3'");
510         if (oxygen == null) {
511           return null;
512         }
513         List<Bond> bonds = oxygen.getBonds();
514         for (Bond b : bonds) {
515           Atom other = b.get1_2(oxygen);
516           if (other.getName().equalsIgnoreCase("P")) {
517             return (Residue) other.getParent().getParent();
518           }
519         }
520         break;
521       default:
522         return null;
523     }
524     return null;
525     // Will generally indicate that you passed in a chain-terminal residue.
526   }
527 
528   /**
529    * getNucleicAcid3.
530    *
531    * @return a {@link NucleicAcid3} object.
532    */
533   public NucleicAcid3 getNucleicAcid3() {
534     if (this.residueType != ResidueType.NA) {
535       throw new IllegalArgumentException(
536           String.format(" This residue is not a nucleic acid: %s", this));
537     }
538     return na;
539   }
540 
541   /**
542    * Returns the NucleicAcid3 corresponding to this Residue, with additional robust checking for 1-
543    * or 2-letter names.
544    *
545    * @param matchShortName Try to match 1- or 2-letter names (e.g. A to ADE).
546    * @return a {@link NucleicAcid3} object.
547    */
548   public NucleicAcid3 getNucleicAcid3(boolean matchShortName) {
549     NucleicAcid3 na3 = getNucleicAcid3();
550     if (na3 == NucleicAcidUtils.NucleicAcid3.UNK && matchShortName) {
551       switch (getName()) {
552         case "A":
553           return NucleicAcidUtils.NucleicAcid3.ADE;
554         case "C":
555           return NucleicAcidUtils.NucleicAcid3.CYT;
556         case "G":
557           return NucleicAcidUtils.NucleicAcid3.GUA;
558         case "T":
559           return NucleicAcidUtils.NucleicAcid3.THY;
560         case "U":
561           return NucleicAcidUtils.NucleicAcid3.URI;
562         case "DA":
563           return NucleicAcidUtils.NucleicAcid3.DAD;
564         case "DC":
565           return NucleicAcidUtils.NucleicAcid3.DCY;
566         case "DG":
567           return NucleicAcidUtils.NucleicAcid3.DGU;
568         case "DT":
569           return NucleicAcidUtils.NucleicAcid3.DTY;
570         case "DU":
571           throw new IllegalArgumentException(
572               " No NucleicAcid3 enum exists for DU (presumed to be deoxy-uracil)!");
573       }
574     }
575     return na3;
576   }
577 
578   /**
579    * Returns the Residue bonded to this Residue at this Residue's 5' or N-terminal end. Any use of
580    * this method to add Residues to a sliding window or similar MUST not add that residue if that
581    * residue has no Rotamers, as several algorithms (such as the distance matrix) assume that all
582    * Residues being optimized have Rotamers.
583    *
584    * @return The previous Residue.
585    */
586   public Residue getPreviousResidue() {
587     switch (residueType) {
588       case AA: {
589         Atom nitrogen = (Atom) getAtomNode("N");
590         if (nitrogen == null) {
591           return null;
592         }
593         List<Bond> bonds = nitrogen.getBonds();
594         for (Bond b : bonds) {
595           Atom other = b.get1_2(nitrogen);
596           if (other.getName().equalsIgnoreCase("C")) {
597             return (Residue) other.getParent().getParent();
598           }
599         }
600         break;
601       }
602       case NA:
603         Atom phosphate = (Atom) getAtomNode("P");
604         if (phosphate == null) {
605           return null;
606         }
607         List<Bond> bonds = phosphate.getBonds();
608         for (Bond b : bonds) {
609           Atom other = b.get1_2(phosphate);
610           if (other.getName().equalsIgnoreCase("O3'")) {
611             return (Residue) other.getParent().getParent();
612           }
613         }
614         break;
615       default:
616         return null;
617     }
618     return null;
619     // Will generally indicate that you passed in a chain-starting residue.
620   }
621 
622   /**
623    * Returns a reference Atom for a Residue, primarily intended for rough distance calculations. This
624    * atom should be roughly centrally located within the residue, and be invariant.
625    *
626    * @return A reference Atom.
627    */
628   public Atom getReferenceAtom() {
629     Atom atom = null;
630     switch (this.getResidueType()) {
631       case AA:
632         atom = (Atom) this.getAtomNode("CA");
633         break;
634       case NA:
635         // If pyrimidine, atom will be N1.  Else, if purine,
636         // N1 will return null, so grab N9.
637         atom = (Atom) this.getAtomNode("N1");
638         if (atom == null) {
639           atom = (Atom) this.getAtomNode("N9");
640         }
641         break;
642       default:
643         break;
644     }
645     if (atom == null) {
646       atom = (Atom) this.getAtomNode(0);
647     }
648     return atom;
649   }
650 
651   /**
652    * Returns this Residue's sequence number.
653    *
654    * @return a int.
655    */
656   public int getResidueNumber() {
657     return resNumber;
658   }
659 
660   /**
661    * Getter for the field <code>residueType</code>.
662    *
663    * @return a {@link ResidueType} object.
664    */
665   public ResidueType getResidueType() {
666     return residueType;
667   }
668 
669   /**
670    * Get the current rotamer.
671    *
672    * @return a {@link ffx.potential.bonded.Rotamer} object.
673    */
674   public Rotamer getRotamer() {
675     return currentRotamer;
676   }
677 
678   /**
679    * Set the current rotamer.
680    *
681    * @param rotamer a {@link ffx.potential.bonded.Rotamer} object.
682    */
683   public void setRotamer(Rotamer rotamer) {
684     this.currentRotamer = rotamer;
685   }
686 
687   /**
688    * Return all currently set rotamers.
689    *
690    * @return All current rotamers for this residue.
691    */
692   public Rotamer[] getRotamers() {
693     return rotamers;
694   }
695 
696   /**
697    * Resets the rotamers for this residue, potentially incorporating the original coordinates if
698    * RotamerLibrary's original coordinates rotamer flag has been set.
699    * <p>
700    * Any rotamers that were set previously are deleted.
701    *
702    * @param library Rotamer library to use
703    * @return An array of Rotamer.
704    */
705   public Rotamer[] setRotamers(RotamerLibrary library) {
706 
707     // Obtain rotamers for this residue from the RotamerLibrary.
708     Rotamer[] libRotamers = library.getRotamers(this, titrationUtils);
709     rotamers = libRotamers;
710 
711     // Add the original coordinates as a rotamer if requested
712     // (unless the library has no rotamers for this residue).
713     if (library.getUsingOrigCoordsRotamer() && rotamers != null) {
714       // Define the current coordinates as a new rotamer.
715       Rotamer[] originalRotamers = Rotamer.defaultRotamerFactory(this, titrationUtils);
716       int nOrig = originalRotamers.length;
717       // Add the original rotamers to those from the library and cache the result.
718       int libRots = libRotamers.length;
719       rotamers = new Rotamer[libRots + nOrig];
720       // First rotamers are the original conformation.
721       arraycopy(originalRotamers, 0, rotamers, 0, nOrig);
722       // Copy in the library.
723       arraycopy(libRotamers, 0, rotamers, nOrig, libRots);
724     }
725 
726     return rotamers;
727   }
728 
729   /**
730    * Getter for the field <code>segID</code>.
731    *
732    * @return a {@link java.lang.String} object.
733    */
734   public String getSegID() {
735     return segID;
736   }
737 
738   /**
739    * Returns a list of side chain atoms; for our purposes, nucleic acid side chain atoms are the
740    * sugar and the phosphate.
741    *
742    * @return List of side chain (or nucleic backbone) atoms.
743    */
744   public List<Atom> getSideChainAtoms() {
745     List<Atom> atoms = getAtomList();
746     List<Atom> ret;
747     switch (residueType) {
748       case NA:
749         ret = new ArrayList<>();
750         for (Atom atom : atoms) {
751           String name = atom.getName().toUpperCase();
752           /*
753            * Very conveniently for our purposes, the entire sugar is
754            * denoted with ' at the end. Note that we add the side chain
755            * atoms for NAs, instead of removing the backbone.
756            */
757           if (name.contains("'") || name.equals("P") || name.startsWith("OP")) {
758             ret.add(atom);
759           }
760         }
761         return ret;
762       case AA:
763         ret = new ArrayList<>(atoms);
764         for (Atom atom : atoms) {
765           String name = atom.getName().toUpperCase();
766           if (name.equals("N")
767               || name.equals("H")
768               || name.equals("H1")
769               || name.equals("H2")
770               || name.equals("H3")
771               || name.equals("CA")
772               || name.startsWith("HA")
773               || name.equals("C")
774               || name.equals("O")
775               || name.equals("OXT")
776               || name.equals("OT2")) {
777             ret.remove(atom);
778           }
779         }
780         return ret;
781       default:
782         return null;
783     }
784   }
785 
786   /**
787    * Returns a list of atoms liable to change during dead-end elimination repacking. For ordinary
788    * amino acids: side chain atoms. For ordinary nucleic acids: sugar/phosphate backbone atoms.
789    * MultiResidue over-rides this to return all atoms (as backbone atom types are nonconstant).
790    *
791    * @return Atoms changeable during DEE.
792    */
793   public List<Atom> getVariableAtoms() {
794     return getSideChainAtoms();
795   }
796 
797   /** {@inheritDoc} */
798   @Override
799   public int hashCode() {
800     return Objects.hash(segID, getResidueNumber(), residueType, getName());
801   }
802 
803   /**
804    * Initializes this (presumably nucleic acid) Residue's C1s, O4s, C4s, O3sNorth, and O3sSouth
805    * default coordinates based on default PDB atom locations; to preserve rotamer independence, this
806    * must be called before any NA rotamers are applied.
807    */
808   public void initializeDefaultAtomicCoordinates() {
809     if (residueType != ResidueType.NA) {
810       return;
811     }
812     boolean isDeoxy;
813     try {
814       switch (NucleicAcidUtils.NucleicAcid3.valueOf(this.getName())) {
815         case DAD:
816         case DCY:
817         case DGU:
818         case DTY:
819           isDeoxy = true;
820           break;
821         case CYT:
822         case ADE:
823         case THY:
824         case URI:
825         case GUA:
826         default:
827           isDeoxy = false;
828           break;
829       }
830       C1sCoords = new double[3];
831       ((Atom) getAtomNode("C1'")).getXYZ(C1sCoords);
832       O4sCoords = new double[3];
833       ((Atom) getAtomNode("O4'")).getXYZ(O4sCoords);
834       C4sCoords = new double[3];
835       ((Atom) getAtomNode("C4'")).getXYZ(C4sCoords);
836 
837       /*
838        With the place flag set false, applySugarPucker returns
839        hypothetical O3' coordinates based on default atom positions and
840        the supplied sugar pucker.
841       */
842       O3sNorthCoords =
843           RotamerLibrary.applySugarPucker(
844               this, RotamerLibrary.NucleicSugarPucker.C3_ENDO, isDeoxy, false);
845       O3sSouthCoords =
846           RotamerLibrary.applySugarPucker(
847               this, RotamerLibrary.NucleicSugarPucker.C2_ENDO, isDeoxy, false);
848     } catch (Exception e) {
849       logger.log(Level.WARNING, toString(), e);
850     }
851   }
852 
853   /**
854    * {@inheritDoc}
855    *
856    * <p>Prints "Residue Number: x" to stdout.
857    */
858   @Override
859   public void print() {
860     logger.info(" " + this);
861     for (Atom a : getAtomNode().getAtomList()) {
862       a.print();
863     }
864   }
865 
866   /**
867    * revertState.
868    *
869    * @param state a {@link ffx.potential.bonded.ResidueState} object.
870    */
871   public void revertState(ResidueState state) {
872     List<Atom> atomList = getAtomList();
873     for (Atom atom : atomList) {
874       atom.moveTo(state.getAtomCoords(atom));
875     }
876   }
877 
878   /** {@inheritDoc} */
879   @Override
880   public void setColor(RendererCache.ColorModel newColorModel, Color3f color, Material mat) {
881     // If Color by Residue, pass this Residue's Color
882     if (newColorModel == RendererCache.ColorModel.RESIDUE) {
883       switch (residueType) {
884         case AA:
885           color = AA3Color.get(aa);
886           break;
887         case NA:
888           color = NA3Color.get(na);
889           break;
890         default:
891           color = null;
892       }
893       if (color == null) {
894         return;
895       }
896       mat = RendererCache.materialFactory(color);
897     } else if (newColorModel == RendererCache.ColorModel.STRUCTURE) {
898       color = SSTypeColor.get(SSType.NONE);
899       mat = RendererCache.materialFactory(color);
900     }
901     super.setColor(newColorModel, color, mat);
902   }
903 
904   /**
905    * setNumber
906    *
907    * @param n a int.
908    */
909   public void setNumber(int n) {
910     resNumber = n;
911     for (Atom atom : getAtomList()) {
912       atom.setResidueNumber(n);
913     }
914   }
915 
916   /**
917    * storeCoordinateArray.
918    *
919    * @return an array of {@link double} objects.
920    */
921   public double[][] storeCoordinateArray() {
922     List<Atom> atomList = getAtomList();
923     int nAtoms = atomList.size();
924     double[][] coords = new double[nAtoms][3];
925     int i = 0;
926     for (Atom atom : atomList) {
927       atom.getXYZ(coords[i++]);
928     }
929     return coords;
930   }
931 
932   /**
933    * storeState.
934    *
935    * @return a {@link ffx.potential.bonded.ResidueState} object.
936    */
937   public ResidueState storeState() {
938     return new ResidueState(this, this);
939   }
940 
941   /**
942    * Formats this residue with some optional inclusions.
943    *
944    * <p>[residue type]-[chain ID]ResNumber-Name.
945    *
946    * @param addResType Include the residue type
947    * @param addChainID Include the chain ID.
948    * @return A descriptive string.
949    */
950   public String toFormattedString(boolean addResType, boolean addChainID) {
951     StringBuilder sb = new StringBuilder();
952     if (addResType) {
953       sb.append(residueType.toString()).append("-");
954     }
955     if (addChainID) {
956       sb.append(chainID);
957     }
958     sb.append(this);
959     return sb.toString();
960   }
961 
962   /**
963    * A descriptive string based on a given rotamer.
964    *
965    * <p>[chain ID]ResNumber-RotamerName.
966    *
967    * @param rotamer The rotamer to use.
968    * @return A descriptive string.
969    */
970   public String toString(Rotamer rotamer) {
971     return "" + chainID + resNumber + "-" + rotamer.getName();
972   }
973 
974   /** {@inheritDoc} */
975   @Override
976   public String toString() {
977     if (shortString == null) {
978       shortString = "" + resNumber + "-" + getName();
979     }
980     return shortString;
981   }
982 
983   /**
984    * Add an array of rotamers to this Residue's cached array of rotamers.
985    *
986    * @param rotamers The rotamers to add.
987    */
988   void addRotamers(Rotamer[] rotamers) {
989     for (Rotamer rotamer : rotamers) {
990       addRotamer(rotamer);
991     }
992   }
993 
994   /**
995    * Add a rotamer to this Residue's cached array of rotamers.
996    *
997    * @param rotamer The rotamer to add.
998    */
999   void addRotamer(Rotamer rotamer) {
1000     if (rotamers != null) {
1001       Rotamer[] libRotamers = rotamers;
1002       int nRots = libRotamers.length;
1003       rotamers = new Rotamer[nRots + 1];
1004       arraycopy(libRotamers, 0, rotamers, 0, nRots);
1005       rotamers[rotamers.length - 1] = rotamer;
1006     } else {
1007       rotamers = new Rotamer[1];
1008       rotamers[0] = rotamer;
1009     }
1010   }
1011 
1012   /**
1013    * deleteAtom
1014    *
1015    * @param atomToDelete a {@link ffx.potential.bonded.Atom} object.
1016    */
1017   void deleteAtom(Atom atomToDelete) {
1018     MSNode atoms = getAtomNode();
1019     if (atoms.contains(atomToDelete) != null) {
1020       logger.info(
1021           " The following atom is being deleted from the model:\n" + atomToDelete);
1022       atoms.remove(atomToDelete);
1023     }
1024   }
1025 
1026   /**
1027    * Returns the position of this (presumably nucleic acid) Residue's default O3' coordinates given a
1028    * North pucker.
1029    *
1030    * @return a new double[] with default XYZ coordinates for O3' in a North pucker.
1031    */
1032   double[] getO3sNorth() {
1033     double[] ret = new double[3];
1034     arraycopy(O3sNorthCoords, 0, ret, 0, ret.length);
1035     return ret;
1036   }
1037 
1038   /**
1039    * Returns the position of this (presumably nucleic acid) Residue's default O3' coordinates given a
1040    * South pucker.
1041    *
1042    * @return a new double[] with default XYZ coordinates for O3' in a South pucker.
1043    */
1044   double[] getO3sSouth() {
1045     double[] ret = new double[3];
1046     arraycopy(O3sSouthCoords, 0, ret, 0, ret.length);
1047     return ret;
1048   }
1049 
1050   /**
1051    * Returns the position of this (presumably nucleic acid) Residue's original C1' coordinates.
1052    *
1053    * @return a new double[] with original XYZ coordinates for C1'.
1054    */
1055   double[] getC1sCoords() {
1056     double[] ret = new double[3];
1057     arraycopy(C1sCoords, 0, ret, 0, ret.length);
1058     return ret;
1059   }
1060 
1061   /**
1062    * Returns the position of this (presumably nucleic acid) Residue's original O4' coordinates.
1063    *
1064    * @return a new double[] with original XYZ coordinates for O4'.
1065    */
1066   double[] getO4sCoords() {
1067     double[] ret = new double[3];
1068     arraycopy(O4sCoords, 0, ret, 0, ret.length);
1069     return ret;
1070   }
1071 
1072   /**
1073    * Returns the position of this (presumably nucleic acid) Residue's original C4' coordinates.
1074    *
1075    * @return a new double[] with original XYZ coordinates for C4'.
1076    */
1077   double[] getC4sCoords() {
1078     double[] ret = new double[3];
1079     arraycopy(C4sCoords, 0, ret, 0, ret.length);
1080     return ret;
1081   }
1082 
1083   /**
1084    * Uses a name to add an Atom to a List<Atom> if the Atom exists for this residue.
1085    *
1086    * @param atList List to add to.
1087    * @param name Atom to add.
1088    * @return If atom exists.
1089    */
1090   private boolean tryAddAtom(List<Atom> atList, String name) {
1091     try {
1092       Atom at = (Atom) getAtomNode(name);
1093       if (at != null) {
1094         atList.add(at);
1095         return true;
1096       } else {
1097         return false;
1098       }
1099     } catch (Exception ex) {
1100       return false;
1101     }
1102   }
1103 
1104   private void assignResidueType() {
1105     String name = getName().toUpperCase();
1106     switch (residueType) {
1107       case AA:
1108         aa = null;
1109         try {
1110           if (name.length() >= 2) {
1111             aa = AminoAcidUtils.AminoAcid3.valueOf(name);
1112           } else if (name.length() == 1) {
1113             AminoAcid1 aa1 = AminoAcidUtils.AminoAcid1.valueOf(name);
1114             aa = AA1toAA3.get(aa1);
1115           }
1116         } catch (Exception e) {
1117           logger.fine(String.format("Exception assigning AA3 for residue: %s", name));
1118           aa = AminoAcidUtils.AminoAcid3.UNK;
1119         }
1120         break;
1121       case NA:
1122         na = null;
1123         try {
1124           if (name.length() >= 2) {
1125             na = NucleicAcidUtils.NucleicAcid3.parse(name);
1126           } else if (name.length() == 1) {
1127             NucleicAcid1 na1 = NucleicAcidUtils.NucleicAcid1.valueOf(name);
1128             na = NA1toNA3.get(na1);
1129           }
1130         } catch (Exception e) {
1131           na = NucleicAcidUtils.NucleicAcid3.UNK;
1132         }
1133         break;
1134     }
1135   }
1136 
1137   public enum SSType {
1138     NONE,
1139     HELIX,
1140     SHEET,
1141     TURN
1142   }
1143 
1144   /**
1145    * Residue type [NA, AA, UNK].
1146    */
1147   public enum ResidueType {
1148     NA,
1149     AA,
1150     UNK
1151   }
1152 }