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