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