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 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       logger.fine(format(" Mapping of nitrogen to residue (%s) failed. Trying to remap to molecule.", residue));
723       return false;
724     }
725     return true;
726   }
727 
728   /**
729    * renameArginineHydrogen.
730    *
731    * @param residue a {@link ffx.potential.bonded.Residue} object.
732    * @param resAtoms a {@link java.util.List} object.
733    */
734   public static void renameArginineHydrogen(Residue residue, List<Atom> resAtoms) {
735     Atom HH11 = (Atom) residue.getAtomNode("HH11");
736     Atom HH12 = (Atom) residue.getAtomNode("HH12");
737     Atom HH21 = (Atom) residue.getAtomNode("HH21");
738     Atom HH22 = (Atom) residue.getAtomNode("HH22");
739     if (HH11 != null) {
740       resAtoms.remove(HH11);
741     }
742     if (HH12 != null) {
743       resAtoms.remove(HH12);
744     }
745     if (HH21 != null) {
746       resAtoms.remove(HH21);
747     }
748     if (HH22 != null) {
749       resAtoms.remove(HH22);
750     }
751     if (!resAtoms.isEmpty() && HH11 == null) {
752       resAtoms.get(0).setName("HH11");
753       resAtoms.remove(0);
754     }
755     if (!resAtoms.isEmpty() && HH12 == null) {
756       resAtoms.get(0).setName("HH12");
757       resAtoms.remove(0);
758     }
759     if (!resAtoms.isEmpty() && HH21 == null) {
760       resAtoms.get(0).setName("HH21");
761       resAtoms.remove(0);
762     }
763     if (!resAtoms.isEmpty() && HH22 == null) {
764       resAtoms.get(0).setName("HH22");
765       resAtoms.remove(0);
766     }
767   }
768 
769   /**
770    * renameAsparagineHydrogen.
771    *
772    * @param residue a {@link ffx.potential.bonded.Residue} object.
773    * @param resAtoms a {@link java.util.List} object.
774    */
775   public static void renameAsparagineHydrogen(Residue residue, List<Atom> resAtoms) {
776     Atom HD21 = (Atom) residue.getAtomNode("HD21");
777     Atom HD22 = (Atom) residue.getAtomNode("HD22");
778     if (HD21 != null) {
779       resAtoms.remove(HD21);
780     }
781     if (HD22 != null) {
782       resAtoms.remove(HD22);
783     }
784     if (!resAtoms.isEmpty() && HD21 == null) {
785       resAtoms.get(0).setName("HD21");
786       resAtoms.remove(0);
787     }
788     if (!resAtoms.isEmpty() && HD22 == null) {
789       resAtoms.get(0).setName("HD21");
790     }
791   }
792 
793   /**
794    * Renames Atoms to PDB standard using bonding patterns, atomic numbers, and residue types.
795    *
796    * <p>Will not work if a force field definition botches its atomic numbers.
797    *
798    * <p>Only implemented for amino acids and nucleic acids at this time.
799    *
800    * @param molecularAssembly MolecularAssembly to fix.
801    */
802   public static void renameAtomsToPDBStandard(MolecularAssembly molecularAssembly) {
803     Polymer[] polymers = molecularAssembly.getChains();
804     if (polymers != null) {
805       for (Polymer polymer : polymers) {
806         for (Residue residue : polymer.getResidues()) {
807           switch (residue.getResidueType()) {
808             case AA -> renameAminoAcidToPDBStandard(residue);
809             case NA -> renameNucleicAcidToPDBStandard(residue);
810             case UNK -> {
811               // Do nothing.
812             }
813           }
814         }
815       }
816     }
817   }
818 
819   /**
820    * renameBetaHydrogen.
821    *
822    * @param residue a {@link ffx.potential.bonded.Residue} object.
823    * @param resAtoms a {@link java.util.List} object.
824    * @param indexes The HB indexes to use.
825    */
826   public static void renameBetaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
827     Atom[] HBn = new Atom[3];
828     switch (indexes) {
829       case 12 -> {
830         HBn[0] = (Atom) residue.getAtomNode("HB1");
831         HBn[1] = (Atom) residue.getAtomNode("HB2");
832       }
833       case 13 -> {
834         HBn[0] = (Atom) residue.getAtomNode("HB1");
835         HBn[2] = (Atom) residue.getAtomNode("HB3");
836       }
837       case 23 -> {
838         HBn[1] = (Atom) residue.getAtomNode("HB2");
839         HBn[2] = (Atom) residue.getAtomNode("HB3");
840       }
841       default -> {
842         return;
843       }
844     }
845     for (Atom HBatom : HBn) {
846       resAtoms.remove(HBatom);
847     }
848     if (!resAtoms.isEmpty() && HBn[0] == null && (indexes == 12 || indexes == 13)) {
849       resAtoms.get(0).setName("HB1");
850       resAtoms.remove(0);
851     }
852     if (!resAtoms.isEmpty() && HBn[1] == null && (indexes == 12 || indexes == 23)) {
853       resAtoms.get(0).setName("HB2");
854       resAtoms.remove(0);
855     }
856     if (!resAtoms.isEmpty() && HBn[2] == null && (indexes == 13 || indexes == 23)) {
857       resAtoms.get(0).setName("HB3");
858       resAtoms.remove(0);
859     }
860   }
861 
862   /**
863    * Renames a numbered carbon, its bonded hydrogen, and returns the next atom in the chain.
864    *
865    * <p>If applied to an atom that is not a carbon, it will be misnamed as a carbon, so fix that
866    * afterwards.
867    *
868    * <p>This is for carbons like PHE CD1 and CD2.
869    *
870    * @param carbon Alkyl carbon to rename.
871    * @param priorAtom Prior atom in the chain.
872    * @param protonOffset Number of the first hydrogen (such as 2 for HB2-3).
873    * @param branchNum Index of the branch.
874    * @param posName Name of the position (such as B for CB).
875    * @return Next atom in the chain if present.
876    */
877   public static Optional<Atom> renameBranchedAlkyl(Atom carbon, Atom priorAtom, int protonOffset,
878       int branchNum, char posName) {
879     carbon.setName(format("C%c%d", posName, branchNum));
880     List<Atom> hydrogen = findBondedAtoms(carbon, 1);
881     int numH = hydrogen.size();
882     if (numH == 1) {
883       hydrogen.get(0).setName(format("H%c%d", posName, branchNum));
884     } else {
885       for (int i = 0; i < numH; i++) {
886         hydrogen.get(i).setName(format("H%c%d%d", posName, branchNum, i + protonOffset));
887       }
888     }
889 
890     return carbon.getBonds().stream()
891         .map((Bond b) -> b.get1_2(carbon))
892         .filter((Atom a) -> a != priorAtom)
893         .filter((Atom a) -> !hydrogen.contains(a))
894         .findAny();
895   }
896 
897   /**
898    * Renames atoms in common amino acids to PDB standard.
899    *
900    * @param residue Residue to perform renaming for.
901    * @param aa3 Its AA3 code.
902    * @param CA Its alpha carbon.
903    * @param CB Its beta carbon.
904    */
905   public static void renameCommonAminoAcids(Residue residue, AminoAcid3 aa3, Atom CA, Atom CB) {
906     switch (aa3) {
907       case ALA: {
908         renameAlkyl(CB, CA, 1, 'B');
909       }
910       break;
911       case CYS:
912       case CYD: {
913         Atom SG = renameAlkyl(CB, CA, 2, 'B').get();
914         SG.setName("SG");
915         if (hasAttachedAtom(SG, 1)) {
916           assert aa3 == AminoAcidUtils.AminoAcid3.CYS;
917           findBondedAtoms(SG, 1).get(0).setName("HG");
918         } else if (hasAttachedAtom(SG, 16)) {
919           logger.finer(format(" SG atom %s likely part of a disulfide bond.", SG));
920         } else {
921           residue.setName("CYD");
922         }
923       }
924       break;
925       case ASP:
926       case ASH:
927       case ASD: {
928         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
929         CG.setName("CG");
930         List<Atom> ODs = findBondedAtoms(CG, 8);
931 
932         int protonatedOD = -1; // -1: Deprotonated ASP. 0/1: Index of protonated oxygen (ASH).
933         for (int i = 0; i < 2; i++) {
934           if (hasAttachedAtom(ODs.get(i), 1)) {
935             protonatedOD = i;
936             break;
937           }
938         }
939 
940         // Check for double protonation for constant pH.
941         if (hasAttachedAtom(ODs.get(0), 1) && hasAttachedAtom(ODs.get(1), 1)) {
942           protonatedOD = 2;
943         }
944 
945         switch (protonatedOD) {
946           case -1 -> {
947             ODs.get(0).setName("OD1");
948             ODs.get(1).setName("OD2");
949           }
950           case 0 -> {
951             if (aa3 != AminoAcid3.ASH) {
952               residue.setName("ASH");
953             }
954             ODs.get(0).setName("OD2");
955             findBondedAtoms(ODs.get(0), 1).get(0).setName("HD2");
956             ODs.get(1).setName("OD1");
957           }
958           case 1 -> {
959             if (aa3 != AminoAcid3.ASH) {
960               residue.setName("ASH");
961             }
962             ODs.get(1).setName("OD2");
963             findBondedAtoms(ODs.get(1), 1).get(0).setName("HD2");
964             ODs.get(0).setName("OD1");
965           }
966           case 2 -> {
967             if (aa3 != AminoAcid3.ASD) {
968               residue.setName("ASD");
969             }
970             ODs.get(0).setName("OD1");
971             findBondedAtoms(ODs.get(0), 1).get(0).setName("HD1");
972             ODs.get(1).setName("OD2");
973             findBondedAtoms(ODs.get(1), 1).get(0).setName("HD2");
974           }
975         }
976       }
977       break;
978       case GLU:
979       case GLH:
980       case GLD: {
981         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
982         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
983         CD.setName("CD");
984         List<Atom> OEs = findBondedAtoms(CD, 8);
985 
986         int protonatedOE = -1; // If it remains -1, deprotonated ASP, else ASH.
987         for (int i = 0; i < 2; i++) {
988           if (hasAttachedAtom(OEs.get(i), 1)) {
989             protonatedOE = i;
990             break;
991           }
992         }
993 
994         // Check for double protonation for constant pH.
995         if (hasAttachedAtom(OEs.get(0), 1) && hasAttachedAtom(OEs.get(1), 1)) {
996           protonatedOE = 2;
997         }
998 
999         switch (protonatedOE) {
1000           case -1 -> {
1001             OEs.get(0).setName("OE1");
1002             OEs.get(1).setName("OE2");
1003           }
1004           case 0 -> {
1005             if (aa3 != AminoAcid3.GLH) {
1006               residue.setName("GLH");
1007             }
1008             OEs.get(0).setName("OE2");
1009             findBondedAtoms(OEs.get(0), 1).get(0).setName("HE2");
1010             OEs.get(1).setName("OE1");
1011           }
1012           case 1 -> {
1013             if (aa3 != AminoAcid3.GLH) {
1014               residue.setName("GLH");
1015             }
1016             OEs.get(1).setName("OE2");
1017             findBondedAtoms(OEs.get(1), 1).get(0).setName("HE2");
1018             OEs.get(0).setName("OE1");
1019           }
1020           case 2 -> {
1021             if (aa3 != AminoAcid3.GLD) {
1022               residue.setName("GLD");
1023             }
1024             OEs.get(0).setName("OE1");
1025             findBondedAtoms(OEs.get(0), 1).get(0).setName("HE1");
1026             OEs.get(1).setName("OE2");
1027             findBondedAtoms(OEs.get(1), 1).get(0).setName("HE2");
1028           }
1029         }
1030       }
1031       break;
1032       case PHE: {
1033         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1034         CG.setName("CG");
1035         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1036 
1037         Atom CZ = null;
1038         for (int i = 1; i <= 2; i++) {
1039           Atom CD = CDs.get(i - 1);
1040           Atom CE = renameBranchedAlkyl(CD, CG, 0, i, 'D').get();
1041           CZ = renameBranchedAlkyl(CE, CD, 0, i, 'E').get();
1042         }
1043         CZ.setName("CZ");
1044         findBondedAtoms(CZ, 1).get(0).setName("HZ");
1045       }
1046       break;
1047       case GLY:
1048         break;
1049       case HIS:
1050       case HIE:
1051       case HID: {
1052         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1053         CG.setName("CG");
1054 
1055         Atom CD2 = findBondedAtoms(CG, 6).stream().filter((Atom a) -> a != CB).findAny().get();
1056         CD2.setName("CD2");
1057         findBondedAtoms(CD2, 1).get(0).setName("HD2");
1058 
1059         Atom NE2 = findBondedAtoms(CD2, 7).get(0);
1060         NE2.setName("NE2");
1061         List<Atom> HE2 = findBondedAtoms(NE2, 1);
1062         boolean epsProtonated = (HE2 != null && !HE2.isEmpty());
1063         if (epsProtonated) {
1064           HE2.get(0).setName("HE2");
1065         }
1066 
1067         Atom CE1 = findBondedAtoms(NE2, CD2, 6).get(0);
1068         CE1.setName("CE1");
1069         findBondedAtoms(CE1, 1).get(0).setName("HE1");
1070 
1071         Atom ND1 = findBondedAtoms(CG, 7).get(0);
1072         ND1.setName("ND1");
1073         List<Atom> HD1 = findBondedAtoms(ND1, 1);
1074         boolean deltaProtonated = (HD1 != null && !HD1.isEmpty());
1075         if (deltaProtonated) {
1076           HD1.get(0).setName("HD1");
1077         }
1078 
1079         // All constant atoms found: now check protonation state.
1080         if (epsProtonated && deltaProtonated) {
1081           assert aa3 == AminoAcidUtils.AminoAcid3.HIS;
1082         } else if (epsProtonated) {
1083           residue.setName("HIE");
1084         } else if (deltaProtonated) {
1085           residue.setName("HID");
1086         } else {
1087           throw new IllegalArgumentException(
1088               format(" Histidine residue %s is doubly deprotonated!", residue));
1089         }
1090       }
1091       break;
1092       case ILE: {
1093         findBondedAtoms(CB, 1).get(0).setName("HB");
1094         List<Atom> CGs = findBondedAtoms(CB, CA, 6);
1095 
1096         for (Atom CG : CGs) {
1097           List<Atom> HGs = findBondedAtoms(CG, 1);
1098           int numHGs = HGs.size();
1099           if (numHGs == 3) {
1100             renameBranchedAlkyl(CG, CB, 1, 2, 'G');
1101           } else if (numHGs == 2) {
1102             Atom CD1 = renameBranchedAlkyl(CG, CB, 2, 1, 'G').get();
1103             renameBranchedAlkyl(CD1, CG, 1, 1, 'D');
1104           } else {
1105             throw new IllegalArgumentException(
1106                 format(
1107                     " Isoleucine residue %s had %d gamma hydrogen, expecting 2-3!",
1108                     residue, numHGs));
1109           }
1110         }
1111       }
1112       break;
1113       case LYS:
1114       case LYD: {
1115         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1116         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1117         Atom CE = renameAlkyl(CD, CG, 2, 'D').get();
1118         Atom NZ = renameAlkyl(CE, CD, 2, 'E').get();
1119         // For a very brief period, NZ will be named CZ.
1120         renameAlkyl(NZ, CE, 1, 'Z');
1121         NZ.setName("NZ");
1122         int numH = findBondedAtoms(NZ, 1).size();
1123         switch (numH) {
1124           case 2 -> residue.setName("LYD");
1125           case 3 -> {
1126             assert aa3 == AminoAcid3.LYS;
1127           }
1128           default -> throw new IllegalArgumentException(
1129               format(" Lysine residue %s had %d amine protons, expecting 2-3!", residue, numH));
1130         }
1131       }
1132       break;
1133       case LEU: {
1134         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1135         CG.setName("CG");
1136         findBondedAtoms(CG, 1).get(0).setName("HG");
1137         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1138 
1139         for (int i = 0; i < 2; i++) {
1140           renameBranchedAlkyl(CDs.get(i), CG, 1, (i + 1), 'D');
1141         }
1142       }
1143       break;
1144       case MET: {
1145         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1146         Atom SD = renameAlkyl(CG, CB, 2, 'G').get();
1147         Atom CE = renameAlkyl(SD, CG, 0, 'D').get();
1148         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1149         SD.setName("SD");
1150         renameAlkyl(CE, SD, 1, 'E');
1151       }
1152       break;
1153       case ASN: {
1154         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1155         CG.setName("CG");
1156         findBondedAtoms(CG, 8).get(0).setName("OD1");
1157         Atom ND2 = findBondedAtoms(CG, 7).get(0);
1158         renameBranchedAlkyl(ND2, CG, 1, 2, 'D');
1159         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1160         ND2.setName("ND2");
1161       }
1162       break;
1163       case PRO: {
1164         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1165         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1166         Atom N = renameAlkyl(CD, CG, 2, 'D').get();
1167         assert N.getName().equals("N");
1168       }
1169       break;
1170       case GLN: {
1171         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1172         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1173         CD.setName("CD");
1174 
1175         findBondedAtoms(CD, 8).get(0).setName("OE1");
1176         Atom NE2 = findBondedAtoms(CD, 7).get(0);
1177         renameBranchedAlkyl(NE2, CD, 1, 2, 'E');
1178         // Once again, briefly misnamed atom because I'm kludging it through renameAlkyl.
1179         NE2.setName("NE2");
1180       }
1181       break;
1182       case ARG: {
1183         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1184         Atom CD = renameAlkyl(CG, CB, 2, 'G').get();
1185         Atom NE = renameAlkyl(CD, CG, 2, 'D').get();
1186         Atom CZ = renameAlkyl(NE, CD, 0, 'E').get();
1187         NE.setName("NE");
1188         CZ.setName("CZ");
1189 
1190         List<Atom> NHs = findBondedAtoms(CZ, NE, 7);
1191         assert NHs.size() == 2;
1192         for (int i = 0; i < 2; i++) {
1193           Atom NHx = NHs.get(i);
1194           renameBranchedAlkyl(NHx, CZ, 1, (i + 1), 'H');
1195           NHx.setName(format("NH%d", (i + 1)));
1196         }
1197       }
1198       break;
1199       case SER: {
1200         Atom OG = renameAlkyl(CB, CA, 2, 'B').get();
1201         renameAlkyl(OG, CB, 0, 'G');
1202         OG.setName("OG");
1203       }
1204       break;
1205       case THR: {
1206         CB.setName("CB"); // Should be unnecessary.
1207         findBondedAtoms(CB, 1).get(0).setName("HB");
1208 
1209         Atom OG1 = findBondedAtoms(CB, 8).get(0);
1210         OG1.setName("OG1");
1211         findBondedAtoms(OG1, 1).get(0).setName("HG1");
1212 
1213         Atom CG2 = findBondedAtoms(CB, CA, 6).get(0);
1214         renameBranchedAlkyl(CG2, CB, 1, 2, 'G');
1215       }
1216       break;
1217       case VAL: {
1218         CB.setName("CB"); // Should be unnecessary.
1219         findBondedAtoms(CB, 1).get(0).setName("HB");
1220 
1221         List<Atom> CGs = findBondedAtoms(CB, CA, 6);
1222 
1223         assert CGs.size() == 2;
1224         for (int i = 0; i < 2; i++) {
1225           Atom CGx = CGs.get(i);
1226           renameBranchedAlkyl(CGx, CB, 1, (i + 1), 'G');
1227         }
1228       }
1229       break;
1230       case TRP: {
1231         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1232         CG.setName("CG");
1233         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1234         Atom CD1 = null;
1235         Atom CD2 = null;
1236 
1237         for (Atom CDx : CDs) {
1238           if (hasAttachedAtom(CDx, 1)) {
1239             CD1 = CDx;
1240           } else {
1241             CD2 = CDx;
1242             CD2.setName("CD2");
1243           }
1244         }
1245         Atom NE1 = renameBranchedAlkyl(CD1, CG, 0, 1, 'D').get();
1246         Atom CE2 = renameBranchedAlkyl(NE1, CD1, 0, 1, 'E').get();
1247         NE1.setName("NE1");
1248         CE2.setName("CE2");
1249 
1250         Atom CZ2 = findBondedAtoms(CE2, CD2, 6).get(0);
1251         Atom CH2 = renameBranchedAlkyl(CZ2, CE2, 0, 2, 'Z').get();
1252         Atom CZ3 = renameBranchedAlkyl(CH2, CZ2, 0, 2, 'H').get();
1253         Atom CE3 = renameBranchedAlkyl(CZ3, CH2, 0, 3, 'Z').get();
1254         if (CD2 != renameBranchedAlkyl(CE3, CZ3, 0, 3, 'E').get()) {
1255           throw new IllegalArgumentException(
1256               format(" Error in cyclizing tryptophan %s!", residue));
1257         }
1258       }
1259       break;
1260       case TYR:
1261       case TYD: {
1262         Atom CG = renameAlkyl(CB, CA, 2, 'B').get();
1263         CG.setName("CG");
1264         List<Atom> CDs = findBondedAtoms(CG, CB, 6);
1265         Atom CZ = null;
1266 
1267         assert CDs.size() == 2;
1268         for (int i = 1; i <= 2; i++) {
1269           Atom CDx = CDs.get(i - 1);
1270           Atom CEx = renameBranchedAlkyl(CDx, CG, 0, i, 'D').get();
1271           CZ = renameBranchedAlkyl(CEx, CDx, 0, i, 'E').get();
1272         }
1273 
1274         CZ.setName("CZ");
1275         Atom OH = findBondedAtoms(CZ, 8).get(0);
1276         OH.setName("OH");
1277         if (hasAttachedAtom(OH, 1)) {
1278           assert aa3 == AminoAcidUtils.AminoAcid3.TYR;
1279           findBondedAtoms(OH, 1).get(0).setName("HH");
1280         } else {
1281           residue.setName("TYD");
1282         }
1283       }
1284       break;
1285       default:
1286         throw new IllegalArgumentException(
1287             (format(" Amino acid %s (%s) not recognized!", residue, aa3)));
1288     }
1289   }
1290 
1291   /**
1292    * Renames atoms in common nucleic acids to PDB standard.
1293    *
1294    * @param residue Residue to perform renaming for.
1295    * @param na3 Its NA3 code.
1296    */
1297   public static void renameCommonNucleicAcid(Residue residue, NucleicAcid3 na3) {
1298     Optional<Atom> optO4s = findNucleotideO4s(residue);
1299     if (optO4s.isPresent()) {
1300       // Name O4', which is the unique ether oxygen.
1301       Atom O4s = optO4s.get();
1302       O4s.setName("O4'");
1303 
1304       // C1' is bonded to a nitrogen (at least for non-abasic sites), C4' isn't
1305       List<Atom> bondedC = findBondedAtoms(O4s, 6);
1306       Atom C4s = null;
1307       Atom C1s = null;
1308       // Will need the first base nitrogen (N1/N9), and H1' later.
1309       Atom N19 = null;
1310       Atom H1s = null;
1311       for (Atom c : bondedC) {
1312         if (hasAttachedAtom(c, 7)) {
1313           C1s = c;
1314           C1s.setName("C1'");
1315           H1s = findBondedAtoms(C1s, 1).get(0);
1316           H1s.setName("H1'");
1317           N19 = findBondedAtoms(C1s, 7).get(0);
1318         } else {
1319           C4s = c;
1320           C4s.setName("C4'");
1321           findBondedAtoms(C4s, 1).get(0).setName("H4'");
1322         }
1323       }
1324       assert C4s != null && C1s != null;
1325 
1326       Atom C2s = findBondedAtoms(C1s, 6).get(0);
1327       C2s.setName("C2'");
1328 
1329       bondedC = findBondedAtoms(C4s, 6);
1330       Atom C5s = null;
1331       Atom C3s;
1332       Atom O3s;
1333       for (Atom c : bondedC) {
1334         if (c.getBonds().stream().anyMatch(b -> b.get1_2(c) == C2s)) {
1335           C3s = c;
1336           C3s.setName("C3'");
1337           O3s = findBondedAtoms(C3s, 8).get(0);
1338           O3s.setName("O3'");
1339           findBondedAtoms(C3s, 1).get(0).setName("H3'");
1340           if (hasAttachedAtom(O3s, 1)) {
1341             findBondedAtoms(O3s, 1).get(0).setName("HO3'");
1342           } // Else, handle the possibility of 3'-P cap later.
1343         } else {
1344           C5s = c;
1345           C5s.setName("C5'");
1346           List<Atom> allH5List = findBondedAtoms(C5s, 1);
1347           Atom[] allH5s = allH5List.toArray(new Atom[0]);
1348           sortAtomsByDistance(O4s, allH5s);
1349           allH5s[0].setName("H5'");
1350           allH5s[1].setName("H5''");
1351         }
1352       }
1353 
1354       if (hasAttachedAtom(C2s, 8)) {
1355         Atom O2s = findBondedAtoms(C2s, 8).get(0);
1356         O2s.setName("O2'");
1357         findBondedAtoms(O2s, 1).get(0).setName("HO2'");
1358         findBondedAtoms(C2s, 1).get(0).setName("H2'");
1359       } else {
1360         List<Atom> bothH2List = findBondedAtoms(C2s, 1);
1361         Atom[] bothH2 = bothH2List.toArray(new Atom[0]);
1362         sortAtomsByDistance(H1s, bothH2);
1363         // Best-guess assignment, but is sometimes the other way around.
1364         bothH2[0].setName("H2''");
1365         bothH2[1].setName("H2'");
1366       }
1367 
1368       // logger.info(format(" C5\' null: %b", C5s == null));
1369       Atom O5s = findBondedAtoms(C5s, 8).get(0);
1370       O5s.setName("O5'");
1371 
1372       if (hasAttachedAtom(O5s, 1)) {
1373         findBondedAtoms(O5s, 1).get(0).setName("HO5'");
1374       } else if (hasAttachedAtom(O5s, 15)) {
1375         Atom P = findBondedAtoms(O5s, 15).get(0);
1376         P.setName("P");
1377         List<Atom> bondedO = findBondedAtoms(P, O5s, 8);
1378         List<Atom> thisResO = bondedO.stream().filter(o -> residue.getAtomList().contains(o))
1379             .toList();
1380         int nBonded = bondedO.size();
1381         int nRes = thisResO.size();
1382         if (nBonded == 0) {
1383           // Do nothing.
1384         } else if (nBonded == nRes) {
1385           Atom OP1 = bondedO.get(0);
1386           OP1.setName("OP1");
1387           // OP2 is approximately +120 degrees from OP1, OP3 is -120 degrees.
1388           final double[] xyzC5s = C5s.getXYZ(new double[3]);
1389           final double[] xyzO5s = O5s.getXYZ(new double[3]);
1390           final double[] xyzP = P.getXYZ(new double[3]);
1391           final double[] xyzOP1 = OP1.getXYZ(new double[3]);
1392           double dihedral = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzOP1);
1393           double twoPiOver3 = 2.0 * PI / 3.0;
1394           double target = modToRange(dihedral + twoPiOver3, -PI, PI);
1395           List<Atom> otherO = bondedO.stream().filter(o -> o != OP1).sorted(
1396               Comparator.comparingDouble((Atom o) -> {
1397                 double[] xyzO = o.getXYZ(new double[3]);
1398                 double dihedO = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzO);
1399                 double diff = dihedO - target;
1400                 double twoPi = 2 * PI;
1401                 diff = modToRange(diff, 0, twoPi);
1402                 diff = diff < PI ? diff : twoPi - diff;
1403                 return diff;
1404               })).toList();
1405           for (int i = 0; i < otherO.size(); i++) {
1406             otherO.get(i).setName(format("OP%d", i + 2));
1407           }
1408         } else {
1409           Atom nextO3s =
1410               bondedO.stream().filter(o -> !residue.getAtomList().contains(o)).findAny().get();
1411 
1412           // OP1 is approximately +120 degrees from next O3', OP2 is -120 degrees.
1413           final double[] xyzC5s = C5s.getXYZ(new double[3]);
1414           final double[] xyzO5s = O5s.getXYZ(new double[3]);
1415           final double[] xyzP = P.getXYZ(new double[3]);
1416           final double[] xyzNextO3s = nextO3s.getXYZ(new double[3]);
1417           double dihedral = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzNextO3s);
1418           double twoPiOver3 = 2.0 * PI / 3.0;
1419           double target = modToRange(dihedral + twoPiOver3, -PI, PI);
1420           List<Atom> otherO = bondedO.stream().filter(o -> o != nextO3s).sorted(
1421               Comparator.comparingDouble((Atom o) -> {
1422                 double[] xyzO = o.getXYZ(new double[3]);
1423                 double dihedO = dihedralAngle(xyzC5s, xyzO5s, xyzP, xyzO);
1424                 double diff = dihedO - target;
1425                 double twoPi = 2 * PI;
1426                 diff = modToRange(diff, 0, twoPi);
1427                 diff = diff < PI ? diff : twoPi - diff;
1428                 return diff;
1429               })).toList();
1430           for (int i = 0; i < otherO.size(); i++) {
1431             otherO.get(i).setName(format("OP%d", i + 1));
1432           }
1433         }
1434 
1435         for (Atom op : bondedO) {
1436           if (hasAttachedAtom(op, 1)) {
1437             findBondedAtoms(op, 1).get(0).setName("H" + op.getName());
1438           }
1439         }
1440       }
1441       renameCommonNucleobase(N19, C1s, na3);
1442     } else {
1443       logger.warning(" Could not find O4' for residue " + residue);
1444     }
1445   }
1446 
1447   /**
1448    * Renames the atoms of the common nucleobases (A, C, G, T, U, and deoxy variants).
1449    *
1450    * @param N19 N1 of pyrimidines, N9 of purines.
1451    * @param C1s C1' of the ribose sugar.
1452    * @param na3 Identity of the nucleic acid.
1453    */
1454   public static void renameCommonNucleobase(Atom N19, Atom C1s, NucleicAcid3 na3) {
1455     switch (na3) {
1456       case ADE, DAD -> {
1457         Map<String, Atom> purineBase = renameCommonPurine(N19, C1s);
1458         // Unique to A: H2, N6, H6[12]
1459         findBondedAtoms(purineBase.get("C2"), 1).get(0).setName("H2");
1460         Atom C6 = purineBase.get("C6");
1461         Atom N1 = purineBase.get("N1");
1462         Atom N6 = findBondedAtoms(C6, N1, 7).get(0);
1463         N6.setName("N6");
1464         List<Atom> allH6List = findBondedAtoms(N6, 1);
1465         Atom[] allH6 = sortAtomsByDistance(N1, allH6List);
1466         allH6[0].setName("H61");
1467         allH6[1].setName("H62");
1468       }
1469       case CYT, DCY -> {
1470         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1471         // Unique to C: N4, H4[12]
1472         Atom C4 = pyrimidineBase.get("C4");
1473         Atom N3 = pyrimidineBase.get("N3");
1474         Atom N4 = findBondedAtoms(C4, N3, 7).get(0);
1475         N4.setName("N4");
1476         Atom[] allH4 = sortAtomsByDistance(N3, findBondedAtoms(N4, 1));
1477         allH4[0].setName("H41");
1478         allH4[1].setName("H42");
1479       }
1480       case GUA, DGU -> {
1481         Map<String, Atom> purineBase = renameCommonPurine(N19, C1s);
1482         // Unique to G: H1, N2, H2[12], O6
1483         Atom N1 = purineBase.get("N1");
1484         Atom C2 = purineBase.get("C2");
1485         Atom C6 = purineBase.get("C6");
1486         findBondedAtoms(N1, 1).get(0).setName("H1");
1487         Atom N2 =
1488             findBondedAtoms(C2, N1, 7).stream()
1489                 .filter(n -> hasAttachedAtom(n, 1))
1490                 .findAny()
1491                 .get();
1492         N2.setName("N2");
1493         Atom[] allH2 = sortAtomsByDistance(N1, findBondedAtoms(N2, 1));
1494         allH2[0].setName("H21");
1495         allH2[1].setName("H22");
1496         findBondedAtoms(C6, 8).get(0).setName("O6");
1497       }
1498       case URI -> {
1499         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1500         // Unique to U: H3, O4
1501         findBondedAtoms(pyrimidineBase.get("N3"), 1).get(0).setName("H3");
1502         findBondedAtoms(pyrimidineBase.get("C4"), 8).get(0).setName("O4");
1503       }
1504       case THY, DTY -> {
1505         Map<String, Atom> pyrimidineBase = renameCommonPyrimidine(N19, C1s);
1506         // Unique to T: H3, O4, C7
1507         findBondedAtoms(pyrimidineBase.get("N3"), 1).get(0).setName("H3");
1508         findBondedAtoms(pyrimidineBase.get("C4"), 8).get(0).setName("O4");
1509         Atom C5 = pyrimidineBase.get("C5");
1510         for (Atom c : findBondedAtoms(C5, 6)) {
1511           List<Atom> bondedH = findBondedAtoms(c, 1);
1512           if (bondedH != null && bondedH.size() == 3) {
1513             c.setName("C7");
1514             for (int i = 0; i < 3; i++) {
1515               bondedH.get(i).setName(format("H7%d", i + 1));
1516             }
1517             break;
1518           }
1519         }
1520       }
1521     }
1522   }
1523 
1524   /**
1525    * Renames atoms common to all standard purines (A, G)
1526    *
1527    * @param N9 The N9 atom.
1528    * @param C1s The C1' atom.
1529    * @return A Map containing Atoms important to finding and naming base-unique atoms.
1530    */
1531   public static Map<String, Atom> renameCommonPurine(Atom N9, Atom C1s) {
1532     Map<String, Atom> keyAtoms = new HashMap<>(10);
1533     N9.setName("N9");
1534     for (Atom c : findBondedAtoms(N9, C1s, 6)) {
1535       if (hasAttachedAtom(c, 1)) {
1536         Atom C8 = c;
1537         C8.setName("C8");
1538         findBondedAtoms(C8, 1).get(0).setName("H8");
1539         Atom N7 = findBondedAtoms(C8, N9, 7).get(0);
1540         N7.setName("N7");
1541         Atom C5 = findBondedAtoms(N7, C8, 6).get(0);
1542         C5.setName("C5");
1543       } else {
1544         Atom C4 = c;
1545         C4.setName("C4");
1546         Atom N3 = findBondedAtoms(C4, N9, 7).get(0);
1547         N3.setName("N3");
1548         Atom C2 = findBondedAtoms(N3, C4, 6).get(0);
1549         C2.setName("C2");
1550         keyAtoms.put("C2", C2);
1551         Atom N1 = findBondedAtoms(C2, N3, 7).get(0);
1552         N1.setName("N1"); // And not, say, "largest non-nuclear explosion ever".
1553         keyAtoms.put("N1", N1);
1554         Atom C6 = findBondedAtoms(N1, C2, 6).get(0);
1555         C6.setName("C6");
1556         keyAtoms.put("C6", C6);
1557       }
1558     }
1559     /* Common atoms: N1, C2, N3, C4, C5, C6, N7, C8, H8, N9. */
1560     return keyAtoms;
1561   }
1562 
1563   /**
1564    * Renames atoms common to all standard pyrimidines (C, T, U)
1565    *
1566    * @param N1 The N1 atom.
1567    * @param C1s The C1' atom.
1568    * @return A Map containing Atoms important to finding and naming base-unique atoms.
1569    */
1570   public static Map<String, Atom> renameCommonPyrimidine(Atom N1, Atom C1s) {
1571     Map<String, Atom> keyAtoms = new HashMap<>();
1572     N1.setName("N1");
1573     for (Atom c : findBondedAtoms(N1, C1s, 6)) {
1574       if (hasAttachedAtom(c, 8)) {
1575         Atom C2 = c;
1576         C2.setName("C2");
1577         findBondedAtoms(C2, 8).get(0).setName("O2");
1578         Atom N3 = findBondedAtoms(C2, N1, 7).get(0);
1579         N3.setName("N3");
1580         keyAtoms.put("N3", N3);
1581         Atom C4 = findBondedAtoms(N3, C2, 6).get(0);
1582         C4.setName("C4");
1583         keyAtoms.put("C4", C4);
1584         Atom C5 = findBondedAtoms(C4, 6).get(0);
1585         C5.setName("C5");
1586         keyAtoms.put("C5", C5);
1587         if (hasAttachedAtom(C5, 1)) {
1588           findBondedAtoms(C5, 1).get(0).setName("H5");
1589         }
1590       } else {
1591         Atom C6 = c;
1592         C6.setName("C6");
1593         findBondedAtoms(C6, 1).get(0).setName("H6");
1594       }
1595     }
1596 
1597     // Common atoms: N1, C2, O2, N3, C4, C5, C6, H6
1598     return keyAtoms;
1599   }
1600 
1601   /**
1602    * renameDeltaHydrogen.
1603    *
1604    * @param residue a {@link ffx.potential.bonded.Residue} object.
1605    * @param resAtoms a {@link java.util.List} object.
1606    * @param indexes The HD indexes to use.
1607    */
1608   public static void renameDeltaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1609     Atom[] HDn = new Atom[3];
1610     switch (indexes) {
1611       case 12 -> {
1612         HDn[0] = (Atom) residue.getAtomNode("HD1");
1613         HDn[1] = (Atom) residue.getAtomNode("HD2");
1614       }
1615       case 13 -> {
1616         HDn[0] = (Atom) residue.getAtomNode("HD1");
1617         HDn[2] = (Atom) residue.getAtomNode("HD3");
1618       }
1619       case 23 -> {
1620         HDn[1] = (Atom) residue.getAtomNode("HD2");
1621         HDn[2] = (Atom) residue.getAtomNode("HD3");
1622       }
1623       default -> {
1624         return;
1625       }
1626     }
1627     for (Atom HDatom : HDn) {
1628       resAtoms.remove(HDatom);
1629     }
1630     if (!resAtoms.isEmpty() && HDn[0] == null && (indexes == 12 || indexes == 13)) {
1631       resAtoms.get(0).setName("HD1");
1632       resAtoms.remove(0);
1633     }
1634     if (!resAtoms.isEmpty() && HDn[1] == null && (indexes == 12 || indexes == 23)) {
1635       resAtoms.get(0).setName("HD2");
1636       resAtoms.remove(0);
1637     }
1638     if (!resAtoms.isEmpty() && HDn[2] == null && (indexes == 13 || indexes == 23)) {
1639       resAtoms.get(0).setName("HD3");
1640       resAtoms.remove(0);
1641     }
1642   }
1643 
1644   /**
1645    * renameEpsilonHydrogen.
1646    *
1647    * @param residue a {@link ffx.potential.bonded.Residue} object.
1648    * @param resAtoms a {@link java.util.List} object.
1649    * @param indexes The HE indexes to use.
1650    */
1651   public static void renameEpsilonHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1652     Atom[] HEn = new Atom[3];
1653     switch (indexes) {
1654       case 12 -> {
1655         HEn[0] = (Atom) residue.getAtomNode("HE1");
1656         HEn[1] = (Atom) residue.getAtomNode("HE2");
1657       }
1658       case 13 -> {
1659         HEn[0] = (Atom) residue.getAtomNode("HE1");
1660         HEn[2] = (Atom) residue.getAtomNode("HE3");
1661       }
1662       case 23 -> {
1663         HEn[1] = (Atom) residue.getAtomNode("HE2");
1664         HEn[2] = (Atom) residue.getAtomNode("HE3");
1665       }
1666       default -> {
1667         return;
1668       }
1669     }
1670     for (Atom HEatom : HEn) {
1671       resAtoms.remove(HEatom);
1672     }
1673     if (!resAtoms.isEmpty() && HEn[0] == null && (indexes == 12 || indexes == 13)) {
1674       resAtoms.get(0).setName("HE1");
1675       resAtoms.remove(0);
1676     }
1677     if (!resAtoms.isEmpty() && HEn[1] == null && (indexes == 12 || indexes == 23)) {
1678       resAtoms.get(0).setName("HE2");
1679       resAtoms.remove(0);
1680     }
1681     if (!resAtoms.isEmpty() && HEn[2] == null && (indexes == 13 || indexes == 23)) {
1682       resAtoms.get(0).setName("HE3");
1683       resAtoms.remove(0);
1684     }
1685   }
1686 
1687   /**
1688    * renameGammaHydrogen.
1689    *
1690    * @param residue a {@link ffx.potential.bonded.Residue} object.
1691    * @param resAtoms a {@link java.util.List} object.
1692    * @param indexes The HG indexes to use.
1693    */
1694   public static void renameGammaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1695     Atom[] HGn = new Atom[3];
1696     switch (indexes) {
1697       case 12 -> {
1698         HGn[0] = (Atom) residue.getAtomNode("HG1");
1699         HGn[1] = (Atom) residue.getAtomNode("HG2");
1700       }
1701       case 13 -> {
1702         HGn[0] = (Atom) residue.getAtomNode("HG1");
1703         HGn[2] = (Atom) residue.getAtomNode("HG3");
1704       }
1705       case 23 -> {
1706         HGn[1] = (Atom) residue.getAtomNode("HG2");
1707         HGn[2] = (Atom) residue.getAtomNode("HG3");
1708       }
1709       default -> {
1710         return;
1711       }
1712     }
1713     for (Atom HGatom : HGn) {
1714       resAtoms.remove(HGatom);
1715     }
1716     if (!resAtoms.isEmpty() && HGn[0] == null && (indexes == 12 || indexes == 13)) {
1717       resAtoms.get(0).setName("HG1");
1718       resAtoms.remove(0);
1719     }
1720     if (!resAtoms.isEmpty() && HGn[1] == null && (indexes == 12 || indexes == 23)) {
1721       resAtoms.get(0).setName("HG2");
1722       resAtoms.remove(0);
1723     }
1724     if (!resAtoms.isEmpty() && HGn[2] == null && (indexes == 13 || indexes == 23)) {
1725       resAtoms.get(0).setName("HG3");
1726       resAtoms.remove(0);
1727     }
1728   }
1729 
1730   /**
1731    * renameGlutamineHydrogen.
1732    *
1733    * @param residue a {@link ffx.potential.bonded.Residue} object.
1734    * @param resAtoms a {@link java.util.List} object.
1735    */
1736   public static void renameGlutamineHydrogen(Residue residue, List<Atom> resAtoms) {
1737     Atom HE21 = (Atom) residue.getAtomNode("HE21");
1738     Atom HE22 = (Atom) residue.getAtomNode("HE22");
1739     if (HE21 != null) {
1740       resAtoms.remove(HE21);
1741     }
1742     if (HE22 != null) {
1743       resAtoms.remove(HE22);
1744     }
1745     if (!resAtoms.isEmpty() && HE21 == null) {
1746       resAtoms.get(0).setName("HE21");
1747       resAtoms.remove(0);
1748     }
1749     if (!resAtoms.isEmpty() && HE22 == null) {
1750       resAtoms.get(0).setName("HE21");
1751     }
1752   }
1753 
1754   /**
1755    * renameGlycineAlphaHydrogen.
1756    *
1757    * @param residue a {@link ffx.potential.bonded.Residue} object.
1758    * @param resAtoms a {@link java.util.List} object.
1759    */
1760   public static void renameGlycineAlphaHydrogen(Residue residue, List<Atom> resAtoms) {
1761     Atom HA2 = (Atom) residue.getAtomNode("HA2");
1762     Atom HA3 = (Atom) residue.getAtomNode("HA3");
1763     if (HA2 != null) {
1764       resAtoms.remove(HA2);
1765     }
1766     if (HA3 != null) {
1767       resAtoms.remove(HA3);
1768     }
1769     if (HA2 == null && !resAtoms.isEmpty()) {
1770       resAtoms.get(0).setName("HA2");
1771       resAtoms.remove(0);
1772     }
1773     if (HA3 == null && !resAtoms.isEmpty()) {
1774       resAtoms.get(0).setName("HA3");
1775     }
1776   }
1777 
1778   /**
1779    * renameIsoleucineHydrogen.
1780    *
1781    * @param residue a {@link ffx.potential.bonded.Residue} object.
1782    * @param resAtoms a {@link java.util.List} object.
1783    */
1784   public static void renameIsoleucineHydrogen(Residue residue, List<Atom> resAtoms) {
1785     Atom HG12 = (Atom) residue.getAtomNode("HG12");
1786     Atom HG13 = (Atom) residue.getAtomNode("HG13");
1787     if (HG12 != null) {
1788       resAtoms.remove(HG12);
1789     }
1790     if (HG13 != null) {
1791       resAtoms.remove(HG13);
1792     }
1793     if (HG12 == null && !resAtoms.isEmpty()) {
1794       resAtoms.get(0).setName("HG12");
1795       resAtoms.remove(0);
1796     }
1797     if (HG13 == null && !resAtoms.isEmpty()) {
1798       resAtoms.get(0).setName("HG13");
1799     }
1800   }
1801 
1802   /**
1803    * renameNTerminusHydrogen.
1804    *
1805    * @param residue a {@link ffx.potential.bonded.Residue} object.
1806    */
1807   public static void renameNTerminusHydrogen(Residue residue) {
1808     Atom[] h = new Atom[3];
1809     h[0] = (Atom) residue.getAtomNode("H1");
1810     h[1] = (Atom) residue.getAtomNode("H2");
1811     h[2] = (Atom) residue.getAtomNode("H3");
1812     int numAtoms = 0;
1813     for (Atom atom : h) {
1814       numAtoms += (atom == null ? 0 : 1);
1815     }
1816     if (numAtoms == 3) {
1817       return;
1818     }
1819     List<Atom> resAtoms = residue.getAtomList();
1820     for (Atom resAtom : resAtoms) {
1821       // Check if already contained in h[].
1822       boolean doContinue = false;
1823       for (Atom hAtom : h) {
1824         if (resAtom.equals(hAtom)) {
1825           doContinue = true;
1826           break;
1827         }
1828       }
1829       if (doContinue) {
1830         continue;
1831       }
1832 
1833       // If the hydrogen matches H or H[1-3], assign to first null h entity.
1834       String atomName = resAtom.getName().toUpperCase();
1835       if (atomName.equals("H") || atomName.matches("H[1-3]") || atomName.matches("[1-3]H")) {
1836         ++numAtoms;
1837         for (int i = 0; i < h.length; i++) {
1838           if (h[i] == null) {
1839             resAtom.setName("H" + (i + 1));
1840             h[i] = resAtom;
1841             break;
1842           }
1843         }
1844         if (numAtoms == 3) {
1845           return;
1846         }
1847       }
1848     }
1849   }
1850 
1851   /**
1852    * Renames the Atoms in a nucleic acid to PDB standard.
1853    *
1854    * @param residue Residue to fix atom names of.
1855    */
1856   public static void renameNucleicAcidToPDBStandard(Residue residue) {
1857     if (residue.getChainID() == null) {
1858       logger.info(" Setting Chain ID to Z for " + residue);
1859       residue.setChainID('Z');
1860     }
1861     assert residue.getResidueType() == Residue.ResidueType.NA;
1862     NucleicAcid3 na3 = residue.getNucleicAcid3(true);
1863     residue.setName(na3.toString());
1864     switch (na3) {
1865       case ADE, DAD, CYT, DCY, GUA, DGU, THY, DTY, URI -> renameCommonNucleicAcid(residue, na3);
1866       default -> logger.info(" Could not rename atoms for nonstandard nucleic acid " + na3);
1867     }
1868   }
1869 
1870   /**
1871    * renameZetaHydrogen.
1872    *
1873    * @param residue a {@link ffx.potential.bonded.Residue} object.
1874    * @param resAtoms a {@link java.util.List} object.
1875    * @param indexes The HZ atom indices to use.
1876    */
1877   public static void renameZetaHydrogen(Residue residue, List<Atom> resAtoms, int indexes) {
1878     Atom[] HZn = new Atom[3];
1879     switch (indexes) {
1880       case 12 -> {
1881         HZn[0] = (Atom) residue.getAtomNode("HZ1");
1882         HZn[1] = (Atom) residue.getAtomNode("HZ2");
1883       }
1884       case 13 -> {
1885         HZn[0] = (Atom) residue.getAtomNode("HZ1");
1886         HZn[2] = (Atom) residue.getAtomNode("HZ3");
1887       }
1888       case 23 -> {
1889         HZn[1] = (Atom) residue.getAtomNode("HZ2");
1890         HZn[2] = (Atom) residue.getAtomNode("HZ3");
1891       }
1892       default -> {
1893         return;
1894       }
1895     }
1896     for (Atom HZatom : HZn) {
1897       resAtoms.remove(HZatom);
1898     }
1899     if (!resAtoms.isEmpty() && HZn[0] == null && (indexes == 12 || indexes == 13)) {
1900       resAtoms.get(0).setName("HZ1");
1901       resAtoms.remove(0);
1902     }
1903     if (!resAtoms.isEmpty() && HZn[1] == null && (indexes == 12 || indexes == 23)) {
1904       resAtoms.get(0).setName("HZ2");
1905       resAtoms.remove(0);
1906     }
1907     if (!resAtoms.isEmpty() && HZn[2] == null && (indexes == 13 || indexes == 23)) {
1908       resAtoms.get(0).setName("HZ3");
1909       resAtoms.remove(0);
1910     }
1911   }
1912 
1913   /** Common HETATOM labels for water and ions. */
1914   public enum HetAtoms {
1915     BR,
1916     CA,
1917     CA2,
1918     CL,
1919     K,
1920     MG,
1921     MG2,
1922     NA,
1923     HOH,
1924     H2O,
1925     WAT,
1926     ZN,
1927     ZN2;
1928 
1929     /**
1930      * Slightly more robust parsing function that ignores case and trailing numbers, -, and +
1931      *
1932      * @param str String to parse
1933      * @return Corresponding HetAtoms value.
1934      */
1935     public static HetAtoms parse(String str) {
1936       String hName = str.toUpperCase().replaceFirst("[0-9+\\-]+$", "");
1937       return valueOf(hName);
1938     }
1939   }
1940 }