View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2024.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential.bonded;
39  
40  import static ffx.numerics.math.DoubleMath.dihedralAngle;
41  import static ffx.numerics.math.ScalarMath.modToRange;
42  import static ffx.potential.bonded.AminoAcidUtils.AminoAcid3;
43  import static ffx.potential.bonded.AminoAcidUtils.getAminoAcid;
44  import static ffx.potential.bonded.BondedUtils.findAtomsOfElement;
45  import static ffx.potential.bonded.BondedUtils.findBondedAtoms;
46  import static ffx.potential.bonded.BondedUtils.findNitrogenAtom;
47  import static ffx.potential.bonded.BondedUtils.findNucleotideO4s;
48  import static ffx.potential.bonded.BondedUtils.getAlphaCarbon;
49  import static ffx.potential.bonded.BondedUtils.hasAttachedAtom;
50  import static ffx.potential.bonded.BondedUtils.sortAtomsByDistance;
51  import static ffx.potential.bonded.NucleicAcidUtils.NucleicAcid3;
52  import static java.lang.Integer.parseInt;
53  import static java.lang.Math.PI;
54  import static java.lang.String.format;
55  
56  import ffx.potential.MolecularAssembly;
57  import ffx.potential.parsers.PDBFilter.PDBFileStandard;
58  import java.util.ArrayList;
59  import java.util.Comparator;
60  import java.util.HashMap;
61  import java.util.List;
62  import java.util.Map;
63  import java.util.Optional;
64  import java.util.logging.Level;
65  import java.util.logging.Logger;
66  
67  /**
68   * Utilities for importing atoms from PDB files and checking their names.
69   *
70   * @author Jacob Litman
71   * @author Michael Schnieders
72   * @since 1.0
73   */
74  public class NamingUtils {
75  
76    private static final Logger logger = Logger.getLogger(NamingUtils.class.getName());
77  
78    /**
79     * Ensures proper naming of hydrogen according to latest PDB format. Presently mostly guesses at
80     * which hydrogen to re-assign, which may cause chirality errors for prochiral hydrogen. If
81     * necessary, we will implement more specific mapping.
82     *
83     * @param residue Residue to examine.
84     * @param fileStandard PDB File Standard to use.
85     */
86    public static void checkHydrogenAtomNames(Residue residue, PDBFileStandard fileStandard) {
87      switch (fileStandard) {
88        case VERSION3_3:
89          return;
90        case VERSION3_2:
91        default:
92          break;
93      }
94      // May have to get position.
95      String residueType = residue.getName().toUpperCase();
96      List<Atom> resAtoms = residue.getAtomList();
97      for (Atom atom : resAtoms) {
98        if (atom == null) {
99          continue;
100       }
101       String atomName = atom.getName().toUpperCase();
102       // Handles situations such as 1H where it should be NA_H1, etc.
103       if (atomName.contains("H")) {
104         try {
105           String firstChar = atomName.substring(0, 1);
106           parseInt(firstChar);
107           atomName = atomName.substring(1);
108           atomName = atomName.concat(firstChar);
109           atom.setName(atomName);
110         } catch (NumberFormatException e) {
111           // Do nothing.
112         }
113       }
114     }
115     // Ensures proper hydrogen assignment; for example, Gln should have HB2,
116     // HB3 instead of HB1, HB2.
117     List<Atom> betas;
118     List<Atom> gammas;
119     List<Atom> deltas;
120     List<Atom> epsilons;
121     List<Atom> zetas;
122     String atomName;
123     Atom OH;
124     Atom HH;
125     Atom HG;
126     Atom HD2;
127     switch (getAminoAcid(residueType)) {
128       case GLY:
129         List<Atom> alphas = new ArrayList<>();
130         for (Atom atom : resAtoms) {
131           if (atom.getName().toUpperCase().contains("HA")) {
132             alphas.add(atom);
133           }
134         }
135         renameGlycineAlphaHydrogen(residue, alphas);
136         break;
137       case ALA:
138         // No known errors with alanine
139         break;
140       case VAL:
141         // No known errors with valine
142         break;
143       case LEU:
144       case SER:
145       case CYD:
146       case ASP:
147         betas = new ArrayList<>();
148         for (Atom atom : resAtoms) {
149           if (atom.getName().toUpperCase().contains("HB")) {
150             betas.add(atom);
151           }
152         }
153         renameBetaHydrogen(residue, betas, 23);
154         break;
155       case ILE:
156         List<Atom> ileAtoms = new ArrayList<>();
157         for (Atom atom : resAtoms) {
158           if (atom.getName().toUpperCase().contains("HG1")) {
159             ileAtoms.add(atom);
160           }
161         }
162         renameIsoleucineHydrogen(residue, ileAtoms);
163         break;
164       case THR:
165         Atom HG1 = (Atom) residue.getAtomNode("HG1");
166         if (HG1 == null) {
167           for (Atom atom : resAtoms) {
168             atomName = atom.getName().toUpperCase();
169             // Gets first HG-containing name of length < 4
170             // Length < 4 avoids bringing in HG21, HG22, or HG23.
171             if (atomName.length() < 4 && atomName.contains("HG")) {
172               atom.setName("HG1");
173               break;
174             }
175           }
176         }
177         break;
178       case CYS:
179         betas = new ArrayList<>();
180         HG = (Atom) residue.getAtomNode("HG");
181         for (Atom atom : resAtoms) {
182           atomName = atom.getName().toUpperCase();
183           if (atomName.contains("HB")) {
184             betas.add(atom);
185           } else if (HG == null && atomName.contains("HG")) {
186             HG = atom;
187             HG.setName("HG");
188           }
189         }
190         renameBetaHydrogen(residue, betas, 23);
191         break;
192       case CYX:
193         // I pray this is never important, because I don't have an example CYX to work from.
194         break;
195       case PRO:
196         betas = new ArrayList<>();
197         gammas = new ArrayList<>();
198         deltas = new ArrayList<>();
199         for (Atom atom : resAtoms) {
200           atomName = atom.getName().toUpperCase();
201           if (atomName.contains("HB")) {
202             betas.add(atom);
203           } else if (atomName.contains("HG")) {
204             gammas.add(atom);
205           } else if (atomName.contains("HD")) {
206             deltas.add(atom);
207           }
208         }
209         renameBetaHydrogen(residue, betas, 23);
210         renameGammaHydrogen(residue, gammas, 23);
211         renameDeltaHydrogen(residue, deltas, 23);
212         break;
213       case PHE:
214         betas = new ArrayList<>();
215         deltas = new ArrayList<>();
216         epsilons = new ArrayList<>();
217         Atom HZ = (Atom) residue.getAtomNode("HZ");
218         for (Atom atom : resAtoms) {
219           atomName = atom.getName().toUpperCase();
220           if (atomName.contains("HB")) {
221             betas.add(atom);
222           } else if (atomName.contains("HD")) {
223             deltas.add(atom);
224           } else if (atomName.contains("HE")) {
225             epsilons.add(atom);
226           } else if (HZ == null && atomName.contains("HZ")) {
227             HZ = atom;
228             HZ.setName("HZ");
229           }
230         }
231         renameBetaHydrogen(residue, betas, 23);
232         renameDeltaHydrogen(residue, deltas, 12);
233         renameEpsilonHydrogen(residue, epsilons, 12);
234         break;
235       case TYR:
236         betas = new ArrayList<>();
237         deltas = new ArrayList<>();
238         epsilons = new ArrayList<>();
239         HH = (Atom) residue.getAtomNode("HH");
240         OH = (Atom) residue.getAtomNode("OH");
241         for (Atom atom : resAtoms) {
242           atomName = atom.getName().toUpperCase();
243           if (atomName.contains("HB")) {
244             betas.add(atom);
245           } else if (atomName.contains("HD")) {
246             deltas.add(atom);
247           } else if (atomName.contains("HE")) {
248             epsilons.add(atom);
249           } else if (HH == null && atomName.contains("HH")) {
250             HH = atom;
251             HH.setName("HH");
252           } else if (OH == null && atomName.contains("O") && atomName.contains("H")) {
253             OH = atom;
254             OH.setName("OH");
255           }
256         }
257         renameBetaHydrogen(residue, betas, 23);
258         renameDeltaHydrogen(residue, deltas, 12);
259         renameEpsilonHydrogen(residue, epsilons, 12);
260         break;
261       case TYD:
262         betas = new ArrayList<>();
263         deltas = new ArrayList<>();
264         epsilons = new ArrayList<>();
265         OH = (Atom) residue.getAtomNode("OH");
266         for (Atom atom : resAtoms) {
267           atomName = atom.getName().toUpperCase();
268           if (atomName.contains("HB")) {
269             betas.add(atom);
270           } else if (atomName.contains("HD")) {
271             deltas.add(atom);
272           } else if (atomName.contains("HE")) {
273             epsilons.add(atom);
274           } else if (OH == null && atomName.contains("O") && atomName.contains("H")) {
275             OH = atom;
276             OH.setName("OH");
277           }
278         }
279         renameBetaHydrogen(residue, betas, 23);
280         renameDeltaHydrogen(residue, deltas, 12);
281         renameEpsilonHydrogen(residue, epsilons, 12);
282         break;
283       case TRP:
284         betas = new ArrayList<>();
285         epsilons = new ArrayList<>();
286         zetas = new ArrayList<>();
287         Atom HD1 = (Atom) residue.getAtomNode("HD1");
288         Atom HH2 = (Atom) residue.getAtomNode("HH2");
289         for (Atom atom : resAtoms) {
290           atomName = atom.getName().toUpperCase();
291           if (atomName.contains("HB")) {
292             betas.add(atom);
293           } else if (atomName.contains("HE")) {
294             epsilons.add(atom);
295           } else if (atomName.contains("HZ")) {
296             zetas.add(atom);
297           } else if (HD1 == null && atomName.contains("HD")) {
298             HD1 = atom;
299             HD1.setName("HD1");
300           } else if (HH2 == null && atomName.contains("HH")) {
301             HH2 = atom;
302             HH2.setName("HH2");
303           }
304         }
305         renameBetaHydrogen(residue, betas, 23);
306         renameEpsilonHydrogen(residue, epsilons, 13);
307         renameZetaHydrogen(residue, zetas, 23);
308         break;
309       case HIS:
310         betas = new ArrayList<>();
311         deltas = new ArrayList<>();
312         epsilons = new ArrayList<>();
313         for (Atom atom : resAtoms) {
314           atomName = atom.getName().toUpperCase();
315           if (atomName.contains("HB")) {
316             betas.add(atom);
317           } else if (atomName.contains("HD")) {
318             deltas.add(atom);
319           } else if (atomName.contains("HE")) {
320             epsilons.add(atom);
321           }
322         }
323         renameBetaHydrogen(residue, betas, 23);
324         renameDeltaHydrogen(residue, deltas, 12);
325         renameEpsilonHydrogen(residue, epsilons, 12);
326         break;
327       case HID:
328         betas = new ArrayList<>();
329         deltas = new ArrayList<>();
330         Atom HE1 = (Atom) residue.getAtomNode("HE1");
331         for (Atom atom : resAtoms) {
332           atomName = atom.getName().toUpperCase();
333           if (atomName.contains("HB")) {
334             betas.add(atom);
335           } else if (atomName.contains("HD")) {
336             deltas.add(atom);
337           } else if (HE1 == null && atomName.contains("HE")) {
338             HE1 = atom;
339             HE1.setName("HE1");
340           }
341         }
342         renameBetaHydrogen(residue, betas, 23);
343         renameDeltaHydrogen(residue, deltas, 12);
344         break;
345       case HIE:
346         betas = new ArrayList<>();
347         epsilons = new ArrayList<>();
348         HD2 = (Atom) residue.getAtomNode("HD2");
349         for (Atom atom : resAtoms) {
350           atomName = atom.getName().toUpperCase();
351           if (atomName.contains("HB")) {
352             betas.add(atom);
353           } else if (atomName.contains("HE")) {
354             epsilons.add(atom);
355           } else if (HD2 == null && atomName.contains("HD")) {
356             HD2 = atom;
357             HD2.setName("HD2");
358           }
359         }
360         renameBetaHydrogen(residue, betas, 23);
361         renameEpsilonHydrogen(residue, epsilons, 12);
362         break;
363       case ASH:
364         betas = new ArrayList<>();
365         HD2 = (Atom) residue.getAtomNode("HD2");
366         for (Atom atom : resAtoms) {
367           atomName = atom.getName().toUpperCase();
368           if (atomName.contains("HB")) {
369             betas.add(atom);
370           } else if (HD2 == null && atomName.contains("HD")) {
371             HD2 = atom;
372             HD2.setName("HD2");
373           }
374         }
375         renameBetaHydrogen(residue, betas, 23);
376         break;
377       case ASD:
378         betas = new ArrayList<>();
379         deltas = new ArrayList<>();
380         for (Atom atom : resAtoms) {
381           atomName = atom.getName().toUpperCase();
382           if (atomName.contains("HB")) {
383             betas.add(atom);
384           } else if (atomName.contains("HD")) {
385             deltas.add(atom);
386           }
387         }
388         renameBetaHydrogen(residue, betas, 23);
389         renameDeltaHydrogen(residue, deltas, 12);
390         break;
391       case ASN:
392         betas = new ArrayList<>();
393         List<Atom> HD2s = new ArrayList<>();
394         for (Atom atom : resAtoms) {
395           atomName = atom.getName().toUpperCase();
396           if (atomName.contains("HB")) {
397             betas.add(atom);
398           } else if (atomName.contains("HD")) {
399             HD2s.add(atom);
400           }
401         }
402         renameBetaHydrogen(residue, betas, 23);
403         renameAsparagineHydrogen(residue, HD2s);
404         break;
405       case GLU:
406       case MET:
407         betas = new ArrayList<>();
408         gammas = new ArrayList<>();
409         for (Atom atom : resAtoms) {
410           atomName = atom.getName().toUpperCase();
411           if (atomName.contains("HB")) {
412             betas.add(atom);
413           } else if (atomName.contains("HG")) {
414             gammas.add(atom);
415           }
416         }
417         renameBetaHydrogen(residue, betas, 23);
418         renameGammaHydrogen(residue, gammas, 23);
419         break;
420       case GLH:
421         betas = new ArrayList<>();
422         gammas = new ArrayList<>();
423         Atom HE2 = (Atom) residue.getAtomNode("HE2");
424         for (Atom atom : resAtoms) {
425           atomName = atom.getName().toUpperCase();
426           if (atomName.contains("HB")) {
427             betas.add(atom);
428           } else if (atomName.contains("HG")) {
429             gammas.add(atom);
430           } else if (HE2 == null && atomName.contains("HE")) {
431             HE2 = atom;
432             HE2.setName("HE2");
433           }
434         }
435         renameBetaHydrogen(residue, betas, 23);
436         renameGammaHydrogen(residue, gammas, 23);
437         break;
438       case GLD:
439         betas = new ArrayList<>();
440         gammas = new ArrayList<>();
441         epsilons = new ArrayList<>();
442         for (Atom atom : resAtoms) {
443           atomName = atom.getName().toUpperCase();
444           if (atomName.contains("HB")) {
445             betas.add(atom);
446           } else if (atomName.contains("HG")) {
447             gammas.add(atom);
448           } else if (atomName.contains("HE")) {
449             epsilons.add(atom);
450           }
451         }
452         renameBetaHydrogen(residue, betas, 23);
453         renameGammaHydrogen(residue, gammas, 23);
454         renameEpsilonHydrogen(residue, epsilons, 12);
455         break;
456       case GLN:
457         betas = new ArrayList<>();
458         gammas = new ArrayList<>();
459         epsilons = new ArrayList<>();
460         for (Atom atom : resAtoms) {
461           atomName = atom.getName().toUpperCase();
462           if (atomName.contains("HB")) {
463             betas.add(atom);
464           } else if (atomName.contains("HG")) {
465             gammas.add(atom);
466           } else if (atomName.contains("HE")) {
467             epsilons.add(atom);
468           }
469         }
470         renameBetaHydrogen(residue, betas, 23);
471         renameGammaHydrogen(residue, gammas, 23);
472         renameGlutamineHydrogen(residue, epsilons);
473         break;
474       // Epsilons should not break, as they are 1-3.
475       case LYS:
476         betas = new ArrayList<>();
477         gammas = new ArrayList<>();
478         deltas = new ArrayList<>();
479         epsilons = new ArrayList<>();
480         // Zetas are 1-3, should not break.
481         for (Atom atom : resAtoms) {
482           atomName = atom.getName().toUpperCase();
483           if (atomName.contains("HB")) {
484             betas.add(atom);
485           } else if (atomName.contains("HG")) {
486             gammas.add(atom);
487           } else if (atomName.contains("HD")) {
488             deltas.add(atom);
489           } else if (atomName.contains("HE")) {
490             epsilons.add(atom);
491           }
492         }
493         renameBetaHydrogen(residue, betas, 23);
494         renameGammaHydrogen(residue, gammas, 23);
495         renameDeltaHydrogen(residue, deltas, 23);
496         renameEpsilonHydrogen(residue, epsilons, 23);
497         break;
498       case LYD:
499         betas = new ArrayList<>();
500         gammas = new ArrayList<>();
501         deltas = new ArrayList<>();
502         epsilons = new ArrayList<>();
503         zetas = new ArrayList<>();
504         for (Atom atom : resAtoms) {
505           atomName = atom.getName().toUpperCase();
506           if (atomName.contains("HB")) {
507             betas.add(atom);
508           } else if (atomName.contains("HG")) {
509             gammas.add(atom);
510           } else if (atomName.contains("HD")) {
511             deltas.add(atom);
512           } else if (atomName.contains("HE")) {
513             epsilons.add(atom);
514           } else if (atomName.contains("HZ")) {
515             zetas.add(atom);
516           }
517         }
518         renameBetaHydrogen(residue, betas, 23);
519         renameGammaHydrogen(residue, gammas, 23);
520         renameDeltaHydrogen(residue, deltas, 23);
521         renameEpsilonHydrogen(residue, epsilons, 23);
522         renameZetaHydrogen(residue, zetas, 12);
523         break;
524       case ARG:
525         betas = new ArrayList<>();
526         gammas = new ArrayList<>();
527         deltas = new ArrayList<>();
528         Atom HE = (Atom) residue.getAtomNode("HE");
529         List<Atom> HHn = new ArrayList<>();
530         for (Atom atom : resAtoms) {
531           atomName = atom.getName().toUpperCase();
532           if (atomName.contains("HB")) {
533             betas.add(atom);
534           } else if (atomName.contains("HG")) {
535             gammas.add(atom);
536           } else if (atomName.contains("HD")) {
537             deltas.add(atom);
538           } else if (HE == null && atomName.contains("HE")) {
539             HE = atom;
540             HE.setName("HE");
541           } else if (atomName.contains("HH")) {
542             HHn.add(atom);
543           }
544         }
545         renameBetaHydrogen(residue, betas, 23);
546         renameGammaHydrogen(residue, gammas, 23);
547         renameDeltaHydrogen(residue, deltas, 23);
548         renameArginineHydrogen(residue, HHn);
549         break;
550       case ORN:
551       case AIB:
552       case PCA:
553       case UNK:
554       default:
555         // I am currently unaware of how these amino acids are typically
556         // labeled under older PDB standards.
557         break;
558     }
559   }
560 
561   /**
562    * Names the atoms in an N-terminal acetyl ACE capping group.
563    *
564    * @param residue Residue containing an acetyl cap.
565    * @param aceC The acetyl group's C atom.
566    */
567   public static void nameAcetylCap(Residue residue, Atom aceC) {
568     logger.fine(format(" Probable ACE cap attached to residue %s; duplicate atom names may result.",
569         residue));
570     aceC.setName("C");
571     findBondedAtoms(aceC, 8).get(0).setName("O");
572     Atom CH3 = findBondedAtoms(aceC, 6).get(0);
573     CH3.setName("CH3");
574     List<Atom> ntermHs = findBondedAtoms(CH3, 1);
575     for (int i = 0; i < 3; i++) {
576       ntermHs.get(i).setName(format("H%d", (i + 1)));
577     }
578   }
579 
580   /**
581    * Renames an atom, its bonded hydrogen, and returns the next atom in the chain.
582    *
583    * <p>If applied to an atom that is not a carbon, it will be misnamed as a carbon, so fix that
584    * afterwards.
585    *
586    * @param carbon Alkyl carbon to rename.
587    * @param priorAtom Prior atom in the chain.
588    * @param protonOffset Number of the first hydrogen (such as 2 for HB2-3).
589    * @param posName Name of the position (such as B for CB).
590    * @return Next atom in the chain if present.
591    */
592   public static Optional<Atom> renameAlkyl(Atom carbon, Atom priorAtom, int protonOffset,
593       char posName) {
594     carbon.setName(format("C%c", posName));
595     List<Atom> hydrogen = findBondedAtoms(carbon, 1);
596     int numH = hydrogen.size();
597     if (numH == 1) {
598       hydrogen.get(0).setName(format("H%c", posName));
599     } else {
600       for (int i = 0; i < numH; i++) {
601         hydrogen.get(i).setName(format("H%c%d", posName, i + protonOffset));
602       }
603     }
604 
605     return carbon.getBonds().stream()
606         .map((Bond b) -> b.get1_2(carbon))
607         .filter((Atom a) -> a != priorAtom)
608         .filter((Atom a) -> !hydrogen.contains(a))
609         .findAny();
610   }
611 
612   /**
613    * Renames the Atoms in an amino acid to PDB standard.
614    *
615    * @param residue Residue to fix atom names of.
616    */
617   public static boolean renameAminoAcidToPDBStandard(Residue residue) {
618     if (residue.getChainID() == null) {
619       if (logger.isLoggable(Level.FINE)) {
620         logger.fine(" Setting Chain ID to Z for " + residue);
621       }
622       residue.setChainID('Z');
623     }
624     AminoAcid3 aa3 = residue.getAminoAcid3();
625     final Atom N = findNitrogenAtom(residue);
626     if (N != null) {
627       // Nitrogen
628       N.setName("N");
629 
630       // C-alpha
631       Atom CA = getAlphaCarbon(residue, N);
632       CA.setName("CA");
633 
634       // C-alpha hydrogen
635       List<Atom> hydrogenForCA = findBondedAtoms(CA, 1);
636       switch (aa3) {
637         case NME -> {
638           // Do all renaming here then return out of the method.
639           findBondedAtoms(N, 1).get(0).setName("H");
640           CA.setName("CH3");
641           for (int i = 1; i <= 3; i++) {
642             hydrogenForCA.get(i - 1).setName(format("H%d", i));
643           }
644           return true;
645         }
646         case GLY -> {
647           hydrogenForCA.get(0).setName("HA2");
648           hydrogenForCA.get(1).setName("HA3");
649         }
650         default -> hydrogenForCA.get(0).setName("HA");
651       }
652 
653       // Carbonyl carbon
654       Atom C = null;
655       // C-beta
656       Atom CB = null;
657       for (Atom carbon : findBondedAtoms(CA, 6)) {
658         // Second check is because of serine/threonine OG bonded straight to CB.
659         if (hasAttachedAtom(carbon, 8) && !hasAttachedAtom(carbon, 1)) {
660           C = carbon;
661           C.setName("C");
662         } else {
663           CB = carbon;
664           CB.setName("CB");
665         }
666       }
667       if (C == null) {
668         throw new IllegalArgumentException(
669             format(" The carbonyl carbon for residue %s could not be found.", residue));
670       }
671       if (CB == null && aa3 != AminoAcidUtils.AminoAcid3.GLY) {
672         throw new IllegalArgumentException(
673             format(" The beta carbon for residue %s could not be found.", residue));
674       }
675 
676       // Carbonyl oxygen (1 for mid-chain, 2 for last residue)
677       List<Atom> cTerminalOxygen = findBondedAtoms(C, 8);
678       switch (cTerminalOxygen.size()) {
679         case 1 ->
680           // Mid-chain
681             cTerminalOxygen.get(0).setName("O");
682         case 2 -> {
683           Atom O = null;
684           for (Atom oxygen : cTerminalOxygen) {
685             if (oxygen.getBonds().size() == 2) {
686               O = oxygen;
687               O.setName("OH");
688               findBondedAtoms(O, 1).get(0).setName("HO");
689             }
690           }
691           if (O == null) {
692             cTerminalOxygen.get(0).setName("O");
693             cTerminalOxygen.get(1).setName("OXT");
694           }
695         }
696       }
697 
698       // Nitrogen hydrogen atoms
699       List<Atom> amideProtons = findBondedAtoms(N, 1);
700       if (amideProtons.size() == 1) {
701         amideProtons.get(0).setName("H");
702       } else {// Should catch both N-termini and proline.
703         for (int i = 1; i <= amideProtons.size(); i++) {
704           amideProtons.get(i - 1).setName(format("H%d", i));
705         }
706       }
707 
708       // All common atoms are now named: N, H[1-3], CA, HA[2-3], CB, C, O[XT], [HO]
709       renameCommonAminoAcids(residue, aa3, CA, CB);
710     } else if (aa3 == AminoAcid3.ACE) {
711       Atom O = findAtomsOfElement(residue, 8).get(0);
712       O.setName("O");
713       Atom C = findBondedAtoms(O, 6).get(0);
714       C.setName("C");
715       Atom CH3 = findBondedAtoms(C, 6).get(0);
716       CH3.setName("CH3");
717       List<Atom> hydrogen = findBondedAtoms(CH3, 1);
718       for (int i = 1; i <= 3; i++) {
719         hydrogen.get(i - 1).setName(format("H%d", i));
720       }
721     } else {
722 //      throw new IllegalArgumentException(
723 //              format(" Could not find nitrogen atom for residue %s!", residue));
724       logger.warning(format(" Mapping of nitrogen to residue (%s) failed. Trying to remap to molecule.", residue));
725       return false;
726     }
727     return true;
728   }
729 
730   /**
731    * renameArginineHydrogen.
732    *
733    * @param residue a {@link ffx.potential.bonded.Residue} object.
734    * @param resAtoms a {@link java.util.List} object.
735    */
736   public static void renameArginineHydrogen(Residue residue, List<Atom> resAtoms) {
737     Atom HH11 = (Atom) residue.getAtomNode("HH11");
738     Atom HH12 = (Atom) residue.getAtomNode("HH12");
739     Atom HH21 = (Atom) residue.getAtomNode("HH21");
740     Atom HH22 = (Atom) residue.getAtomNode("HH22");
741     if (HH11 != null) {
742       resAtoms.remove(HH11);
743     }
744     if (HH12 != null) {
745       resAtoms.remove(HH12);
746     }
747     if (HH21 != null) {
748       resAtoms.remove(HH21);
749     }
750     if (HH22 != null) {
751       resAtoms.remove(HH22);
752     }
753     if (!resAtoms.isEmpty() && HH11 == null) {
754       resAtoms.get(0).setName("HH11");
755       resAtoms.remove(0);
756     }
757     if (!resAtoms.isEmpty() && HH12 == null) {
758       resAtoms.get(0).setName("HH12");
759       resAtoms.remove(0);
760     }
761     if (!resAtoms.isEmpty() && HH21 == null) {
762       resAtoms.get(0).setName("HH21");
763       resAtoms.remove(0);
764     }
765     if (!resAtoms.isEmpty() && HH22 == null) {
766       resAtoms.get(0).setName("HH22");
767       resAtoms.remove(0);
768     }
769   }
770 
771   /**
772    * renameAsparagineHydrogen.
773    *
774    * @param residue a {@link ffx.potential.bonded.Residue} object.
775    * @param resAtoms a {@link java.util.List} object.
776    */
777   public static void renameAsparagineHydrogen(Residue residue, List<Atom> resAtoms) {
778     Atom HD21 = (Atom) residue.getAtomNode("HD21");
779     Atom HD22 = (Atom) residue.getAtomNode("HD22");
780     if (HD21 != null) {
781       resAtoms.remove(HD21);
782     }
783     if (HD22 != null) {
784       resAtoms.remove(HD22);
785     }
786     if (!resAtoms.isEmpty() && HD21 == null) {
787       resAtoms.get(0).setName("HD21");
788       resAtoms.remove(0);
789     }
790     if (!resAtoms.isEmpty() && HD22 == null) {
791       resAtoms.get(0).setName("HD21");
792     }
793   }
794 
795   /**
796    * Renames Atoms to PDB standard using bonding patterns, atomic numbers, and residue types.
797    *
798    * <p>Will not work if a force field definition botches its atomic numbers.
799    *
800    * <p>Only implemented for amino acids and nucleic acids at this time.
801    *
802    * @param molecularAssembly MolecularAssembly to fix.
803    */
804   public static void renameAtomsToPDBStandard(MolecularAssembly molecularAssembly) {
805     Polymer[] polymers = molecularAssembly.getChains();
806     if (polymers != null) {
807       for (Polymer polymer : polymers) {
808         for (Residue residue : polymer.getResidues()) {
809           switch (residue.getResidueType()) {
810             case AA -> renameAminoAcidToPDBStandard(residue);
811             case NA -> renameNucleicAcidToPDBStandard(residue);
812             case UNK -> {
813               // Do nothing.
814             }
815           }
816         }
817       }
818     }
819   }
820 
821   /**
822    * renameBetaHydrogen.
823    *
824    * @param residue a {@link ffx.potential.bonded.Residue} object.
825    * @param resAtoms a {@link java.util.List} object.
826    * @param indexes The HB indexes to use.
827    */
828   public static void renameBetaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
829     Atom[] HBn = new Atom[3];
830     switch (indexes) {
831       case 12 -> {
832         HBn[0] = (Atom) residue.getAtomNode("HB1");
833         HBn[1] = (Atom) residue.getAtomNode("HB2");
834       }
835       case 13 -> {
836         HBn[0] = (Atom) residue.getAtomNode("HB1");
837         HBn[2] = (Atom) residue.getAtomNode("HB3");
838       }
839       case 23 -> {
840         HBn[1] = (Atom) residue.getAtomNode("HB2");
841         HBn[2] = (Atom) residue.getAtomNode("HB3");
842       }
843       default -> {
844         return;
845       }
846     }
847     for (Atom HBatom : HBn) {
848       resAtoms.remove(HBatom);
849     }
850     if (!resAtoms.isEmpty() && HBn[0] == null && (indexes == 12 || indexes == 13)) {
851       resAtoms.get(0).setName("HB1");
852       resAtoms.remove(0);
853     }
854     if (!resAtoms.isEmpty() && HBn[1] == null && (indexes == 12 || indexes == 23)) {
855       resAtoms.get(0).setName("HB2");
856       resAtoms.remove(0);
857     }
858     if (!resAtoms.isEmpty() && HBn[2] == null && (indexes == 13 || indexes == 23)) {
859       resAtoms.get(0).setName("HB3");
860       resAtoms.remove(0);
861     }
862   }
863 
864   /**
865    * Renames a numbered carbon, its bonded hydrogen, and returns the next atom in the chain.
866    *
867    * <p>If applied to an atom that is not a carbon, it will be misnamed as a carbon, so fix that
868    * afterwards.
869    *
870    * <p>This is for carbons like PHE CD1 and CD2.
871    *
872    * @param carbon Alkyl carbon to rename.
873    * @param priorAtom Prior atom in the chain.
874    * @param protonOffset Number of the first hydrogen (such as 2 for HB2-3).
875    * @param branchNum Index of the branch.
876    * @param posName Name of the position (such as B for CB).
877    * @return Next atom in the chain if present.
878    */
879   public static Optional<Atom> renameBranchedAlkyl(Atom carbon, Atom priorAtom, int protonOffset,
880       int branchNum, char posName) {
881     carbon.setName(format("C%c%d", posName, branchNum));
882     List<Atom> hydrogen = findBondedAtoms(carbon, 1);
883     int numH = hydrogen.size();
884     if (numH == 1) {
885       hydrogen.get(0).setName(format("H%c%d", posName, branchNum));
886     } else {
887       for (int i = 0; i < numH; i++) {
888         hydrogen.get(i).setName(format("H%c%d%d", posName, branchNum, i + protonOffset));
889       }
890     }
891 
892     return carbon.getBonds().stream()
893         .map((Bond b) -> b.get1_2(carbon))
894         .filter((Atom a) -> a != priorAtom)
895         .filter((Atom a) -> !hydrogen.contains(a))
896         .findAny();
897   }
898 
899   /**
900    * Renames atoms in common amino acids to PDB standard.
901    *
902    * @param residue Residue to perform renaming for.
903    * @param aa3 Its AA3 code.
904    * @param CA Its alpha carbon.
905    * @param CB Its beta carbon.
906    */
907   public static void renameCommonAminoAcids(Residue residue, AminoAcid3 aa3, Atom CA, Atom CB) {
908     switch (aa3) {
909       case ALA: {
910         renameAlkyl(CB, CA, 1, 'B');
911       }
912       break;
913       case CYS:
914       case CYD: {
915         Atom SG = renameAlkyl(CB, CA, 2, 'B').get();
916         SG.setName("SG");
917         if (hasAttachedAtom(SG, 1)) {
918           assert aa3 == AminoAcidUtils.AminoAcid3.CYS;
919           findBondedAtoms(SG, 1).get(0).setName("HG");
920         } else if (hasAttachedAtom(SG, 16)) {
921           logger.finer(format(" SG atom %s likely part of a disulfide bond.", SG));
922         } else {
923           residue.setName("CYD");
924         }
925       }
926       break;
927       case ASP:
928       case ASH:
929       case ASD: {
930         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
931         CG.setName("CG");
932         List<Atom> ODs = findBondedAtoms(CG, 8);
933 
934         int protonatedOD = -1; // -1: Deprotonated ASP. 0/1: Index of protonated oxygen (ASH).
935         for (int i = 0; i < 2; i++) {
936           if (hasAttachedAtom(ODs.get(i), 1)) {
937             protonatedOD = i;
938             break;
939           }
940         }
941 
942         // Check for double protonation for constant pH.
943         if (hasAttachedAtom(ODs.get(0), 1) && hasAttachedAtom(ODs.get(1), 1)) {
944           protonatedOD = 2;
945         }
946 
947         switch (protonatedOD) {
948           case -1 -> {
949             ODs.get(0).setName("OD1");
950             ODs.get(1).setName("OD2");
951           }
952           case 0 -> {
953             if (aa3 != AminoAcid3.ASH) {
954               residue.setName("ASH");
955             }
956             ODs.get(0).setName("OD2");
957             findBondedAtoms(ODs.get(0), 1).get(0).setName("HD2");
958             ODs.get(1).setName("OD1");
959           }
960           case 1 -> {
961             if (aa3 != AminoAcid3.ASH) {
962               residue.setName("ASH");
963             }
964             ODs.get(1).setName("OD2");
965             findBondedAtoms(ODs.get(1), 1).get(0).setName("HD2");
966             ODs.get(0).setName("OD1");
967           }
968           case 2 -> {
969             if (aa3 != AminoAcid3.ASD) {
970               residue.setName("ASD");
971             }
972             ODs.get(0).setName("OD1");
973             findBondedAtoms(ODs.get(0), 1).get(0).setName("HD1");
974             ODs.get(1).setName("OD2");
975             findBondedAtoms(ODs.get(1), 1).get(0).setName("HD2");
976           }
977         }
978       }
979       break;
980       case GLU:
981       case GLH:
982       case GLD: {
983         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
984         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
985         CD.setName("CD");
986         List<Atom> OEs = findBondedAtoms(CD, 8);
987 
988         int protonatedOE = -1; // If it remains -1, deprotonated ASP, else ASH.
989         for (int i = 0; i < 2; i++) {
990           if (hasAttachedAtom(OEs.get(i), 1)) {
991             protonatedOE = i;
992             break;
993           }
994         }
995 
996         // Check for double protonation for constant pH.
997         if (hasAttachedAtom(OEs.get(0), 1) && hasAttachedAtom(OEs.get(1), 1)) {
998           protonatedOE = 2;
999         }
1000 
1001         switch (protonatedOE) {
1002           case -1 -> {
1003             OEs.get(0).setName("OE1");
1004             OEs.get(1).setName("OE2");
1005           }
1006           case 0 -> {
1007             if (aa3 != AminoAcid3.GLH) {
1008               residue.setName("GLH");
1009             }
1010             OEs.get(0).setName("OE2");
1011             findBondedAtoms(OEs.get(0), 1).get(0).setName("HE2");
1012             OEs.get(1).setName("OE1");
1013           }
1014           case 1 -> {
1015             if (aa3 != AminoAcid3.GLH) {
1016               residue.setName("GLH");
1017             }
1018             OEs.get(1).setName("OE2");
1019             findBondedAtoms(OEs.get(1), 1).get(0).setName("HE2");
1020             OEs.get(0).setName("OE1");
1021           }
1022           case 2 -> {
1023             if (aa3 != AminoAcid3.GLD) {
1024               residue.setName("GLD");
1025             }
1026             OEs.get(0).setName("OE1");
1027             findBondedAtoms(OEs.get(0), 1).get(0).setName("HE1");
1028             OEs.get(1).setName("OE2");
1029             findBondedAtoms(OEs.get(1), 1).get(0).setName("HE2");
1030           }
1031         }
1032       }
1033       break;
1034       case PHE: {
1035         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1036         CG.setName("CG");
1037         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1038 
1039         Atom CZ = null;
1040         for (int i = 1; i <= 2; i++) {
1041           Atom CD = CDs.get(i - 1);
1042           Atom CE = renameBranchedAlkyl(CD, CG, 0, i, 'D').get();
1043           CZ = renameBranchedAlkyl(CE, CD, 0, i, 'E').get();
1044         }
1045         CZ.setName("CZ");
1046         findBondedAtoms(CZ, 1).get(0).setName("HZ");
1047       }
1048       break;
1049       case GLY:
1050         break;
1051       case HIS:
1052       case HIE:
1053       case HID: {
1054         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1055         CG.setName("CG");
1056 
1057         Atom CD2 = findBondedAtoms(CG, 6).stream().filter((Atom a) -> a != CB).findAny().get();
1058         CD2.setName("CD2");
1059         findBondedAtoms(CD2, 1).get(0).setName("HD2");
1060 
1061         Atom NE2 = findBondedAtoms(CD2, 7).get(0);
1062         NE2.setName("NE2");
1063         List<Atom> HE2 = findBondedAtoms(NE2, 1);
1064         boolean epsProtonated = (HE2 != null && !HE2.isEmpty());
1065         if (epsProtonated) {
1066           HE2.get(0).setName("HE2");
1067         }
1068 
1069         Atom CE1 = findBondedAtoms(NE2, CD2, 6).get(0);
1070         CE1.setName("CE1");
1071         findBondedAtoms(CE1, 1).get(0).setName("HE1");
1072 
1073         Atom ND1 = findBondedAtoms(CG, 7).get(0);
1074         ND1.setName("ND1");
1075         List<Atom> HD1 = findBondedAtoms(ND1, 1);
1076         boolean deltaProtonated = (HD1 != null && !HD1.isEmpty());
1077         if (deltaProtonated) {
1078           HD1.get(0).setName("HD1");
1079         }
1080 
1081         // All constant atoms found: now check protonation state.
1082         if (epsProtonated && deltaProtonated) {
1083           assert aa3 == AminoAcidUtils.AminoAcid3.HIS;
1084         } else if (epsProtonated) {
1085           residue.setName("HIE");
1086         } else if (deltaProtonated) {
1087           residue.setName("HID");
1088         } else {
1089           throw new IllegalArgumentException(
1090               format(" Histidine residue %s is doubly deprotonated!", residue));
1091         }
1092       }
1093       break;
1094       case ILE: {
1095         findBondedAtoms(CB, 1).get(0).setName("HB");
1096         List<Atom> CGs = findBondedAtoms(CB, CA, 6);
1097 
1098         for (Atom CG : CGs) {
1099           List<Atom> HGs = findBondedAtoms(CG, 1);
1100           int numHGs = HGs.size();
1101           if (numHGs == 3) {
1102             renameBranchedAlkyl(CG, CB, 1, 2, 'G');
1103           } else if (numHGs == 2) {
1104             Atom CD1 = renameBranchedAlkyl(CG, CB, 2, 1, 'G').get();
1105             renameBranchedAlkyl(CD1, CG, 1, 1, 'D');
1106           } else {
1107             throw new IllegalArgumentException(
1108                 format(
1109                     " Isoleucine residue %s had %d gamma hydrogen, expecting 2-3!",
1110                     residue, numHGs));
1111           }
1112         }
1113       }
1114       break;
1115       case LYS:
1116       case LYD: {
1117         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1118         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1119         Atom CE = renameAlkyl(CD, CG, 2, 'D').get();
1120         Atom NZ = renameAlkyl(CE, CD, 2, 'E').get();
1121         // For a very brief period, NZ will be named CZ.
1122         renameAlkyl(NZ, CE, 1, 'Z');
1123         NZ.setName("NZ");
1124         int numH = findBondedAtoms(NZ, 1).size();
1125         switch (numH) {
1126           case 2 -> residue.setName("LYD");
1127           case 3 -> {
1128             assert aa3 == AminoAcid3.LYS;
1129           }
1130           default -> throw new IllegalArgumentException(
1131               format(" Lysine residue %s had %d amine protons, expecting 2-3!", residue, numH));
1132         }
1133       }
1134       break;
1135       case LEU: {
1136         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1137         CG.setName("CG");
1138         findBondedAtoms(CG, 1).get(0).setName("HG");
1139         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1140 
1141         for (int i = 0; i < 2; i++) {
1142           renameBranchedAlkyl(CDs.get(i), CG, 1, (i + 1), 'D');
1143         }
1144       }
1145       break;
1146       case MET: {
1147         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1148         Atom SD = renameAlkyl(CG, CB, 2, 'G').get();
1149         Atom CE = renameAlkyl(SD, CG, 0, 'D').get();
1150         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1151         SD.setName("SD");
1152         renameAlkyl(CE, SD, 1, 'E');
1153       }
1154       break;
1155       case ASN: {
1156         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1157         CG.setName("CG");
1158         findBondedAtoms(CG, 8).get(0).setName("OD1");
1159         Atom ND2 = findBondedAtoms(CG, 7).get(0);
1160         renameBranchedAlkyl(ND2, CG, 1, 2, 'D');
1161         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1162         ND2.setName("ND2");
1163       }
1164       break;
1165       case PRO: {
1166         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1167         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1168         Atom N = renameAlkyl(CD, CG, 2, 'D').get();
1169         assert N.getName().equals("N");
1170       }
1171       break;
1172       case GLN: {
1173         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1174         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1175         CD.setName("CD");
1176 
1177         findBondedAtoms(CD, 8).get(0).setName("OE1");
1178         Atom NE2 = findBondedAtoms(CD, 7).get(0);
1179         renameBranchedAlkyl(NE2, CD, 1, 2, 'E');
1180         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1181         NE2.setName("NE2");
1182       }
1183       break;
1184       case ARG: {
1185         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1186         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1187         Atom NE = renameAlkyl(CD, CG, 2, 'D').get();
1188         Atom CZ = renameAlkyl(NE, CD, 0, 'E').get();
1189         NE.setName("NE");
1190         CZ.setName("CZ");
1191 
1192         List<Atom> NHs = findBondedAtoms(CZ, NE, 7);
1193         assert NHs.size() == 2;
1194         for (int i = 0; i < 2; i++) {
1195           Atom NHx = NHs.get(i);
1196           renameBranchedAlkyl(NHx, CZ, 1, (i + 1), 'H');
1197           NHx.setName(format("NH%d", (i + 1)));
1198         }
1199       }
1200       break;
1201       case SER: {
1202         Atom OG = renameAlkyl(CB, CA, 2, 'B').get();
1203         renameAlkyl(OG, CB, 0, 'G');
1204         OG.setName("OG");
1205       }
1206       break;
1207       case THR: {
1208         CB.setName("CB"); // Should be unnecessary.
1209         findBondedAtoms(CB, 1).get(0).setName("HB");
1210 
1211         Atom OG1 = findBondedAtoms(CB, 8).get(0);
1212         OG1.setName("OG1");
1213         findBondedAtoms(OG1, 1).get(0).setName("HG1");
1214 
1215         Atom CG2 = findBondedAtoms(CB, CA, 6).get(0);
1216         renameBranchedAlkyl(CG2, CB, 1, 2, 'G');
1217       }
1218       break;
1219       case VAL: {
1220         CB.setName("CB"); // Should be unnecessary.
1221         findBondedAtoms(CB, 1).get(0).setName("HB");
1222 
1223         List<Atom> CGs = findBondedAtoms(CB, CA, 6);
1224 
1225         assert CGs.size() == 2;
1226         for (int i = 0; i < 2; i++) {
1227           Atom CGx = CGs.get(i);
1228           renameBranchedAlkyl(CGx, CB, 1, (i + 1), 'G');
1229         }
1230       }
1231       break;
1232       case TRP: {
1233         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1234         CG.setName("CG");
1235         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1236         Atom CD1 = null;
1237         Atom CD2 = null;
1238 
1239         for (Atom CDx : CDs) {
1240           if (hasAttachedAtom(CDx, 1)) {
1241             CD1 = CDx;
1242           } else {
1243             CD2 = CDx;
1244             CD2.setName("CD2");
1245           }
1246         }
1247         Atom NE1 = renameBranchedAlkyl(CD1, CG, 0, 1, 'D').get();
1248         Atom CE2 = renameBranchedAlkyl(NE1, CD1, 0, 1, 'E').get();
1249         NE1.setName("NE1");
1250         CE2.setName("CE2");
1251 
1252         Atom CZ2 = findBondedAtoms(CE2, CD2, 6).get(0);
1253         Atom CH2 = renameBranchedAlkyl(CZ2, CE2, 0, 2, 'Z').get();
1254         Atom CZ3 = renameBranchedAlkyl(CH2, CZ2, 0, 2, 'H').get();
1255         Atom CE3 = renameBranchedAlkyl(CZ3, CH2, 0, 3, 'Z').get();
1256         if (CD2 != renameBranchedAlkyl(CE3, CZ3, 0, 3, 'E').get()) {
1257           throw new IllegalArgumentException(
1258               format(" Error in cyclizing tryptophan %s!", residue));
1259         }
1260       }
1261       break;
1262       case TYR:
1263       case TYD: {
1264         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1265         CG.setName("CG");
1266         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1267         Atom CZ = null;
1268 
1269         assert CDs.size() == 2;
1270         for (int i = 1; i <= 2; i++) {
1271           Atom CDx = CDs.get(i - 1);
1272           Atom CEx = renameBranchedAlkyl(CDx, CG, 0, i, 'D').get();
1273           CZ = renameBranchedAlkyl(CEx, CDx, 0, i, 'E').get();
1274         }
1275 
1276         CZ.setName("CZ");
1277         Atom OH = findBondedAtoms(CZ, 8).get(0);
1278         OH.setName("OH");
1279         if (hasAttachedAtom(OH, 1)) {
1280           assert aa3 == AminoAcidUtils.AminoAcid3.TYR;
1281           findBondedAtoms(OH, 1).get(0).setName("HH");
1282         } else {
1283           residue.setName("TYD");
1284         }
1285       }
1286       break;
1287       default:
1288         throw new IllegalArgumentException(
1289             (format(" Amino acid %s (%s) not recognized!", residue, aa3)));
1290     }
1291   }
1292 
1293   /**
1294    * Renames atoms in common nucleic acids to PDB standard.
1295    *
1296    * @param residue Residue to perform renaming for.
1297    * @param na3 Its NA3 code.
1298    */
1299   public static void renameCommonNucleicAcid(Residue residue, NucleicAcid3 na3) {
1300     Optional<Atom> optO4s = findNucleotideO4s(residue);
1301     if (optO4s.isPresent()) {
1302       // Name O4', which is the unique ether oxygen.
1303       Atom O4s = optO4s.get();
1304       O4s.setName("O4'");
1305 
1306       // C1' is bonded to a nitrogen (at least for non-abasic sites), C4' isn't
1307       List<Atom> bondedC = findBondedAtoms(O4s, 6);
1308       Atom C4s = null;
1309       Atom C1s = null;
1310       // Will need the first base nitrogen (N1/N9), and H1' later.
1311       Atom N19 = null;
1312       Atom H1s = null;
1313       for (Atom c : bondedC) {
1314         if (hasAttachedAtom(c, 7)) {
1315           C1s = c;
1316           C1s.setName("C1'");
1317           H1s = findBondedAtoms(C1s, 1).get(0);
1318           H1s.setName("H1'");
1319           N19 = findBondedAtoms(C1s, 7).get(0);
1320         } else {
1321           C4s = c;
1322           C4s.setName("C4'");
1323           findBondedAtoms(C4s, 1).get(0).setName("H4'");
1324         }
1325       }
1326       assert C4s != null && C1s != null;
1327 
1328       Atom C2s = findBondedAtoms(C1s, 6).get(0);
1329       C2s.setName("C2'");
1330 
1331       bondedC = findBondedAtoms(C4s, 6);
1332       Atom C5s = null;
1333       Atom C3s;
1334       Atom O3s;
1335       for (Atom c : bondedC) {
1336         if (c.getBonds().stream().anyMatch(b -> b.get1_2(c) == C2s)) {
1337           C3s = c;
1338           C3s.setName("C3'");
1339           O3s = findBondedAtoms(C3s, 8).get(0);
1340           O3s.setName("O3'");
1341           findBondedAtoms(C3s, 1).get(0).setName("H3'");
1342           if (hasAttachedAtom(O3s, 1)) {
1343             findBondedAtoms(O3s, 1).get(0).setName("HO3'");
1344           } // Else, handle the possibility of 3'-P cap later.
1345         } else {
1346           C5s = c;
1347           C5s.setName("C5'");
1348           List<Atom> allH5List = findBondedAtoms(C5s, 1);
1349           Atom[] allH5s = allH5List.toArray(new Atom[0]);
1350           sortAtomsByDistance(O4s, allH5s);
1351           allH5s[0].setName("H5'");
1352           allH5s[1].setName("H5''");
1353         }
1354       }
1355 
1356       if (hasAttachedAtom(C2s, 8)) {
1357         Atom O2s = findBondedAtoms(C2s, 8).get(0);
1358         O2s.setName("O2'");
1359         findBondedAtoms(O2s, 1).get(0).setName("HO2'");
1360         findBondedAtoms(C2s, 1).get(0).setName("H2'");
1361       } else {
1362         List<Atom> bothH2List = findBondedAtoms(C2s, 1);
1363         Atom[] bothH2 = bothH2List.toArray(new Atom[0]);
1364         sortAtomsByDistance(H1s, bothH2);
1365         // Best-guess assignment, but is sometimes the other way around.
1366         bothH2[0].setName("H2''");
1367         bothH2[1].setName("H2'");
1368       }
1369 
1370       // logger.info(format(" C5\' null: %b", C5s == null));
1371       Atom O5s = findBondedAtoms(C5s, 8).get(0);
1372       O5s.setName("O5'");
1373 
1374       if (hasAttachedAtom(O5s, 1)) {
1375         findBondedAtoms(O5s, 1).get(0).setName("HO5'");
1376       } else if (hasAttachedAtom(O5s, 15)) {
1377         Atom P = findBondedAtoms(O5s, 15).get(0);
1378         P.setName("P");
1379         List<Atom> bondedO = findBondedAtoms(P, O5s, 8);
1380         List<Atom> thisResO = bondedO.stream().filter(o -> residue.getAtomList().contains(o))
1381             .toList();
1382         int nBonded = bondedO.size();
1383         int nRes = thisResO.size();
1384         if (nBonded == 0) {
1385           // Do nothing.
1386         } else if (nBonded == nRes) {
1387           Atom OP1 = bondedO.get(0);
1388           OP1.setName("OP1");
1389           // OP2 is approximately +120 degrees from OP1, OP3 is -120 degrees.
1390           final double[] xyzC5s = C5s.getXYZ(new double[3]);
1391           final double[] xyzO5s = O5s.getXYZ(new double[3]);
1392           final double[] xyzP = P.getXYZ(new double[3]);
1393           final double[] xyzOP1 = OP1.getXYZ(new double[3]);
1394           double dihedral = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzOP1);
1395           double twoPiOver3 = 2.0 * PI / 3.0;
1396           double target = modToRange(dihedral + twoPiOver3, -PI, PI);
1397           List<Atom> otherO = bondedO.stream().filter(o -> o != OP1).sorted(
1398               Comparator.comparingDouble((Atom o) -> {
1399                 double[] xyzO = o.getXYZ(new double[3]);
1400                 double dihedO = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzO);
1401                 double diff = dihedO - target;
1402                 double twoPi = 2 * PI;
1403                 diff = modToRange(diff, 0, twoPi);
1404                 diff = diff < PI ? diff : twoPi - diff;
1405                 return diff;
1406               })).toList();
1407           for (int i = 0; i < otherO.size(); i++) {
1408             otherO.get(i).setName(format("OP%d", i + 2));
1409           }
1410         } else {
1411           Atom nextO3s =
1412               bondedO.stream().filter(o -> !residue.getAtomList().contains(o)).findAny().get();
1413 
1414           // OP1 is approximately +120 degrees from next O3', OP2 is -120 degrees.
1415           final double[] xyzC5s = C5s.getXYZ(new double[3]);
1416           final double[] xyzO5s = O5s.getXYZ(new double[3]);
1417           final double[] xyzP = P.getXYZ(new double[3]);
1418           final double[] xyzNextO3s = nextO3s.getXYZ(new double[3]);
1419           double dihedral = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzNextO3s);
1420           double twoPiOver3 = 2.0 * PI / 3.0;
1421           double target = modToRange(dihedral + twoPiOver3, -PI, PI);
1422           List<Atom> otherO = bondedO.stream().filter(o -> o != nextO3s).sorted(
1423               Comparator.comparingDouble((Atom o) -> {
1424                 double[] xyzO = o.getXYZ(new double[3]);
1425                 double dihedO = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzO);
1426                 double diff = dihedO - target;
1427                 double twoPi = 2 * PI;
1428                 diff = modToRange(diff, 0, twoPi);
1429                 diff = diff < PI ? diff : twoPi - diff;
1430                 return diff;
1431               })).toList();
1432           for (int i = 0; i < otherO.size(); i++) {
1433             otherO.get(i).setName(format("OP%d", i + 1));
1434           }
1435         }
1436 
1437         for (Atom op : bondedO) {
1438           if (hasAttachedAtom(op, 1)) {
1439             findBondedAtoms(op, 1).get(0).setName("H" + op.getName());
1440           }
1441         }
1442       }
1443       renameCommonNucleobase(N19, C1s, na3);
1444     } else {
1445       logger.warning(" Could not find O4' for residue " + residue);
1446     }
1447   }
1448 
1449   /**
1450    * Renames the atoms of the common nucleobases (A, C, G, T, U, and deoxy variants).
1451    *
1452    * @param N19 N1 of pyrimidines, N9 of purines.
1453    * @param C1s C1' of the ribose sugar.
1454    * @param na3 Identity of the nucleic acid.
1455    */
1456   public static void renameCommonNucleobase(Atom N19, Atom C1s, NucleicAcid3 na3) {
1457     switch (na3) {
1458       case ADE, DAD -> {
1459         Map<String, Atom> purineBase = renameCommonPurine(N19, C1s);
1460         // Unique to A: H2, N6, H6[12]
1461         findBondedAtoms(purineBase.get("C2"), 1).get(0).setName("H2");
1462         Atom C6 = purineBase.get("C6");
1463         Atom N1 = purineBase.get("N1");
1464         Atom N6 = findBondedAtoms(C6, N1, 7).get(0);
1465         N6.setName("N6");
1466         List<Atom> allH6List = findBondedAtoms(N6, 1);
1467         Atom[] allH6 = sortAtomsByDistance(N1, allH6List);
1468         allH6[0].setName("H61");
1469         allH6[1].setName("H62");
1470       }
1471       case CYT, DCY -> {
1472         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1473         // Unique to C: N4, H4[12]
1474         Atom C4 = pyrimidineBase.get("C4");
1475         Atom N3 = pyrimidineBase.get("N3");
1476         Atom N4 = findBondedAtoms(C4, N3, 7).get(0);
1477         N4.setName("N4");
1478         Atom[] allH4 = sortAtomsByDistance(N3, findBondedAtoms(N4, 1));
1479         allH4[0].setName("H41");
1480         allH4[1].setName("H42");
1481       }
1482       case GUA, DGU -> {
1483         Map<String, Atom> purineBase = renameCommonPurine(N19, C1s);
1484         // Unique to G: H1, N2, H2[12], O6
1485         Atom N1 = purineBase.get("N1");
1486         Atom C2 = purineBase.get("C2");
1487         Atom C6 = purineBase.get("C6");
1488         findBondedAtoms(N1, 1).get(0).setName("H1");
1489         Atom N2 =
1490             findBondedAtoms(C2, N1, 7).stream()
1491                 .filter(n -> hasAttachedAtom(n, 1))
1492                 .findAny()
1493                 .get();
1494         N2.setName("N2");
1495         Atom[] allH2 = sortAtomsByDistance(N1, findBondedAtoms(N2, 1));
1496         allH2[0].setName("H21");
1497         allH2[1].setName("H22");
1498         findBondedAtoms(C6, 8).get(0).setName("O6");
1499       }
1500       case URI -> {
1501         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1502         // Unique to U: H3, O4
1503         findBondedAtoms(pyrimidineBase.get("N3"), 1).get(0).setName("H3");
1504         findBondedAtoms(pyrimidineBase.get("C4"), 8).get(0).setName("O4");
1505       }
1506       case THY, DTY -> {
1507         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1508         // Unique to T: H3, O4, C7
1509         findBondedAtoms(pyrimidineBase.get("N3"), 1).get(0).setName("H3");
1510         findBondedAtoms(pyrimidineBase.get("C4"), 8).get(0).setName("O4");
1511         Atom C5 = pyrimidineBase.get("C5");
1512         for (Atom c : findBondedAtoms(C5, 6)) {
1513           List<Atom> bondedH = findBondedAtoms(c, 1);
1514           if (bondedH != null && bondedH.size() == 3) {
1515             c.setName("C7");
1516             for (int i = 0; i < 3; i++) {
1517               bondedH.get(i).setName(format("H7%d", i + 1));
1518             }
1519             break;
1520           }
1521         }
1522       }
1523     }
1524   }
1525 
1526   /**
1527    * Renames atoms common to all standard purines (A, G)
1528    *
1529    * @param N9 The N9 atom.
1530    * @param C1s The C1' atom.
1531    * @return A Map containing Atoms important to finding and naming base-unique atoms.
1532    */
1533   public static Map<String, Atom> renameCommonPurine(Atom N9, Atom C1s) {
1534     Map<String, Atom> keyAtoms = new HashMap<>(10);
1535     N9.setName("N9");
1536     for (Atom c : findBondedAtoms(N9, C1s, 6)) {
1537       if (hasAttachedAtom(c, 1)) {
1538         Atom C8 = c;
1539         C8.setName("C8");
1540         findBondedAtoms(C8, 1).get(0).setName("H8");
1541         Atom N7 = findBondedAtoms(C8, N9, 7).get(0);
1542         N7.setName("N7");
1543         Atom C5 = findBondedAtoms(N7, C8, 6).get(0);
1544         C5.setName("C5");
1545       } else {
1546         Atom C4 = c;
1547         C4.setName("C4");
1548         Atom N3 = findBondedAtoms(C4, N9, 7).get(0);
1549         N3.setName("N3");
1550         Atom C2 = findBondedAtoms(N3, C4, 6).get(0);
1551         C2.setName("C2");
1552         keyAtoms.put("C2", C2);
1553         Atom N1 = findBondedAtoms(C2, N3, 7).get(0);
1554         N1.setName("N1"); // And not, say, "largest non-nuclear explosion ever".
1555         keyAtoms.put("N1", N1);
1556         Atom C6 = findBondedAtoms(N1, C2, 6).get(0);
1557         C6.setName("C6");
1558         keyAtoms.put("C6", C6);
1559       }
1560     }
1561     /* Common atoms: N1, C2, N3, C4, C5, C6, N7, C8, H8, N9. */
1562     return keyAtoms;
1563   }
1564 
1565   /**
1566    * Renames atoms common to all standard pyrimidines (C, T, U)
1567    *
1568    * @param N1 The N1 atom.
1569    * @param C1s The C1' atom.
1570    * @return A Map containing Atoms important to finding and naming base-unique atoms.
1571    */
1572   public static Map<String, Atom> renameCommonPyrimidine(Atom N1, Atom C1s) {
1573     Map<String, Atom> keyAtoms = new HashMap<>();
1574     N1.setName("N1");
1575     for (Atom c : findBondedAtoms(N1, C1s, 6)) {
1576       if (hasAttachedAtom(c, 8)) {
1577         Atom C2 = c;
1578         C2.setName("C2");
1579         findBondedAtoms(C2, 8).get(0).setName("O2");
1580         Atom N3 = findBondedAtoms(C2, N1, 7).get(0);
1581         N3.setName("N3");
1582         keyAtoms.put("N3", N3);
1583         Atom C4 = findBondedAtoms(N3, C2, 6).get(0);
1584         C4.setName("C4");
1585         keyAtoms.put("C4", C4);
1586         Atom C5 = findBondedAtoms(C4, 6).get(0);
1587         C5.setName("C5");
1588         keyAtoms.put("C5", C5);
1589         if (hasAttachedAtom(C5, 1)) {
1590           findBondedAtoms(C5, 1).get(0).setName("H5");
1591         }
1592       } else {
1593         Atom C6 = c;
1594         C6.setName("C6");
1595         findBondedAtoms(C6, 1).get(0).setName("H6");
1596       }
1597     }
1598 
1599     // Common atoms: N1, C2, O2, N3, C4, C5, C6, H6
1600     return keyAtoms;
1601   }
1602 
1603   /**
1604    * renameDeltaHydrogen.
1605    *
1606    * @param residue a {@link ffx.potential.bonded.Residue} object.
1607    * @param resAtoms a {@link java.util.List} object.
1608    * @param indexes The HD indexes to use.
1609    */
1610   public static void renameDeltaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1611     Atom[] HDn = new Atom[3];
1612     switch (indexes) {
1613       case 12 -> {
1614         HDn[0] = (Atom) residue.getAtomNode("HD1");
1615         HDn[1] = (Atom) residue.getAtomNode("HD2");
1616       }
1617       case 13 -> {
1618         HDn[0] = (Atom) residue.getAtomNode("HD1");
1619         HDn[2] = (Atom) residue.getAtomNode("HD3");
1620       }
1621       case 23 -> {
1622         HDn[1] = (Atom) residue.getAtomNode("HD2");
1623         HDn[2] = (Atom) residue.getAtomNode("HD3");
1624       }
1625       default -> {
1626         return;
1627       }
1628     }
1629     for (Atom HDatom : HDn) {
1630       resAtoms.remove(HDatom);
1631     }
1632     if (!resAtoms.isEmpty() && HDn[0] == null && (indexes == 12 || indexes == 13)) {
1633       resAtoms.get(0).setName("HD1");
1634       resAtoms.remove(0);
1635     }
1636     if (!resAtoms.isEmpty() && HDn[1] == null && (indexes == 12 || indexes == 23)) {
1637       resAtoms.get(0).setName("HD2");
1638       resAtoms.remove(0);
1639     }
1640     if (!resAtoms.isEmpty() && HDn[2] == null && (indexes == 13 || indexes == 23)) {
1641       resAtoms.get(0).setName("HD3");
1642       resAtoms.remove(0);
1643     }
1644   }
1645 
1646   /**
1647    * renameEpsilonHydrogen.
1648    *
1649    * @param residue a {@link ffx.potential.bonded.Residue} object.
1650    * @param resAtoms a {@link java.util.List} object.
1651    * @param indexes The HE indexes to use.
1652    */
1653   public static void renameEpsilonHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1654     Atom[] HEn = new Atom[3];
1655     switch (indexes) {
1656       case 12 -> {
1657         HEn[0] = (Atom) residue.getAtomNode("HE1");
1658         HEn[1] = (Atom) residue.getAtomNode("HE2");
1659       }
1660       case 13 -> {
1661         HEn[0] = (Atom) residue.getAtomNode("HE1");
1662         HEn[2] = (Atom) residue.getAtomNode("HE3");
1663       }
1664       case 23 -> {
1665         HEn[1] = (Atom) residue.getAtomNode("HE2");
1666         HEn[2] = (Atom) residue.getAtomNode("HE3");
1667       }
1668       default -> {
1669         return;
1670       }
1671     }
1672     for (Atom HEatom : HEn) {
1673       resAtoms.remove(HEatom);
1674     }
1675     if (!resAtoms.isEmpty() && HEn[0] == null && (indexes == 12 || indexes == 13)) {
1676       resAtoms.get(0).setName("HE1");
1677       resAtoms.remove(0);
1678     }
1679     if (!resAtoms.isEmpty() && HEn[1] == null && (indexes == 12 || indexes == 23)) {
1680       resAtoms.get(0).setName("HE2");
1681       resAtoms.remove(0);
1682     }
1683     if (!resAtoms.isEmpty() && HEn[2] == null && (indexes == 13 || indexes == 23)) {
1684       resAtoms.get(0).setName("HE3");
1685       resAtoms.remove(0);
1686     }
1687   }
1688 
1689   /**
1690    * renameGammaHydrogen.
1691    *
1692    * @param residue a {@link ffx.potential.bonded.Residue} object.
1693    * @param resAtoms a {@link java.util.List} object.
1694    * @param indexes The HG indexes to use.
1695    */
1696   public static void renameGammaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1697     Atom[] HGn = new Atom[3];
1698     switch (indexes) {
1699       case 12 -> {
1700         HGn[0] = (Atom) residue.getAtomNode("HG1");
1701         HGn[1] = (Atom) residue.getAtomNode("HG2");
1702       }
1703       case 13 -> {
1704         HGn[0] = (Atom) residue.getAtomNode("HG1");
1705         HGn[2] = (Atom) residue.getAtomNode("HG3");
1706       }
1707       case 23 -> {
1708         HGn[1] = (Atom) residue.getAtomNode("HG2");
1709         HGn[2] = (Atom) residue.getAtomNode("HG3");
1710       }
1711       default -> {
1712         return;
1713       }
1714     }
1715     for (Atom HGatom : HGn) {
1716       resAtoms.remove(HGatom);
1717     }
1718     if (!resAtoms.isEmpty() && HGn[0] == null && (indexes == 12 || indexes == 13)) {
1719       resAtoms.get(0).setName("HG1");
1720       resAtoms.remove(0);
1721     }
1722     if (!resAtoms.isEmpty() && HGn[1] == null && (indexes == 12 || indexes == 23)) {
1723       resAtoms.get(0).setName("HG2");
1724       resAtoms.remove(0);
1725     }
1726     if (!resAtoms.isEmpty() && HGn[2] == null && (indexes == 13 || indexes == 23)) {
1727       resAtoms.get(0).setName("HG3");
1728       resAtoms.remove(0);
1729     }
1730   }
1731 
1732   /**
1733    * renameGlutamineHydrogen.
1734    *
1735    * @param residue a {@link ffx.potential.bonded.Residue} object.
1736    * @param resAtoms a {@link java.util.List} object.
1737    */
1738   public static void renameGlutamineHydrogen(Residue residue, List<Atom> resAtoms) {
1739     Atom HE21 = (Atom) residue.getAtomNode("HE21");
1740     Atom HE22 = (Atom) residue.getAtomNode("HE22");
1741     if (HE21 != null) {
1742       resAtoms.remove(HE21);
1743     }
1744     if (HE22 != null) {
1745       resAtoms.remove(HE22);
1746     }
1747     if (!resAtoms.isEmpty() && HE21 == null) {
1748       resAtoms.get(0).setName("HE21");
1749       resAtoms.remove(0);
1750     }
1751     if (!resAtoms.isEmpty() && HE22 == null) {
1752       resAtoms.get(0).setName("HE21");
1753     }
1754   }
1755 
1756   /**
1757    * renameGlycineAlphaHydrogen.
1758    *
1759    * @param residue a {@link ffx.potential.bonded.Residue} object.
1760    * @param resAtoms a {@link java.util.List} object.
1761    */
1762   public static void renameGlycineAlphaHydrogen(Residue residue, List<Atom> resAtoms) {
1763     Atom HA2 = (Atom) residue.getAtomNode("HA2");
1764     Atom HA3 = (Atom) residue.getAtomNode("HA3");
1765     if (HA2 != null) {
1766       resAtoms.remove(HA2);
1767     }
1768     if (HA3 != null) {
1769       resAtoms.remove(HA3);
1770     }
1771     if (HA2 == null && !resAtoms.isEmpty()) {
1772       resAtoms.get(0).setName("HA2");
1773       resAtoms.remove(0);
1774     }
1775     if (HA3 == null && !resAtoms.isEmpty()) {
1776       resAtoms.get(0).setName("HA3");
1777     }
1778   }
1779 
1780   /**
1781    * renameIsoleucineHydrogen.
1782    *
1783    * @param residue a {@link ffx.potential.bonded.Residue} object.
1784    * @param resAtoms a {@link java.util.List} object.
1785    */
1786   public static void renameIsoleucineHydrogen(Residue residue, List<Atom> resAtoms) {
1787     Atom HG12 = (Atom) residue.getAtomNode("HG12");
1788     Atom HG13 = (Atom) residue.getAtomNode("HG13");
1789     if (HG12 != null) {
1790       resAtoms.remove(HG12);
1791     }
1792     if (HG13 != null) {
1793       resAtoms.remove(HG13);
1794     }
1795     if (HG12 == null && !resAtoms.isEmpty()) {
1796       resAtoms.get(0).setName("HG12");
1797       resAtoms.remove(0);
1798     }
1799     if (HG13 == null && !resAtoms.isEmpty()) {
1800       resAtoms.get(0).setName("HG13");
1801     }
1802   }
1803 
1804   /**
1805    * renameNTerminusHydrogen.
1806    *
1807    * @param residue a {@link ffx.potential.bonded.Residue} object.
1808    */
1809   public static void renameNTerminusHydrogen(Residue residue) {
1810     Atom[] h = new Atom[3];
1811     h[0] = (Atom) residue.getAtomNode("H1");
1812     h[1] = (Atom) residue.getAtomNode("H2");
1813     h[2] = (Atom) residue.getAtomNode("H3");
1814     int numAtoms = 0;
1815     for (Atom atom : h) {
1816       numAtoms += (atom == null ? 0 : 1);
1817     }
1818     if (numAtoms == 3) {
1819       return;
1820     }
1821     List<Atom> resAtoms = residue.getAtomList();
1822     for (Atom resAtom : resAtoms) {
1823       // Check if already contained in h[].
1824       boolean doContinue = false;
1825       for (Atom hAtom : h) {
1826         if (resAtom.equals(hAtom)) {
1827           doContinue = true;
1828           break;
1829         }
1830       }
1831       if (doContinue) {
1832         continue;
1833       }
1834 
1835       // If the hydrogen matches H or H[1-3], assign to first null h entity.
1836       String atomName = resAtom.getName().toUpperCase();
1837       if (atomName.equals("H") || atomName.matches("H[1-3]") || atomName.matches("[1-3]H")) {
1838         ++numAtoms;
1839         for (int i = 0; i < h.length; i++) {
1840           if (h[i] == null) {
1841             resAtom.setName("H" + (i + 1));
1842             h[i] = resAtom;
1843             break;
1844           }
1845         }
1846         if (numAtoms == 3) {
1847           return;
1848         }
1849       }
1850     }
1851   }
1852 
1853   /**
1854    * Renames the Atoms in a nucleic acid to PDB standard.
1855    *
1856    * @param residue Residue to fix atom names of.
1857    */
1858   public static void renameNucleicAcidToPDBStandard(Residue residue) {
1859     if (residue.getChainID() == null) {
1860       logger.info(" Setting Chain ID to Z for " + residue);
1861       residue.setChainID('Z');
1862     }
1863     assert residue.getResidueType() == Residue.ResidueType.NA;
1864     NucleicAcid3 na3 = residue.getNucleicAcid3(true);
1865     residue.setName(na3.toString());
1866     switch (na3) {
1867       case ADE, DAD, CYT, DCY, GUA, DGU, THY, DTY, URI -> renameCommonNucleicAcid(residue, na3);
1868       default -> logger.info(" Could not rename atoms for nonstandard nucleic acid " + na3);
1869     }
1870   }
1871 
1872   /**
1873    * renameZetaHydrogen.
1874    *
1875    * @param residue a {@link ffx.potential.bonded.Residue} object.
1876    * @param resAtoms a {@link java.util.List} object.
1877    * @param indexes The HZ atom indices to use.
1878    */
1879   public static void renameZetaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1880     Atom[] HZn = new Atom[3];
1881     switch (indexes) {
1882       case 12 -> {
1883         HZn[0] = (Atom) residue.getAtomNode("HZ1");
1884         HZn[1] = (Atom) residue.getAtomNode("HZ2");
1885       }
1886       case 13 -> {
1887         HZn[0] = (Atom) residue.getAtomNode("HZ1");
1888         HZn[2] = (Atom) residue.getAtomNode("HZ3");
1889       }
1890       case 23 -> {
1891         HZn[1] = (Atom) residue.getAtomNode("HZ2");
1892         HZn[2] = (Atom) residue.getAtomNode("HZ3");
1893       }
1894       default -> {
1895         return;
1896       }
1897     }
1898     for (Atom HZatom : HZn) {
1899       resAtoms.remove(HZatom);
1900     }
1901     if (!resAtoms.isEmpty() && HZn[0] == null && (indexes == 12 || indexes == 13)) {
1902       resAtoms.get(0).setName("HZ1");
1903       resAtoms.remove(0);
1904     }
1905     if (!resAtoms.isEmpty() && HZn[1] == null && (indexes == 12 || indexes == 23)) {
1906       resAtoms.get(0).setName("HZ2");
1907       resAtoms.remove(0);
1908     }
1909     if (!resAtoms.isEmpty() && HZn[2] == null && (indexes == 13 || indexes == 23)) {
1910       resAtoms.get(0).setName("HZ3");
1911       resAtoms.remove(0);
1912     }
1913   }
1914 
1915   /** Common HETATOM labels for water and ions. */
1916   public enum HetAtoms {
1917     BR,
1918     CA,
1919     CA2,
1920     CL,
1921     K,
1922     MG,
1923     MG2,
1924     NA,
1925     HOH,
1926     H2O,
1927     WAT,
1928     ZN,
1929     ZN2;
1930 
1931     /**
1932      * Slightly more robust parsing function that ignores case and trailing numbers, -, and +
1933      *
1934      * @param str String to parse
1935      * @return Corresponding HetAtoms value.
1936      */
1937     public static HetAtoms parse(String str) {
1938       String hName = str.toUpperCase().replaceFirst("[0-9+\\-]+$", "");
1939       return valueOf(hName);
1940     }
1941   }
1942 }