1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  package ffx.potential.parsers;
39  
40  import ffx.crystal.Crystal;
41  import ffx.crystal.SpaceGroup;
42  import ffx.crystal.SpaceGroupDefinitions;
43  import ffx.crystal.SpaceGroupInfo;
44  import ffx.crystal.SymOp;
45  import ffx.numerics.math.DoubleMath;
46  import ffx.potential.MolecularAssembly;
47  import ffx.potential.Utilities.FileType;
48  import ffx.potential.bonded.AminoAcidUtils;
49  import ffx.potential.bonded.AminoAcidUtils.AminoAcid3;
50  import ffx.potential.bonded.Atom;
51  import ffx.potential.bonded.Bond;
52  import ffx.potential.bonded.MSNode;
53  import ffx.potential.bonded.Molecule;
54  import ffx.potential.bonded.NucleicAcidUtils;
55  import ffx.potential.bonded.Polymer;
56  import ffx.potential.bonded.Residue;
57  import ffx.potential.parameters.ForceField;
58  import ffx.utilities.Hybrid36;
59  import ffx.utilities.StringUtils;
60  import org.apache.commons.configuration2.CompositeConfiguration;
61  
62  import java.io.BufferedReader;
63  import java.io.BufferedWriter;
64  import java.io.File;
65  import java.io.FileNotFoundException;
66  import java.io.FileReader;
67  import java.io.FileWriter;
68  import java.io.IOException;
69  import java.util.ArrayList;
70  import java.util.Collections;
71  import java.util.HashMap;
72  import java.util.List;
73  import java.util.Map;
74  import java.util.Objects;
75  import java.util.OptionalDouble;
76  import java.util.Set;
77  import java.util.logging.Level;
78  import java.util.logging.Logger;
79  import java.util.regex.Matcher;
80  import java.util.regex.Pattern;
81  import java.util.stream.Collectors;
82  
83  import static ffx.potential.bonded.BondedUtils.numberAtoms;
84  import static ffx.potential.bonded.NamingUtils.renameAtomsToPDBStandard;
85  import static ffx.potential.bonded.PolymerUtils.assignAtomTypes;
86  import static ffx.potential.bonded.PolymerUtils.buildDisulfideBonds;
87  import static ffx.potential.bonded.PolymerUtils.buildMissingResidues;
88  import static ffx.potential.bonded.PolymerUtils.locateDisulfideBonds;
89  import static ffx.potential.parsers.PDBFilter.PDBFileStandard.VERSION3_2;
90  import static ffx.potential.parsers.PDBFilter.PDBFileStandard.VERSION3_3;
91  import static ffx.utilities.StringUtils.padLeft;
92  import static java.lang.Double.parseDouble;
93  import static java.lang.Integer.parseInt;
94  import static java.lang.String.format;
95  import static org.apache.commons.lang3.StringUtils.repeat;
96  import static org.apache.commons.math3.util.FastMath.min;
97  import static org.apache.commons.math3.util.FastMath.toDegrees;
98  
99  
100 
101 
102 
103 
104 
105 
106 
107 
108 public final class PDBFilter extends SystemFilter {
109 
110   private static final Logger logger = Logger.getLogger(PDBFilter.class.getName());
111   private static final Set<String> backboneNames;
112   private static final Set<String> constantPhBackboneNames;
113   private static final Set<String> naBackboneNames;
114 
115   static {
116     String[] names = {"C", "CA", "N", "O", "OXT", "OT2"};
117     backboneNames = Set.of(names);
118 
119     String[] constantPhNames = {"C", "CA", "N", "O", "OXT", "OT2", "H", "HA", "H1", "H2", "H3"};
120     constantPhBackboneNames = Set.of(constantPhNames);
121 
122     String[] naNames = {"P", "OP1", "OP2", "O5'", "C5'", "C4'", "O4'", "C3'", "O3'", "C2'", "C1'"};
123     naBackboneNames = Set.of(naNames);
124   }
125 
126   
127   private final Map<Character, String[]> seqRes = new HashMap<>();
128   
129   private final Map<Character, int[]> dbRef = new HashMap<>();
130   
131   private final List<Character> altLocs = new ArrayList<>();
132   
133 
134 
135 
136 
137 
138 
139 
140 
141 
142   private final List<String> segIDs = new ArrayList<>();
143 
144   private final Map<Character, List<String>> segidMap = new HashMap<>();
145   
146   private final Map<Character, Integer> insertionCodeCount = new HashMap<>();
147   
148 
149 
150 
151   private final Map<String, String> pdbToNewResMap = new HashMap<>();
152   
153   private final Map<String, String> modRes = new HashMap<>();
154   
155   private final HashMap<Integer, Atom> atoms = new HashMap<>();
156 
157   private final Map<MolecularAssembly, BufferedReader> readers = new HashMap<>();
158   
159   private Character currentAltLoc = 'A';
160   
161   private Character currentChainID = null;
162   
163   private String currentSegID = null;
164   
165   private boolean mutate = false;
166   private List<Mutation> mutations = null;
167   private List<Integer> resNumberList = null;
168   
169   private boolean printMissingFields = true;
170   
171   private int nSymOp = -1;
172   
173   private int lValue = -1;
174   
175   private int mValue = -1;
176   
177   private int nValue = -1;
178   
179 
180 
181 
182   private int serialP1 = 0;
183   
184   private PDBFileStandard fileStandard = VERSION3_3;
185   
186   private boolean logWrites = true;
187   
188   private int modelsRead = 1;
189   
190   private int modelsWritten = -1;
191   
192   private int[] lmn = new int[]{1,1,1};
193   private String versionFileName;
194 
195   private final File readFile;
196   private List<String> remarkLines = Collections.emptyList();
197   private double lastReadLambda = Double.NaN;
198 
199   
200 
201 
202   private boolean constantPH = false;
203   
204 
205 
206   private boolean rotamerTitration = false;
207   
208 
209 
210   private static final HashMap<AminoAcid3, AminoAcid3> constantPHResidueMap = new HashMap<>();
211 
212   static {
213     
214     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.LYD, AminoAcidUtils.AminoAcid3.LYS);
215     
216     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.CYD, AminoAcidUtils.AminoAcid3.CYS);
217     
218     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.HID, AminoAcidUtils.AminoAcid3.HIS);
219     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.HIE, AminoAcidUtils.AminoAcid3.HIS);
220     
221     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.ASP, AminoAcidUtils.AminoAcid3.ASD);
222     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.ASH, AminoAcidUtils.AminoAcid3.ASD);
223     
224     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.GLU, AminoAcidUtils.AminoAcid3.GLD);
225     constantPHResidueMap.put(AminoAcidUtils.AminoAcid3.GLH, AminoAcidUtils.AminoAcid3.GLD);
226   }
227 
228   
229 
230 
231   private static final HashMap<AminoAcid3, AminoAcid3> rotamerResidueMap = new HashMap<>();
232 
233   static {
234     
235     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.LYD, AminoAcidUtils.AminoAcid3.LYS);
236     
237     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.HID, AminoAcidUtils.AminoAcid3.HIS);
238     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.HIE, AminoAcidUtils.AminoAcid3.HIS);
239     
240     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.ASP, AminoAcidUtils.AminoAcid3.ASH);
241     
242     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.GLU, AminoAcidUtils.AminoAcid3.GLH);
243     
244     rotamerResidueMap.put(AminoAcidUtils.AminoAcid3.CYD, AminoAcidUtils.AminoAcid3.CYS);
245   }
246 
247   
248 
249 
250 
251 
252 
253 
254 
255 
256   public PDBFilter(List<File> files, MolecularAssembly molecularAssembly, ForceField forceField,
257                    CompositeConfiguration properties) {
258     super(files, molecularAssembly, forceField, properties);
259     bondList = new ArrayList<>();
260     this.fileType = FileType.PDB;
261     readFile = files.get(0);
262   }
263 
264   
265 
266 
267 
268 
269 
270 
271 
272 
273   public PDBFilter(File file, MolecularAssembly molecularAssembly, ForceField forceField,
274                    CompositeConfiguration properties) {
275     super(file, molecularAssembly, forceField, properties);
276     bondList = new ArrayList<>();
277     this.fileType = FileType.PDB;
278     readFile = file;
279   }
280 
281   
282 
283 
284 
285 
286 
287 
288 
289 
290   public PDBFilter(File file, List<MolecularAssembly> molecularAssemblies, ForceField forceField,
291                    CompositeConfiguration properties) {
292     super(file, molecularAssemblies, forceField, properties);
293     bondList = new ArrayList<>();
294     this.fileType = FileType.PDB;
295     readFile = file;
296   }
297 
298   
299 
300 
301 
302 
303 
304 
305 
306 
307 
308 
309   public PDBFilter(File file, MolecularAssembly molecularAssembly, ForceField forceField,
310                    CompositeConfiguration properties, List<Integer> resNumberList) {
311     super(file, molecularAssembly, forceField, properties);
312     bondList = new ArrayList<>();
313     this.fileType = FileType.PDB;
314     this.readFile = file;
315     this.resNumberList = resNumberList;
316     
317   }
318 
319 
320   
321 
322 
323 
324 
325 
326   public static String toPDBAtomLine(Atom atom) {
327     StringBuilder sb;
328     if (atom.isHetero()) {
329       sb = new StringBuilder("HETATM");
330     } else {
331       sb = new StringBuilder("ATOM  ");
332     }
333     sb.append(repeat(" ", 74));
334 
335     String name = atom.getName();
336     int nameLength = name.length();
337     if (nameLength > 4) {
338       name = name.substring(0, 4);
339     } else if (nameLength == 1) {
340       name = name + "  ";
341     } else if (nameLength == 2) {
342       name = name + " ";
343     }
344     int serial = atom.getXyzIndex();
345     sb.replace(6, 16, format("%5s " + padLeft(name.toUpperCase(), 4), Hybrid36.encode(5, serial)));
346 
347     Character altLoc = atom.getAltLoc();
348     if (altLoc != null) {
349       sb.setCharAt(16, altLoc);
350     } else {
351       char blankChar = ' ';
352       sb.setCharAt(16, blankChar);
353     }
354 
355     String resName = atom.getResidueName();
356     sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
357 
358     char chain = atom.getChainID();
359     sb.setCharAt(21, chain);
360 
361     int resID = atom.getResidueNumber();
362     sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
363 
364     double[] xyz = atom.getXYZ(null);
365     StringBuilder decimals = new StringBuilder();
366     for (int i = 0; i < 3; i++) {
367       try {
368         decimals.append(StringUtils.fwFpDec(xyz[i], 8, 3));
369       } catch (IllegalArgumentException ex) {
370         String newValue = StringUtils.fwFpTrunc(xyz[i], 8, 3);
371         logger.info(
372                 format(" XYZ coordinate %8.3f for atom %s overflowed PDB format and is truncated to %s.",
373                         xyz[i], atom, newValue));
374         decimals.append(newValue);
375       }
376     }
377     try {
378       decimals.append(StringUtils.fwFpDec(atom.getOccupancy(), 6, 2));
379     } catch (IllegalArgumentException ex) {
380       logger.severe(
381               format(" Occupancy %6.2f for atom %s must be between 0 and 1.", atom.getOccupancy(),
382                       atom));
383     }
384     try {
385       decimals.append(StringUtils.fwFpDec(atom.getTempFactor(), 6, 2));
386     } catch (IllegalArgumentException ex) {
387       String newValue = StringUtils.fwFpTrunc(atom.getTempFactor(), 6, 2);
388       logger.info(
389               format(" B-factor %6.2f for atom %s overflowed the PDB format and is truncated to %s.",
390                       atom.getTempFactor(), atom, newValue));
391       decimals.append(newValue);
392     }
393 
394     sb.replace(30, 66, decimals.toString());
395     sb.replace(78, 80, format("%2d", 0));
396     sb.append("\n");
397     return sb.toString();
398   }
399 
400   public void setConstantPH(boolean constantPH) {
401     this.constantPH = constantPH;
402   }
403 
404   public void setRotamerTitration(boolean rotamerTitration) {
405     this.rotamerTitration = rotamerTitration;
406   }
407 
408   
409   public void clearSegIDs() {
410     segIDs.clear();
411   }
412 
413   
414   @Override
415   public void closeReader() {
416     for (MolecularAssembly system : systems) {
417       BufferedReader br = readers.get(system);
418       if (br != null) {
419         try {
420           br.close();
421         } catch (IOException ex) {
422           logger.warning(format(" Exception in closing system %s: %s", system.toString(), ex));
423         }
424       }
425     }
426   }
427 
428   @Override
429   public int countNumModels() {
430     Set<File> files = systems.stream().map(MolecularAssembly::getFile).map(File::toString).distinct()
431             .map(File::new).collect(Collectors.toSet());
432 
433     
434     return files.parallelStream().mapToInt((File fi) -> {
435       int nModelsLocal = 0;
436       try (BufferedReader br = new BufferedReader(new FileReader(fi))) {
437         String line = br.readLine();
438         while (line != null) {
439           if (line.startsWith("MODEL")) {
440             ++nModelsLocal;
441           }
442           line = br.readLine();
443         }
444         nModelsLocal = Math.max(1, nModelsLocal);
445       } catch (IOException ex) {
446         logger.info(format(" Exception in parsing file %s: %s", fi, ex));
447       }
448       return nModelsLocal;
449     }).sum();
450   }
451 
452   
453 
454 
455 
456 
457   public List<Character> getAltLocs() {
458     return altLocs;
459   }
460 
461   
462   @Override
463   public OptionalDouble getLastReadLambda() {
464     return Double.isNaN(lastReadLambda) ? OptionalDouble.empty() : OptionalDouble.of(lastReadLambda);
465   }
466 
467   
468 
469 
470 
471 
472   @Override
473   public String[] getRemarkLines() {
474     int nRemarks = remarkLines.size();
475     return remarkLines.toArray(new String[nRemarks]);
476   }
477 
478   @Override
479   public int getSnapshot() {
480     return modelsRead;
481   }
482 
483   
484 
485 
486 
487 
488   public void mutate(List<Mutation> mutations) {
489     mutate = true;
490     if (this.mutations == null) {
491       this.mutations = new ArrayList<>();
492     }
493     this.mutations.addAll(mutations);
494   }
495 
496   
497   @Override
498   public boolean readFile() {
499     remarkLines = new ArrayList<>();
500     
501     int xyzIndex = 1;
502     setFileRead(false);
503     systems.add(activeMolecularAssembly);
504 
505     List<String> conects = new ArrayList<>();
506     List<String> links = new ArrayList<>();
507     List<String> ssbonds = new ArrayList<>();
508     List<String> structs = new ArrayList<>();
509     try {
510       for (File file : files) {
511         currentFile = file;
512         if (mutate) {
513           List<Character> chainIDs = new ArrayList<>();
514           try (BufferedReader br = new BufferedReader(new FileReader(file))) {
515             String line = br.readLine();
516             while (line != null) {
517               
518               line = line.replaceAll("\t", "    ");
519               String identity = line;
520               if (line.length() > 6) {
521                 identity = line.substring(0, 6);
522               }
523               identity = identity.trim().toUpperCase();
524               Record record;
525               try {
526                 record = Record.valueOf(identity);
527               } catch (Exception e) {
528                 
529                 line = br.readLine();
530                 continue;
531               }
532               switch (record) {
533                 case ANISOU, HETATM, ATOM -> {
534                   char c22 = line.charAt(21);
535                   boolean idFound = false;
536                   for (Character chainID : chainIDs) {
537                     if (c22 == chainID) {
538                       idFound = true;
539                       break;
540                     }
541                   }
542                   if (!idFound) {
543                     chainIDs.add(c22);
544                   }
545                 }
546               }
547               line = br.readLine();
548             }
549             for (Mutation mtn : mutations) {
550               if (!chainIDs.contains(mtn.chainChar)) {
551                 if (chainIDs.size() == 1) {
552                   logger.warning(
553                           format(" Chain ID %c for mutation not found: only one chain %c found.",
554                                   mtn.chainChar, chainIDs.get(0)));
555                 } else {
556                   logger.warning(
557                           format(" Chain ID %c for mutation not found: mutation will not proceed.",
558                                   mtn.chainChar));
559                 }
560               }
561             }
562           } catch (IOException ioException) {
563             logger.fine(format(" Exception %s in parsing file to find chain IDs", ioException));
564           }
565         }
566 
567         
568         if (currentFile == null || !currentFile.exists() || !currentFile.canRead()) {
569           return false;
570         }
571 
572         
573         try (BufferedReader br = new BufferedReader(new FileReader(currentFile))) {
574           
575           if (currentAltLoc == 'A') {
576             logger.info(format(" Reading %s", currentFile.getName()));
577           } else {
578             logger.info(format(" Reading %s alternate location %s", currentFile.getName(), currentAltLoc));
579 
580           }
581           activeMolecularAssembly.setAlternateLocation(currentAltLoc);
582 
583           
584           currentChainID = null;
585           currentSegID = null;
586           boolean containsInsCode = false;
587 
588           
589           String line = br.readLine();
590 
591           
592           while (line != null) {
593             
594             line = line.replaceAll("\t", "    ");
595             String identity = line;
596             if (line.length() > 6) {
597               identity = line.substring(0, 6);
598             }
599             identity = identity.trim().toUpperCase();
600             Record record;
601             try {
602               record = Record.valueOf(identity);
603             } catch (Exception e) {
604               
605               line = br.readLine();
606               continue;
607             }
608 
609             
610             switch (record) {
611               case ENDMDL:
612               case END:
613                 
614                 line = null;
615                 continue;
616               case DBREF:
617 
618 
619 
620 
621 
622 
623 
624 
625 
626 
627 
628 
629 
630 
631 
632 
633 
634 
635 
636 
637 
638 
639 
640 
641                 Character chainID = line.substring(12, 13).toUpperCase().charAt(0);
642                 int seqBegin = parseInt(line.substring(14, 18).trim());
643                 int seqEnd = parseInt(line.substring(20, 24).trim());
644                 int[] seqRange = dbRef.computeIfAbsent(chainID, k -> new int[2]);
645                 seqRange[0] = seqBegin;
646                 seqRange[1] = seqEnd;
647                 break;
648               case SEQRES:
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 
659 
660 
661 
662 
663 
664 
665 
666 
667 
668 
669 
670 
671 
672 
673                 activeMolecularAssembly.addHeaderLine(line);
674                 chainID = line.substring(11, 12).toUpperCase().charAt(0);
675                 int serNum = parseInt(line.substring(7, 10).trim());
676                 String[] chain = seqRes.get(chainID);
677                 int numRes = parseInt(line.substring(13, 17).trim());
678                 if (chain == null) {
679                   chain = new String[numRes];
680                   seqRes.put(chainID, chain);
681                 }
682                 int resID = (serNum - 1) * 13;
683                 int end = line.length();
684                 for (int start = 19; start + 3 <= end; start += 4) {
685                   String res = line.substring(start, start + 3).trim();
686                   if (res.isEmpty()) {
687                     break;
688                   }
689                   chain[resID++] = res;
690                 }
691                 break;
692               case MODRES:
693                 String modResName = line.substring(12, 15).trim();
694                 String stdName = line.substring(24, 27).trim();
695                 modRes.put(modResName.toUpperCase(), stdName.toUpperCase());
696                 activeMolecularAssembly.addHeaderLine(line);
697 
698 
699 
700 
701 
702 
703 
704 
705 
706 
707                 break;
708               case ANISOU:
709 
710 
711 
712 
713 
714 
715 
716 
717 
718 
719 
720 
721 
722 
723 
724 
725 
726 
727                 boolean deleteAnisou = properties.getBoolean("delete-anisou", false);
728                 double resetBfactors = properties.getDouble("reset-bfactors", -1.0);
729                 if (deleteAnisou || resetBfactors >= 0.0) {
730                   break;
731                 }
732                 Integer serial = Hybrid36.decode(5, line.substring(6, 11));
733                 Character altLoc = line.substring(16, 17).toUpperCase().charAt(0);
734                 if (!altLocs.contains(altLoc)) {
735                   altLocs.add(altLoc);
736                 }
737                 if (!altLoc.equals(' ') && !altLoc.equals('A') && !altLoc.equals(currentAltLoc)) {
738                   break;
739                 }
740                 double[] adp = new double[6];
741                 adp[0] = parseInt(line.substring(28, 35).trim()) * 1.0e-4;
742                 adp[1] = parseInt(line.substring(35, 42).trim()) * 1.0e-4;
743                 adp[2] = parseInt(line.substring(42, 49).trim()) * 1.0e-4;
744                 adp[3] = parseInt(line.substring(49, 56).trim()) * 1.0e-4;
745                 adp[4] = parseInt(line.substring(56, 63).trim()) * 1.0e-4;
746                 adp[5] = parseInt(line.substring(63, 70).trim()) * 1.0e-4;
747                 if (atoms.containsKey(serial)) {
748                   Atom a = atoms.get(serial);
749                   a.setAltLoc(altLoc);
750                   a.setAnisou(adp);
751                 } else {
752                   logger.info(
753                           format(" No ATOM record for ANISOU serial number %d has been found.", serial));
754                   logger.info(format(" This ANISOU record will be ignored:\n %s", line));
755                 }
756                 break;
757               case ATOM:
758 
759 
760 
761 
762 
763 
764 
765 
766 
767 
768 
769 
770 
771 
772 
773 
774 
775                 String name;
776                 String resName;
777                 String segID;
778                 int resSeq;
779                 boolean printAtom;
780                 double[] d;
781                 double occupancy;
782                 double tempFactor;
783                 Atom newAtom;
784                 Atom returnedAtom;
785                 
786                 if (!line.substring(17, 20).trim().equals("HOH")) {
787                   serial = Hybrid36.decode(5, line.substring(6, 11));
788                   name = line.substring(12, 16).trim();
789                   if (name.toUpperCase().contains("1H") || name.toUpperCase().contains("2H")
790                           || name.toUpperCase().contains("3H")) {
791                     
792                     fileStandard = VERSION3_2;
793                   }
794                   altLoc = line.substring(16, 17).toUpperCase().charAt(0);
795                   if (!altLocs.contains(altLoc)) {
796                     altLocs.add(altLoc);
797                   }
798 
799                   if (!altLoc.equals(' ') && !altLoc.equals('A') && !altLoc.equals(currentAltLoc)) {
800                     break;
801                   }
802 
803                   resName = line.substring(17, 20).trim();
804                   chainID = line.substring(21, 22).charAt(0);
805                   segID = getSegID(chainID);
806                   resSeq = Hybrid36.decode(4, line.substring(22, 26));
807 
808                   char insertionCode = line.charAt(26);
809                   if (insertionCode != ' ' && !containsInsCode) {
810                     containsInsCode = true;
811                     logger.warning(
812                             " FFX support for files with " + "insertion codes is experimental. "
813                                     + "Residues will be renumbered to " + "eliminate insertion codes (52A "
814                                     + "becomes 53, 53 becomes 54, etc)");
815                   }
816 
817                   int offset = insertionCodeCount.getOrDefault(chainID, 0);
818                   String pdbResNum = format("%c%d%c", chainID, resSeq, insertionCode);
819                   if (!pdbToNewResMap.containsKey(pdbResNum)) {
820                     if (insertionCode != ' ') {
821                       ++offset;
822                       insertionCodeCount.put(chainID, offset);
823                     }
824                     resSeq += offset;
825                     if (offset != 0) {
826                       logger.info(
827                               format(" Chain %c " + "residue %s-%s renumbered to %c %s-%d", chainID,
828                                       pdbResNum.substring(1).trim(), resName, chainID, resName, resSeq));
829                     }
830                     String newNum = format("%c%d", chainID, resSeq);
831                     pdbToNewResMap.put(pdbResNum, newNum);
832                   } else {
833                     resSeq += offset;
834                   }
835 
836                   printAtom = false;
837                   if (mutate) {
838                     boolean doBreak = false;
839                     for (Mutation mtn : mutations) {
840                       if (chainID == mtn.chainChar && resSeq == mtn.resID) {
841                         mtn.origResName = resName;
842                         resName = mtn.resName;
843                         String atomName = name.toUpperCase();
844 
845                         int isAA = AminoAcidUtils.getAminoAcidNumber(resName);
846                         int isNA = NucleicAcidUtils.getNucleicAcidNumber(resName);
847 
848                         if ((isNA != -1 && naBackboneNames.contains(atomName)) || (isAA != -1 && backboneNames.contains(atomName))) {
849                           printAtom = true;
850                         } else {
851                           
852                           ArrayList<String> alchAtoms = mtn.getAlchemicalAtoms(false);
853                           if (alchAtoms == null) {
854                             
855                             String newName = mtn.isNonAlchemicalAtom(atomName);
856                             if (newName != null) { 
857                               printAtom = true;
858                               if (newName.startsWith("~")) { 
859                                 
860                                 name = newName.substring(1);
861                                 logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
862                               } else {
863                                 
864                                 name = newName;
865                               }
866                               doBreak = false;
867                             } else if (!atomName.contains("'")) {
868                               logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
869                               doBreak = true;
870                             } else {
871                               printAtom = true;
872                               doBreak = false;
873                             }
874                           } else {
875                             if (alchAtoms.contains(atomName) && !atomName.contains("'")) {
876                               logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
877                               doBreak = true;
878                             } else {
879                               printAtom = true;
880                               doBreak = false;
881                             }
882                           }
883                           break;
884                         }
885                       }
886                     }
887                     if (doBreak) {
888                       break;
889                     }
890                   }
891 
892                   if (constantPH) {
893                     AminoAcid3 aa3 = AminoAcidUtils.getAminoAcid(resName.toUpperCase());
894                     if (constantPHResidueMap.containsKey(aa3)) {
895                       String atomName = name.toUpperCase();
896                       AminoAcid3 aa3PH = constantPHResidueMap.get(aa3);
897                       resName = aa3PH.name();
898                       if (constantPhBackboneNames.contains(atomName)) {
899                         logger.info(format(" %s-%d %s", resName, resSeq, atomName));
900                       } else if (!atomName.startsWith("H")) {
901                         logger.info(format(" %s-%d %s", resName, resSeq, atomName));
902                       } else {
903                         logger.info(format(" %s-%d %s skipped", resName, resSeq, atomName));
904                         break;
905                       }
906                     }
907                   } else if (rotamerTitration) {
908                     AminoAcid3 aa3 = AminoAcidUtils.getAminoAcid(resName.toUpperCase());
909                     if (rotamerResidueMap.containsKey(aa3) && resNumberList.contains(resSeq)) {
910                       AminoAcid3 aa3rotamer = rotamerResidueMap.get(aa3);
911                       resName = aa3rotamer.name();
912                     }
913                   }
914                   d = new double[3];
915                   d[0] = parseDouble(line.substring(30, 38).trim());
916                   d[1] = parseDouble(line.substring(38, 46).trim());
917                   d[2] = parseDouble(line.substring(46, 54).trim());
918                   occupancy = 1.0;
919                   tempFactor = 1.0;
920                   try {
921                     occupancy = parseDouble(line.substring(54, 60).trim());
922                     tempFactor = parseDouble(line.substring(60, 66).trim());
923                   } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
924                     
925                     if (printMissingFields) {
926                       logger.info(" Missing occupancy and b-factors set to 1.0.");
927                       printMissingFields = false;
928                     } else if (logger.isLoggable(Level.FINE)) {
929                       logger.fine(" Missing occupancy and b-factors set to 1.0.");
930                     }
931                   }
932 
933                   double bfactor = properties.getDouble("reset-bfactors", -1.0);
934                   if (bfactor >= 0.0) {
935                     tempFactor = bfactor;
936                   }
937 
938                   newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy, tempFactor, segID);
939 
940                   
941                   if (modRes.containsKey(resName.toUpperCase())) {
942                     newAtom.setModRes(true);
943                   }
944                   returnedAtom = (Atom) activeMolecularAssembly.addMSNode(newAtom);
945                   if (returnedAtom != newAtom) {
946                     
947                     atoms.put(serial, returnedAtom);
948                     if (logger.isLoggable(Level.FINE)) {
949                       logger.fine(returnedAtom + " has been retained over\n" + newAtom);
950                     }
951                   } else {
952                     
953                     atoms.put(serial, newAtom);
954                     
955                     if (newAtom.getIndex() == 0) {
956                       newAtom.setXyzIndex(xyzIndex++);
957                     }
958                     if (printAtom) {
959                       logger.info(newAtom.toString());
960                     }
961                   }
962                   break;
963                 }
964                 break;
965               case HETATM:
966 
967 
968 
969 
970 
971 
972 
973 
974 
975 
976 
977 
978 
979 
980 
981 
982 
983                 serial = Hybrid36.decode(5, line.substring(6, 11));
984                 name = line.substring(12, 16).trim();
985                 altLoc = line.substring(16, 17).toUpperCase().charAt(0);
986                 if (!altLocs.contains(altLoc)) {
987                   altLocs.add(altLoc);
988                 }
989                 if (!altLoc.equals(' ') && !altLoc.equals(currentAltLoc)) {
990                   break;
991                 }
992                 
993                 
994                 
995                 resName = line.substring(17, 20).trim();
996                 chainID = line.substring(21, 22).charAt(0);
997                 segID = getSegID(chainID);
998                 resSeq = Hybrid36.decode(4, line.substring(22, 26));
999 
1000                 char insertionCode = line.charAt(26);
1001                 if (insertionCode != ' ' && !containsInsCode) {
1002                   containsInsCode = true;
1003                   logger.warning(" FFX support for files with " + "insertion codes is experimental. "
1004                           + "Residues will be renumbered to " + "eliminate insertion codes (52A "
1005                           + "becomes 53, 53 becomes 54, etc)");
1006                 }
1007 
1008                 int offset = insertionCodeCount.getOrDefault(chainID, 0);
1009                 String pdbResNum = format("%c%d%c", chainID, resSeq, insertionCode);
1010                 if (!pdbToNewResMap.containsKey(pdbResNum)) {
1011                   if (insertionCode != ' ') {
1012                     ++offset;
1013                     insertionCodeCount.put(chainID, offset);
1014                   }
1015                   resSeq += offset;
1016                   if (offset != 0) {
1017                     logger.info(
1018                             format(" Chain %c " + "molecule %s-%s renumbered to %c %s-%d", chainID,
1019                                     pdbResNum.substring(1).trim(), resName, chainID, resName, resSeq));
1020                   }
1021                   String newNum = format("%c%d", chainID, resSeq);
1022                   pdbToNewResMap.put(pdbResNum, newNum);
1023                 } else {
1024                   resSeq += offset;
1025                 }
1026 
1027                 d = new double[3];
1028                 d[0] = parseDouble(line.substring(30, 38).trim());
1029                 d[1] = parseDouble(line.substring(38, 46).trim());
1030                 d[2] = parseDouble(line.substring(46, 54).trim());
1031                 occupancy = 1.0;
1032                 tempFactor = 1.0;
1033                 try {
1034                   occupancy = parseDouble(line.substring(54, 60).trim());
1035                   tempFactor = parseDouble(line.substring(60, 66).trim());
1036                 } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
1037                   
1038                   if (printMissingFields) {
1039                     logger.info(" Missing occupancy and b-factors set to 1.0.");
1040                     printMissingFields = false;
1041                   } else if (logger.isLoggable(Level.FINE)) {
1042                     logger.fine(" Missing occupancy and b-factors set to 1.0.");
1043                   }
1044                 }
1045 
1046                 double bfactor = properties.getDouble("reset-bfactors", -1.0);
1047                 if (bfactor >= 0.0) {
1048                   tempFactor = bfactor;
1049                 }
1050 
1051                 newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy, tempFactor, segID);
1052                 newAtom.setHetero(true);
1053                 
1054                 if (modRes.containsKey(resName.toUpperCase())) {
1055                   newAtom.setModRes(true);
1056                 }
1057                 returnedAtom = (Atom) activeMolecularAssembly.addMSNode(newAtom);
1058                 if (returnedAtom != newAtom) {
1059                   
1060                   atoms.put(serial, returnedAtom);
1061                   if (logger.isLoggable(Level.FINE)) {
1062                     logger.fine(returnedAtom + " has been retained over\n" + newAtom);
1063                   }
1064                 } else {
1065                   
1066                   atoms.put(serial, newAtom);
1067                   newAtom.setXyzIndex(xyzIndex++);
1068                 }
1069                 break;
1070               case CRYST1:
1071 
1072 
1073 
1074 
1075 
1076 
1077 
1078 
1079 
1080 
1081 
1082 
1083 
1084 
1085                 if (line.length() < 55) {
1086                   logger.severe(" CRYST1 record is improperly formatted.");
1087                 }
1088                 double aaxis = parseDouble(line.substring(6, 15).trim());
1089                 double baxis = parseDouble(line.substring(15, 24).trim());
1090                 double caxis = parseDouble(line.substring(24, 33).trim());
1091                 double alpha = parseDouble(line.substring(33, 40).trim());
1092                 double beta = parseDouble(line.substring(40, 47).trim());
1093                 double gamma = parseDouble(line.substring(47, 54).trim());
1094                 int limit = min(line.length(), 66);
1095                 String sg = line.substring(55, limit).trim();
1096                 properties.addProperty("a-axis", aaxis);
1097                 properties.addProperty("b-axis", baxis);
1098                 properties.addProperty("c-axis", caxis);
1099                 properties.addProperty("alpha", alpha);
1100                 properties.addProperty("beta", beta);
1101                 properties.addProperty("gamma", gamma);
1102                 properties.addProperty("spacegroup", SpaceGroupInfo.pdb2ShortName(sg));
1103                 break;
1104               case CONECT:
1105 
1106 
1107 
1108 
1109 
1110 
1111 
1112 
1113 
1114 
1115 
1116 
1117                 conects.add(line);
1118                 break;
1119               case LINK:
1120 
1121 
1122 
1123 
1124 
1125 
1126 
1127 
1128 
1129 
1130 
1131 
1132 
1133 
1134 
1135 
1136 
1137 
1138 
1139 
1140 
1141                 char a1 = line.charAt(16);
1142                 char a2 = line.charAt(46);
1143                 if (a1 != a2) {
1144                   
1145                   
1146                   break;
1147                 }
1148                 if (currentAltLoc == 'A') {
1149                   if ((a1 == ' ' || a1 == 'A') && (a2 == ' ' || a2 == 'A')) {
1150                     links.add(line);
1151                   }
1152                 } else if (a1 == currentAltLoc && a2 == currentAltLoc) {
1153                   links.add(line);
1154                 }
1155                 break;
1156               case SSBOND:
1157 
1158 
1159 
1160 
1161 
1162 
1163 
1164 
1165 
1166 
1167 
1168 
1169 
1170 
1171 
1172 
1173 
1174 
1175 
1176 
1177 
1178 
1179 
1180 
1181 
1182 
1183 
1184 
1185                 ssbonds.add(line);
1186                 break;
1187               case HELIX:
1188 
1189 
1190 
1191 
1192 
1193 
1194 
1195 
1196 
1197 
1198 
1199 
1200 
1201 
1202 
1203 
1204 
1205 
1206 
1207 
1208 
1209 
1210 
1211 
1212 
1213 
1214 
1215 
1216 
1217 
1218 
1219 
1220 
1221 
1222 
1223 
1224 
1225 
1226               case SHEET:
1227 
1228 
1229 
1230 
1231 
1232 
1233 
1234 
1235 
1236 
1237 
1238 
1239 
1240 
1241 
1242 
1243 
1244 
1245 
1246 
1247 
1248 
1249 
1250 
1251 
1252 
1253 
1254 
1255 
1256 
1257 
1258 
1259 
1260 
1261                 structs.add(line);
1262                 break;
1263               case MODEL: 
1264                 break;
1265               case MTRIX1:
1266 
1267 
1268 
1269 
1270 
1271 
1272 
1273 
1274 
1275 
1276 
1277 
1278 
1279 
1280 
1281 
1282                 StringBuilder MTRX1 = new StringBuilder(line.substring(11, 55));
1283                 properties.addProperty("MTRIX1", MTRX1);
1284                 break;
1285               case MTRIX2:
1286                 StringBuilder MTRX2 = new StringBuilder(line.substring(11, 55));
1287                 properties.addProperty("MTRIX2", MTRX2);
1288                 break;
1289               case MTRIX3:
1290                 StringBuilder MTRX3 = new StringBuilder(line.substring(11, 55));
1291                 properties.addProperty("MTRIX3", MTRX3);
1292                 break;
1293               case REMARK:
1294                 remarkLines.add(line.trim());
1295                 if (line.contains("Lambda:")) {
1296                   Matcher m = lambdaPattern.matcher(line);
1297                   if (m.find()) {
1298                     lastReadLambda = Double.parseDouble(m.group(1));
1299                   }
1300                 }
1301 
1302 
1303 
1304 
1305 
1306 
1307 
1308 
1309 
1310 
1311 
1312 
1313 
1314 
1315                 if (line.length() >= 68) {
1316                   String remarkType = line.substring(7, 10).trim();
1317                   if (remarkType.matches("\\d+") && parseInt(remarkType) == 350 && line.substring(13,
1318                           18).equalsIgnoreCase("BIOMT")) {
1319                     properties.addProperty("BIOMTn", new StringBuilder(line.substring(24, 68)));
1320                   }
1321                 }
1322                 break;
1323               default:
1324                 break;
1325             }
1326             line = br.readLine();
1327           }
1328 
1329         } catch (FileNotFoundException fileNotFoundException) {
1330           logger.log(Level.SEVERE, " PDB file not found", fileNotFoundException);
1331         }
1332       }
1333       xyzIndex--;
1334       setFileRead(true);
1335     } catch (IOException e) {
1336       logger.exiting(PDBFilter.class.getName(), "readFile", e);
1337       return false;
1338     }
1339 
1340     
1341     List<Bond> ssBondList = locateDisulfideBonds(ssbonds, activeMolecularAssembly, pdbToNewResMap);
1342 
1343     
1344     
1345     int pdbAtoms = activeMolecularAssembly.getAtomArray().length;
1346     removeExcessHydrogens();
1347     
1348     buildMissingResidues(xyzIndex, activeMolecularAssembly, seqRes, dbRef);
1349 
1350     
1351     bondList = assignAtomTypes(activeMolecularAssembly, fileStandard);
1352 
1353     
1354     buildDisulfideBonds(ssBondList, activeMolecularAssembly, bondList);
1355 
1356     
1357     int currentN = activeMolecularAssembly.getAtomArray().length;
1358     boolean renumber = forceField.getBoolean("renumber-pdb", false);
1359     if (pdbAtoms != currentN) {
1360       logger.info(format(" Renumbering PDB file due to built atoms (%d vs %d)", currentN, pdbAtoms));
1361       numberAtoms(activeMolecularAssembly);
1362     } else if (renumber) {
1363       logger.info(" Renumbering PDB file due to renumber-pdb flag.");
1364       numberAtoms(activeMolecularAssembly);
1365     }
1366     return true;
1367   }
1368 
1369   public void removeExcessHydrogens(){
1370     logger.info(" Removing excess Hydrogens");
1371     for(Residue residue: activeMolecularAssembly.getResidueList()){
1372       if(residue.getName().equals("ACE") || residue.getName().equals("NME") || residue.getResidueType() != Residue.ResidueType.AA){
1373         break;
1374       }
1375       String trueResName = residue.getAtomByName("CA", true).getResidueName();
1376       Atom atom;
1377       switch (trueResName) {
1378         case "HID", "GLU" -> {
1379           
1380           atom = residue.getAtomByName("HE2", true);
1381         }
1382         case "HIE" -> {
1383           
1384           atom = residue.getAtomByName("HD1", true);
1385         }
1386         case "ASP" -> {
1387           
1388           atom = residue.getAtomByName("HD2", true);
1389         }
1390         case "LYD" -> {
1391           
1392           atom = residue.getAtomByName("HZ3", true);
1393         }
1394         case "CYD" -> {
1395           
1396           atom = residue.getAtomByName("HG", true);
1397         }
1398         default -> {
1399           atom = null;
1400         }
1401         
1402       }
1403       if(atom != null){
1404         int index = activeMolecularAssembly.getResidueList().indexOf(residue);
1405         MSNode atoms = residue.getAtomNode();
1406         atoms.remove(atom);
1407         residue.setName(trueResName);
1408         activeMolecularAssembly.getResidueList().set(index, residue);
1409       }
1410     }
1411   }
1412 
1413   
1414   @Override
1415   public boolean readNext() {
1416     return readNext(false);
1417   }
1418 
1419   
1420   @Override
1421   public boolean readNext(boolean resetPosition) {
1422     return readNext(resetPosition, false);
1423   }
1424 
1425   
1426   @Override
1427   public boolean readNext(boolean resetPosition, boolean print) {
1428     return readNext(resetPosition, print, true);
1429   }
1430 
1431   
1432   @Override
1433   @SuppressWarnings("fallthrough")
1434   public boolean readNext(boolean resetPosition, boolean print, boolean parse) {
1435     modelsRead = resetPosition ? 1 : modelsRead + 1;
1436     if (!parse) {
1437       if (print) {
1438         logger.info(format(" Skipped Model %d.", modelsRead));
1439       }
1440       return true;
1441     }
1442     remarkLines = new ArrayList<>(remarkLines.size());
1443     
1444     
1445     Pattern modelPatt = Pattern.compile("^MODEL\\s+(\\d+)");
1446     boolean eof = true;
1447     for (MolecularAssembly system : systems) {
1448       try {
1449         BufferedReader currentReader;
1450         if (readers.containsKey(system)) {
1451           currentReader = readers.get(system);
1452           try {
1453             if (!currentReader.ready()) {
1454               currentReader = new BufferedReader(new FileReader(readFile));
1455               
1456               currentReader.mark(0);
1457               readers.remove(system);
1458               readers.put(system, currentReader);
1459             } else if (resetPosition) {
1460               
1461               currentReader.reset();
1462             }
1463           } catch (Exception exception) {
1464             
1465             
1466             currentReader = new BufferedReader(new FileReader(readFile));
1467             
1468             currentReader.mark(0);
1469             readers.remove(system);
1470             readers.put(system, currentReader);
1471           }
1472         } else {
1473           currentReader = new BufferedReader(new FileReader(readFile));
1474           
1475           currentReader.mark(0);
1476           readers.put(system, currentReader);
1477         }
1478 
1479         
1480         String line = currentReader.readLine();
1481         while (line != null) {
1482           line = line.trim();
1483           Matcher m = modelPatt.matcher(line);
1484           if (m.find()) {
1485             int modelNum = parseInt(m.group(1));
1486             if (modelNum == modelsRead) {
1487               if (print) {
1488                 logger.log(Level.INFO, format(" Reading model %d for %s", modelNum, currentFile));
1489               }
1490               eof = false;
1491               break;
1492             }
1493           }
1494           line = currentReader.readLine();
1495         }
1496         if (eof) {
1497           if (logger.isLoggable(Level.FINEST)) {
1498             logger.log(Level.FINEST, format("\n End of file reached for %s", readFile));
1499           }
1500           currentReader.close();
1501           return false;
1502         }
1503 
1504         
1505         currentChainID = null;
1506         currentSegID = null;
1507         boolean modelDone = false;
1508         line = currentReader.readLine();
1509         while (line != null) {
1510           line = line.trim();
1511           String recID = line.substring(0, Math.min(6, line.length())).trim();
1512           try {
1513             Record record = Record.valueOf(recID);
1514             boolean hetatm = true;
1515             switch (record) {
1516               
1517               
1518               
1519               
1520               
1521               
1522               
1523               
1524               
1525               
1526               
1527               
1528               
1529               
1530               
1531               
1532               
1533               
1534               
1535               
1536               
1537               
1538               
1539               
1540               case ATOM:
1541                 hetatm = false;
1542               case HETATM:
1543                 String name = line.substring(12, 16).trim();
1544                 if (name.toUpperCase().contains("1H") || name.toUpperCase().contains("2H")
1545                         || name.toUpperCase().contains("3H")) {
1546                   
1547                   fileStandard = VERSION3_2;
1548                 }
1549                 Character altLoc = line.substring(16, 17).toUpperCase().charAt(0);
1550                 if (!altLoc.equals(' ') && !altLoc.equals(currentAltLoc)) {
1551                   break;
1552                 }
1553                 
1554                 
1555                 
1556                 String resName = line.substring(17, 20).trim();
1557                 Character chainID = line.substring(21, 22).charAt(0);
1558                 String segID = getExistingSegID(chainID);
1559                 int resSeq = Hybrid36.decode(4, line.substring(22, 26));
1560                 double[] d = new double[3];
1561                 d[0] = parseDouble(line.substring(30, 38).trim());
1562                 d[1] = parseDouble(line.substring(38, 46).trim());
1563                 d[2] = parseDouble(line.substring(46, 54).trim());
1564                 double occupancy = 1.0;
1565                 double tempFactor = 1.0;
1566                 Atom newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy,
1567                         tempFactor, segID);
1568                 newAtom.setHetero(hetatm);
1569                 
1570                 if (modRes.containsKey(resName.toUpperCase())) {
1571                   newAtom.setModRes(true);
1572                 }
1573 
1574                 Atom returnedAtom = activeMolecularAssembly.findAtom(newAtom);
1575                 if (returnedAtom != null) {
1576                   returnedAtom.setXYZ(d);
1577                   double[] retXYZ = new double[3];
1578                   returnedAtom.getXYZ(retXYZ);
1579                 } else {
1580                   String message = format(" Could not find atom %s in assembly", newAtom);
1581                   if (dieOnMissingAtom) {
1582                     logger.severe(message);
1583                   } else {
1584                     logger.warning(message);
1585                   }
1586                 }
1587                 break;
1588               case CRYST1:
1589                 
1590                 
1591                 
1592                 
1593                 
1594                 
1595                 
1596                 
1597                 
1598                 
1599                 
1600                 
1601                 
1602                 
1603                 logger.fine(" Crystal record found.");
1604                 if (line.length() < 55) {
1605                   logger.severe(" CRYST1 record is improperly formatted.");
1606                 }
1607                 double aaxis = parseDouble(line.substring(6, 15).trim());
1608                 double baxis = parseDouble(line.substring(15, 24).trim());
1609                 double caxis = parseDouble(line.substring(24, 33).trim());
1610                 double alpha = parseDouble(line.substring(33, 40).trim());
1611                 double beta = parseDouble(line.substring(40, 47).trim());
1612                 double gamma = parseDouble(line.substring(47, 54).trim());
1613                 int limit = min(line.length(), 66);
1614                 String sg = line.substring(55, limit).trim();
1615 
1616 
1617 
1618 
1619 
1620 
1621 
1622 
1623 
1624 
1625 
1626 
1627 
1628 
1629 
1630                 Crystal crystal = activeMolecularAssembly.getCrystal();
1631                 SpaceGroup spaceGroup = SpaceGroupDefinitions.spaceGroupFactory(sg);
1632                 if (Objects.equals(crystal.spaceGroup.shortName, spaceGroup.shortName)) {
1633                   crystal.changeUnitCellParameters(aaxis, baxis, caxis, alpha, beta, gamma);
1634                 } else {
1635                   
1636                   logger.warning(format(" Original space group %s could not be changed to %s",
1637                           crystal.spaceGroup.shortName, spaceGroup.shortName));
1638                 }
1639                 break;
1640               case ENDMDL:
1641               case END: 
1642                 
1643                 logger.log(Level.FINE, format(" Model %d successfully read", modelsRead));
1644                 modelDone = true;
1645                 break;
1646               case REMARK:
1647                 remarkLines.add(line.trim());
1648                 if (line.contains("Lambda:")) {
1649                   Matcher m = lambdaPattern.matcher(line);
1650                   if (m.find()) {
1651                     lastReadLambda = Double.parseDouble(m.group(1));
1652                   }
1653                 }
1654                 break;
1655               default:
1656                 break;
1657             }
1658           } catch (Exception ex) {
1659             
1660           }
1661           if (modelDone) {
1662             break;
1663           }
1664           line = currentReader.readLine();
1665         }
1666         return true;
1667       } catch (IOException ex) {
1668         logger.info(
1669                 format(" Exception in parsing frame %d of %s:" + " %s", modelsRead, system.toString(),
1670                         ex));
1671       }
1672     }
1673     return false;
1674   }
1675 
1676   
1677 
1678 
1679 
1680 
1681 
1682   public void setAltID(MolecularAssembly molecularAssembly, Character altLoc) {
1683     setMolecularSystem(molecularAssembly);
1684     currentAltLoc = altLoc;
1685   }
1686 
1687   
1688 
1689 
1690 
1691 
1692   public void setLogWrites(boolean logWrites) {
1693     this.logWrites = logWrites;
1694   }
1695 
1696   
1697 
1698 
1699 
1700 
1701   public void setModelNumbering(int modelsWritten) {
1702     this.modelsWritten = modelsWritten;
1703   }
1704 
1705   public void setLMN(int[] lmn) {
1706     if(lmn[0] >= 1 && lmn[1] >= 1 && lmn[2] >= 1){
1707       this.lmn = lmn;
1708     }else{
1709       
1710       this.lmn = new int[]{1,1,1};
1711     }
1712   }
1713 
1714   
1715 
1716 
1717 
1718 
1719   public void setSymOp(int symOp) {
1720     this.nSymOp = symOp;
1721   }
1722 
1723   
1724 
1725 
1726 
1727 
1728 
1729   public boolean writeFileAsP1(File file) {
1730     
1731     final int l = lmn[0];
1732     final int m = lmn[1];
1733     final int n = lmn[2];
1734     final int numReplicates = l * m * n;
1735     Crystal crystal = activeMolecularAssembly.getCrystal();
1736     int nSymOps = crystal.getUnitCell().spaceGroup.getNumberOfSymOps();
1737 
1738     if (nSymOps == 1 && l <= 1 && m <= 1 && n <= 1) {
1739       
1740       if (!writeFile(file, false)) {
1741         logger.info(format(" Save failed for %s", activeMolecularAssembly));
1742         return false;
1743       } else {
1744         return true;
1745       }
1746     } else {
1747       Polymer[] polymers = activeMolecularAssembly.getChains();
1748       int chainCount = 0;
1749       for (Polymer polymer : polymers) {
1750         Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1751         polymer.setChainID(chainID);
1752         polymer.setSegID(chainID.toString());
1753       }
1754       nSymOp = 0;
1755       logWrites = false;
1756       boolean writeEnd = false;
1757       if (!writeFile(file, false, false, writeEnd)) {
1758         logger.info(format(" Save failed for %s", activeMolecularAssembly));
1759         return false;
1760       } else {
1761         for (int i = 0; i < l; i++) {
1762           for (int j = 0; j < m; j++) {
1763             for (int k = 0; k < n; k++) {
1764               lValue = i;
1765               mValue = j;
1766               nValue = k;
1767               for (int iSym = 0; iSym < nSymOps; iSym++) {
1768                 nSymOp = iSym;
1769                 for (Polymer polymer : polymers) {
1770                   Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1771                   polymer.setChainID(chainID);
1772                   polymer.setSegID(chainID.toString());
1773                 }
1774                 
1775                 writeEnd = iSym == nSymOps - 1 && i == l - 1 && j == m - 1 && k == n - 1;
1776                 if (!writeFile(file, true, false, writeEnd)) {
1777                   logger.info(format(" Save failed for %s", activeMolecularAssembly));
1778                   return false;
1779                 }
1780               }
1781             }
1782           }
1783         }
1784       }
1785 
1786       
1787       chainCount = 0;
1788       for (Polymer polymer : polymers) {
1789         Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1790         polymer.setChainID(chainID);
1791         polymer.setSegID(chainID.toString());
1792       }
1793 
1794     }
1795 
1796     return true;
1797   }
1798 
1799   
1800 
1801 
1802 
1803 
1804 
1805 
1806 
1807 
1808   public boolean writeFile(File saveFile, boolean append, boolean printLinear, boolean writeEnd) {
1809     return writeFile(saveFile, append, Collections.emptySet(), writeEnd, true);
1810   }
1811 
1812   
1813 
1814 
1815 
1816 
1817 
1818 
1819 
1820 
1821 
1822 
1823 
1824   public boolean writeFile(File saveFile, boolean append, Set<Atom> toExclude, boolean writeEnd,
1825                            boolean versioning) {
1826     return writeFile(saveFile, append, toExclude, writeEnd, versioning, null);
1827   }
1828 
1829   
1830 
1831 
1832 
1833 
1834 
1835 
1836 
1837 
1838 
1839 
1840 
1841 
1842   public boolean writeFile(File saveFile, boolean append, Set<Atom> toExclude, boolean writeEnd,
1843                            boolean versioning, String[] extraLines) {
1844     
1845     List<Atom> deuteriumAtoms = new ArrayList<>();
1846     for(Atom atom: activeMolecularAssembly.getAtomArray()){
1847       if(atom.getName().startsWith("D")){
1848         String name = atom.getName().replace("D","H");
1849         atom.setName(name);
1850         deuteriumAtoms.add(atom);
1851       }
1852     }
1853     if (standardizeAtomNames) {
1854       logger.info(" Setting atom names to PDB standard.");
1855       renameAtomsToPDBStandard(activeMolecularAssembly);
1856     }
1857 
1858     for(Atom atom: activeMolecularAssembly.getAtomArray()){
1859       if(deuteriumAtoms.contains(atom) && atom.getName().startsWith("H")){
1860         String name = atom.getName().replace("H","D");
1861         atom.setName(name);
1862       }
1863     }
1864     final Set<Atom> atomExclusions = toExclude == null ? Collections.emptySet() : toExclude;
1865     if (saveFile == null) {
1866       return false;
1867     }
1868     if (vdwH) {
1869       logger.info(" Saving hydrogen to van der Waals centers instead of nuclear locations.");
1870     }
1871     if (nSymOp > -1) {
1872       logger.info(format(" Saving atoms using the symmetry operator:\n%s\n",
1873               activeMolecularAssembly.getCrystal().getUnitCell().spaceGroup.getSymOp(nSymOp)
1874                       .toString()));
1875     }
1876 
1877     
1878     StringBuilder sb = new StringBuilder("ATOM  ");
1879     StringBuilder anisouSB = new StringBuilder("ANISOU");
1880     StringBuilder terSB = new StringBuilder("TER   ");
1881     StringBuilder model = null;
1882     for (int i = 6; i < 80; i++) {
1883       sb.append(' ');
1884       anisouSB.append(' ');
1885       terSB.append(' ');
1886     }
1887 
1888     File newFile = saveFile;
1889     if (!append) {
1890       if (versioning) {
1891         newFile = version(saveFile);
1892       }
1893     } else if (modelsWritten >= 0) {
1894       model = new StringBuilder(format("MODEL     %-4d", ++modelsWritten));
1895       model.append(repeat(" ", 65));
1896     }
1897     activeMolecularAssembly.setFile(newFile);
1898     if (activeMolecularAssembly.getName() == null) {
1899       activeMolecularAssembly.setName(newFile.getName());
1900     }
1901     if (logWrites) {
1902       logger.log(Level.INFO, " Saving {0}", newFile.getName());
1903     }
1904 
1905     try (FileWriter fw = new FileWriter(newFile, append);
1906          BufferedWriter bw = new BufferedWriter(fw)) {
1907       
1908 
1909 
1910 
1911       String[] headerLines = activeMolecularAssembly.getHeaderLines();
1912       for (String line : headerLines) {
1913         bw.write(format("%s\n", line));
1914       }
1915       if (extraLines != null) {
1916         if (rotamerTitration && extraLines[0].contains("REMARK")) {
1917           for (String line : extraLines) {
1918             bw.write(line + "\n");
1919           }
1920         } else {
1921           for (String line : extraLines) {
1922             bw.write(format("REMARK 999 %s\n", line));
1923           }
1924         }
1925       }
1926       if (model != null) {
1927         bw.write(model.toString());
1928         bw.newLine();
1929       }
1930       
1931       
1932       
1933       
1934       
1935       
1936       
1937       
1938       
1939       
1940       
1941       
1942       
1943       
1944       if (nSymOp < 0) {
1945         
1946         Crystal crystal = activeMolecularAssembly.getCrystal();
1947         if (crystal != null && !crystal.aperiodic()) {
1948           Crystal c = crystal.getUnitCell();
1949           if (lmn[0] > 0 || lmn[1] > 0 || lmn[2] > 0) {
1950             c.a = c.a * lmn[0];
1951             c.b = c.b * lmn[1];
1952             c.c = c.c * lmn[2];
1953           }
1954           bw.write(c.toCRYST1());
1955         }
1956       } else if (nSymOp == 0) {
1957         
1958         Crystal crystal = activeMolecularAssembly.getCrystal();
1959         if (crystal != null && !crystal.aperiodic()) {
1960           Crystal c = crystal.getUnitCell();
1961           Crystal p1 = new Crystal((lmn[0]>0)? c.a * lmn[0] : c.a, (lmn[1]>0)? c.b * lmn[1] : c.b, (lmn[2]>0)? c.c * lmn[2] : c.c, c.alpha, c.beta, c.gamma, "P1");
1962           bw.write(p1.toCRYST1());
1963         }
1964       }
1965       
1966       
1967       
1968       
1969       
1970       
1971       
1972       
1973       
1974       
1975       
1976       
1977       
1978       
1979       
1980       
1981       
1982       
1983       
1984       
1985       
1986       
1987       
1988       
1989       int serNum = 1;
1990       Polymer[] polymers = activeMolecularAssembly.getChains();
1991       if (polymers != null) {
1992         for (Polymer polymer : polymers) {
1993           List<Residue> residues = polymer.getResidues();
1994           for (Residue residue : residues) {
1995             if (residue.getName().equalsIgnoreCase("CYS")) {
1996               List<Atom> cysAtoms = residue.getAtomList().stream()
1997                       .filter(a -> !atomExclusions.contains(a)).toList();
1998               Atom SG1 = null;
1999               for (Atom atom : cysAtoms) {
2000                 String atName = atom.getName().toUpperCase();
2001                 if (atName.equals("SG") || atName.equals("SH")
2002                         || atom.getAtomType().atomicNumber == 16) {
2003                   SG1 = atom;
2004                   break;
2005                 }
2006               }
2007               List<Bond> bonds = SG1.getBonds();
2008               for (Bond bond : bonds) {
2009                 Atom SG2 = bond.get1_2(SG1);
2010                 if (SG2.getAtomType().atomicNumber == 16 && !atomExclusions.contains(SG2)) {
2011                   if (SG1.getIndex() < SG2.getIndex()) {
2012                     bond.energy(false);
2013                     bw.write(format("SSBOND %3d CYS %1s %4s    CYS %1s %4s %36s %5.2f\n", serNum++,
2014                             SG1.getChainID().toString(), Hybrid36.encode(4, SG1.getResidueNumber()),
2015                             SG2.getChainID().toString(), Hybrid36.encode(4, SG2.getResidueNumber()), "",
2016                             bond.getValue()));
2017                   }
2018                 }
2019               }
2020             }
2021           }
2022         }
2023       }
2024       
2025       
2026       
2027       
2028       
2029       
2030       
2031       
2032       
2033       
2034       
2035       
2036       
2037       
2038       
2039       
2040       
2041       
2042       
2043       
2044       
2045       MolecularAssembly[] molecularAssemblies = this.getMolecularAssemblyArray();
2046       int serial = 1;
2047       if (nSymOp > 0) {
2048         serial = serialP1;
2049       }
2050 
2051       
2052       if (polymers != null) {
2053         for (Polymer polymer : polymers) {
2054           currentSegID = polymer.getName();
2055           currentChainID = polymer.getChainID();
2056           sb.setCharAt(21, currentChainID);
2057           
2058           List<Residue> residues = polymer.getResidues();
2059           for (Residue residue : residues) {
2060             String resName = residue.getName();
2061             if (resName.length() > 3) {
2062               resName = resName.substring(0, 3);
2063             }
2064             int resID = residue.getResidueNumber();
2065             sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2066             sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2067             
2068             List<Atom> residueAtoms = residue.getAtomList().stream()
2069                     .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2070             boolean altLocFound = false;
2071             for (Atom atom : residueAtoms) {
2072               if (mutate) {
2073                 for (Mutation mtn : mutations) {
2074                   if (resID == mtn.resID && currentChainID == mtn.chainChar) {
2075                     mtn.addTors(atom, serial);
2076                     ArrayList<String> alchAtoms = mtn.getAlchemicalAtoms(true);
2077                     if (alchAtoms != null) {
2078                       if (residue.getBackboneAtoms().contains(atom) && alchAtoms.contains(atom.getName())) {
2079                         logger.info(format(" MUTATION atom is %d chain %s",serial, currentChainID));
2080                       }
2081                     } else {
2082                       
2083                       if (residue.getBackboneAtoms().contains(atom)) {
2084                         logger.info(format(" MUTATION atom is %d chain %s",serial, currentChainID));
2085                       }
2086                     }
2087                   }
2088                 }
2089               }
2090               writeAtom(atom, serial++, sb, anisouSB, bw);
2091               Character altLoc = atom.getAltLoc();
2092               if (altLoc != null && !altLoc.equals(' ')) {
2093                 altLocFound = true;
2094               }
2095             }
2096             
2097             if (altLocFound) {
2098               for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2099                 MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2100                 Polymer altPolymer = altMolecularAssembly.getPolymer(currentChainID, currentSegID, false);
2101                 Residue altResidue = altPolymer.getResidue(resName, resID, false, Residue.ResidueType.AA);
2102                 if (altResidue == null) {
2103                   resName = AminoAcid3.UNK.name();
2104                   altResidue = altPolymer.getResidue(resName, resID, false, Residue.ResidueType.AA);
2105                 }
2106                 residueAtoms = altResidue.getAtomList().stream()
2107                         .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2108                 for (Atom atom : residueAtoms) {
2109                   if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2110                           .equals('A')) {
2111                     sb.replace(17, 20, padLeft(atom.getResidueName().toUpperCase(), 3));
2112                     writeAtom(atom, serial++, sb, anisouSB, bw);
2113                   }
2114                 }
2115               }
2116             }
2117           }
2118           terSB.replace(6, 11, format("%5s", Hybrid36.encode(5, serial++)));
2119           terSB.replace(12, 16, "    ");
2120           terSB.replace(16, 26, sb.substring(16, 26));
2121           bw.write(terSB.toString());
2122           bw.newLine();
2123         }
2124       }
2125       sb.replace(0, 6, "HETATM");
2126       sb.setCharAt(21, 'A');
2127 
2128       Character chainID = 'A';
2129       if (polymers != null) {
2130         chainID = polymers[0].getChainID();
2131       }
2132       activeMolecularAssembly.setChainIDAndRenumberMolecules(chainID);
2133 
2134       
2135       List<MSNode> molecules = activeMolecularAssembly.getMolecules();
2136       int numMolecules = molecules.size();
2137       for (int i = 0; i < numMolecules; i++) {
2138         Molecule molecule = (Molecule) molecules.get(i);
2139         chainID = molecule.getChainID();
2140         sb.setCharAt(21, chainID);
2141         String resName = molecule.getResidueName();
2142         int resID = molecule.getResidueNumber();
2143         if (resName.length() > 3) {
2144           resName = resName.substring(0, 3);
2145         }
2146         sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2147         sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2148         
2149         List<Atom> moleculeAtoms = molecule.getAtomList().stream()
2150                 .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2151         boolean altLocFound = false;
2152         for (Atom atom : moleculeAtoms) {
2153           writeAtom(atom, serial++, sb, anisouSB, bw);
2154           Character altLoc = atom.getAltLoc();
2155           if (altLoc != null && !altLoc.equals(' ')) {
2156             altLocFound = true;
2157           }
2158         }
2159         
2160         if (altLocFound) {
2161           for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2162             MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2163             MSNode altmolecule = altMolecularAssembly.getMolecules().get(i);
2164             moleculeAtoms = altmolecule.getAtomList();
2165             for (Atom atom : moleculeAtoms) {
2166               if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2167                       .equals('A')) {
2168                 writeAtom(atom, serial++, sb, anisouSB, bw);
2169               }
2170             }
2171           }
2172         }
2173       }
2174 
2175       List<MSNode> ions = activeMolecularAssembly.getIons();
2176       for (int i = 0; i < ions.size(); i++) {
2177         Molecule ion = (Molecule) ions.get(i);
2178         chainID = ion.getChainID();
2179         sb.setCharAt(21, chainID);
2180         String resName = ion.getResidueName();
2181         int resID = ion.getResidueNumber();
2182         if (resName.length() > 3) {
2183           resName = resName.substring(0, 3);
2184         }
2185         sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2186         sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2187         
2188         List<Atom> ionAtoms = ion.getAtomList().stream().filter(a -> !atomExclusions.contains(a))
2189                 .collect(Collectors.toList());
2190         boolean altLocFound = false;
2191         for (Atom atom : ionAtoms) {
2192           writeAtom(atom, serial++, sb, anisouSB, bw);
2193           Character altLoc = atom.getAltLoc();
2194           if (altLoc != null && !altLoc.equals(' ')) {
2195             altLocFound = true;
2196           }
2197         }
2198         
2199         if (altLocFound) {
2200           for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2201             MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2202             MSNode altion = altMolecularAssembly.getIons().get(i);
2203             ionAtoms = altion.getAtomList();
2204             for (Atom atom : ionAtoms) {
2205               if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2206                       .equals('A')) {
2207                 writeAtom(atom, serial++, sb, anisouSB, bw);
2208               }
2209             }
2210           }
2211         }
2212       }
2213 
2214       List<MSNode> water = activeMolecularAssembly.getWater();
2215       for (int i = 0; i < water.size(); i++) {
2216         Molecule wat = (Molecule) water.get(i);
2217         chainID = wat.getChainID();
2218         sb.setCharAt(21, chainID);
2219         String resName = wat.getResidueName();
2220         int resID = wat.getResidueNumber();
2221         if (resName.length() > 3) {
2222           resName = resName.substring(0, 3);
2223         }
2224         sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2225         sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2226         List<Atom> waterAtoms = wat.getAtomList().stream().filter(a -> !atomExclusions.contains(a))
2227                 .collect(Collectors.toList());
2228         boolean altLocFound = false;
2229         for (Atom atom : waterAtoms) {
2230           writeAtom(atom, serial++, sb, anisouSB, bw);
2231           Character altLoc = atom.getAltLoc();
2232           if (altLoc != null && !altLoc.equals(' ')) {
2233             altLocFound = true;
2234           }
2235         }
2236         
2237         if (altLocFound) {
2238           for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2239             MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2240             MSNode altwater = altMolecularAssembly.getWater().get(i);
2241             waterAtoms = altwater.getAtomList();
2242             for (Atom atom : waterAtoms) {
2243               if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2244                       .equals('A')) {
2245                 writeAtom(atom, serial++, sb, anisouSB, bw);
2246               }
2247             }
2248           }
2249         }
2250       }
2251 
2252       if (model != null) {
2253         bw.write("ENDMDL");
2254         bw.newLine();
2255       }
2256 
2257       if (writeEnd) {
2258         bw.write("END");
2259         bw.newLine();
2260       }
2261 
2262       if (nSymOp >= 0) {
2263         serialP1 = serial;
2264       }
2265 
2266     } catch (Exception e) {
2267       String message = "Exception writing to file: " + saveFile;
2268       logger.log(Level.WARNING, message, e);
2269       return false;
2270     }
2271     return true;
2272   }
2273 
2274   
2275 
2276 
2277 
2278 
2279   @Override
2280   public boolean writeFile(File saveFile, boolean append) {
2281     return writeFile(saveFile, append, false, true);
2282   }
2283 
2284   public boolean writeFile(File saveFile, boolean append, String[] extraLines) {
2285     return writeFile(saveFile, append, Collections.emptySet(), false, !append, extraLines);
2286   }
2287 
2288   
2289 
2290 
2291 
2292 
2293 
2294 
2295 
2296 
2297 
2298   public boolean writeFile(File saveFile, boolean append, boolean versioning) {
2299     return writeFile(saveFile, append, Collections.emptySet(), true, versioning);
2300   }
2301 
2302   
2303 
2304 
2305 
2306 
2307 
2308 
2309 
2310   public boolean writeFileWithHeader(File saveFile, String header, boolean append) {
2311     
2312     List<Atom> deuteriumAtoms = new ArrayList<>();
2313     for(Atom atom: activeMolecularAssembly.getAtomArray()){
2314       if(atom.getName().startsWith("D")){
2315         String name = atom.getName().replace("D","H");
2316         atom.setName(name);
2317         deuteriumAtoms.add(atom);
2318       }
2319     }
2320     if (standardizeAtomNames) {
2321       logger.info(" Setting atom names to PDB standard.");
2322       renameAtomsToPDBStandard(activeMolecularAssembly);
2323     }
2324 
2325     for(Atom atom: activeMolecularAssembly.getAtomArray()){
2326       if(deuteriumAtoms.contains(atom) && atom.getName().startsWith("H")){
2327         String name = atom.getName().replace("H","D");
2328         atom.setName(name);
2329       }
2330     }
2331     activeMolecularAssembly.setFile(saveFile);
2332     activeMolecularAssembly.setName(saveFile.getName());
2333 
2334     try (FileWriter fw = new FileWriter(saveFile, append); BufferedWriter bw = new BufferedWriter(
2335             fw)) {
2336       bw.write(header);
2337       bw.newLine();
2338     } catch (Exception e) {
2339       String message = " Exception writing to file: " + saveFile;
2340       logger.log(Level.WARNING, message, e);
2341       return false;
2342     }
2343     if (writeFile(saveFile, true)) {
2344       logger.log(Level.INFO, " Wrote PDB to file {0}", saveFile.getPath());
2345       return true;
2346     } else {
2347       logger.log(Level.INFO, " Error writing to file {0}", saveFile.getPath());
2348       return false;
2349     }
2350   }
2351 
2352   
2353 
2354 
2355 
2356 
2357 
2358 
2359   public boolean writeFileWithHeader(File saveFile, String header) {
2360     return writeFileWithHeader(saveFile, header, true);
2361   }
2362 
2363   
2364 
2365 
2366 
2367 
2368 
2369 
2370   public boolean writeFileWithHeader(File saveFile, StringBuilder header) {
2371     return writeFileWithHeader(saveFile, header.toString());
2372   }
2373 
2374   
2375 
2376 
2377 
2378 
2379 
2380   private String getExistingSegID(Character c) {
2381     if (c.equals(' ')) {
2382       c = 'A';
2383     }
2384 
2385     
2386     if (c.equals(currentChainID)) {
2387       return currentSegID;
2388     } else {
2389       currentChainID = null;
2390     }
2391 
2392     List<String> segIDs = segidMap.get(c);
2393     if (segIDs != null) {
2394       if (segIDs.size() > 1) {
2395         if (currentSegID == null) {
2396           currentChainID = c;
2397           currentSegID = segIDs.get(0);
2398           return segIDs.get(0);
2399         } else if (currentSegID.length() == 1) {
2400           currentChainID = c;
2401           currentSegID = segIDs.get(1);
2402           return segIDs.get(1);
2403         } else if (currentSegID.length() == 2) {
2404           String s = currentSegID.substring(0,1);
2405           int num = -2;
2406           try {
2407             num = Integer.parseInt(s);
2408           } catch (NumberFormatException e) {
2409             logger.severe(" SegID of length 2 does not start with an integer.");
2410           }
2411           currentChainID = c;
2412           currentSegID = segIDs.get(num+1);
2413           return segIDs.get(num+1);
2414         } else {
2415           logger.info(" Too many repeated chains. Using single letter for segID.");
2416         }
2417       }
2418       return segIDs.get(0);
2419     } else {
2420       logger.log(Level.INFO, format(" Creating SegID for to chain %s", c));
2421       return getSegID(c);
2422     }
2423   }
2424 
2425   
2426 
2427 
2428 
2429 
2430 
2431   private String getSegID(Character c) {
2432     if (c.equals(' ')) {
2433       c = 'A';
2434     }
2435 
2436     
2437     if (c.equals(currentChainID)) {
2438       return currentSegID;
2439     }
2440 
2441     
2442     int count = 0;
2443     for (String segID : segIDs) {
2444       if (segID.endsWith(c.toString())) {
2445         count++;
2446       }
2447     }
2448 
2449     
2450     String newSegID;
2451     if (count == 0) {
2452       newSegID = c.toString();
2453     } else {
2454       newSegID = count + c.toString();
2455     }
2456     segIDs.add(newSegID);
2457     currentChainID = c;
2458     currentSegID = newSegID;
2459 
2460     if (segidMap.containsKey(c)) {
2461       segidMap.get(c).add(newSegID);
2462     } else {
2463       List<String> newChainList = new ArrayList<>();
2464       newChainList.add(newSegID);
2465       segidMap.put(c, newChainList);
2466     }
2467 
2468     return newSegID;
2469   }
2470 
2471   
2472 
2473 
2474 
2475 
2476 
2477 
2478 
2479 
2480 
2481   private void writeAtom(Atom atom, int serial, StringBuilder sb, StringBuilder anisouSB,
2482                          BufferedWriter bw) throws IOException {
2483     String name = atom.getName();
2484     int nameLength = name.length();
2485     if (nameLength > 4) {
2486       name = name.substring(0, 4);
2487     } else if (nameLength == 1) {
2488       name = name + "  ";
2489     } else if (nameLength == 2) {
2490       if (atom.getAtomType().valence == 0) {
2491         name = name + "  ";
2492       } else {
2493         name = name + " ";
2494       }
2495     }
2496     double[] xyz = vdwH ? atom.getRedXYZ() : atom.getXYZ(null);
2497     if (nSymOp >= 0) {
2498       Crystal crystal = activeMolecularAssembly.getCrystal().getUnitCell();
2499       SymOp symOp = crystal.spaceGroup.getSymOp(nSymOp);
2500       double[] newXYZ = new double[xyz.length];
2501       crystal.applySymOp(xyz, newXYZ, symOp);
2502       if (lValue > 0 || mValue > 0 || nValue > 0) {
2503         double[] translation = new double[] {lValue, mValue, nValue};
2504         crystal.getUnitCell().toCartesianCoordinates(translation, translation);
2505         newXYZ[0] += translation[0];
2506         newXYZ[1] += translation[1];
2507         newXYZ[2] += translation[2];
2508       }
2509       xyz = newXYZ;
2510     }
2511     sb.replace(6, 16, format("%5s " + padLeft(name.toUpperCase(), 4), Hybrid36.encode(5, serial)));
2512     Character altLoc = atom.getAltLoc();
2513     sb.setCharAt(16, Objects.requireNonNullElse(altLoc, ' '));
2514 
2515 
2516     
2517 
2518 
2519 
2520 
2521 
2522 
2523 
2524 
2525 
2526 
2527 
2528     StringBuilder decimals = new StringBuilder();
2529     for (int i = 0; i < 3; i++) {
2530       try {
2531         decimals.append(StringUtils.fwFpDec(xyz[i], 8, 3));
2532       } catch (IllegalArgumentException ex) {
2533         String newValue = StringUtils.fwFpTrunc(xyz[i], 8, 3);
2534         logger.info(format(" XYZ %d coordinate %8.3f for atom %s "
2535                 + "overflowed bounds of 8.3f string specified by PDB "
2536                 + "format; truncating value to %s", i, xyz[i], atom, newValue));
2537         decimals.append(newValue);
2538       }
2539     }
2540     try {
2541       decimals.append(StringUtils.fwFpDec(atom.getOccupancy(), 6, 2));
2542     } catch (IllegalArgumentException ex) {
2543       logger.severe(
2544               format(" Occupancy %f for atom %s is impossible; " + "value must be between 0 and 1",
2545                       atom.getOccupancy(), atom));
2546     }
2547     try {
2548       decimals.append(StringUtils.fwFpDec(atom.getTempFactor(), 6, 2));
2549     } catch (IllegalArgumentException ex) {
2550       String newValue = StringUtils.fwFpTrunc(atom.getTempFactor(), 6, 2);
2551       logger.info(format(" Atom temp factor %6.2f for atom %s overflowed "
2552                       + "bounds of 6.2f string specified by PDB format; truncating " + "value to %s",
2553               atom.getTempFactor(), atom, newValue));
2554       decimals.append(newValue);
2555     }
2556     sb.replace(30, 66, decimals.toString());
2557 
2558     name = Atom.ElementSymbol.values()[atom.getAtomicNumber() - 1].toString();
2559     name = name.toUpperCase();
2560     if (atom.isDeuterium()) {
2561       name = "D";
2562     }
2563     sb.replace(76, 78, padLeft(name, 2));
2564     sb.replace(78, 80, format("%2d", 0));
2565     bw.write(sb.toString());
2566     bw.newLine();
2567     
2568     
2569     
2570     
2571     
2572     
2573     
2574     
2575     
2576     
2577     
2578     
2579     
2580     
2581     
2582     
2583     
2584     
2585     double[] anisou = atom.getAnisou(null);
2586     if (anisou != null) {
2587       anisouSB.replace(6, 80, sb.substring(6, 80));
2588       anisouSB.replace(28, 70,
2589               format("%7d%7d%7d%7d%7d%7d", (int) (anisou[0] * 1e4), (int) (anisou[1] * 1e4),
2590                       (int) (anisou[2] * 1e4), (int) (anisou[3] * 1e4), (int) (anisou[4] * 1e4),
2591                       (int) (anisou[5] * 1e4)));
2592       bw.write(anisouSB.toString());
2593       bw.newLine();
2594     }
2595   }
2596 
2597   
2598   private enum Record {
2599     ANISOU, ATOM, CONECT, CRYST1, DBREF, END, MODEL, ENDMDL, HELIX, HETATM, LINK, MTRIX1, MTRIX2, MTRIX3, MODRES, SEQRES, SHEET, SSBOND, REMARK
2600   }
2601 
2602   
2603   public enum PDBFileStandard {
2604     VERSION2_3, VERSION3_0, VERSION3_1, VERSION3_2, VERSION3_3
2605   }
2606 
2607   public static class Mutation {
2608 
2609     
2610     final int resID;
2611     
2612     final String resName;
2613     
2614     final char chainChar;
2615     
2616     String origResName;
2617     
2618     double[][] glyco = new double[4][3];
2619     
2620     int[] glycoAtomIndex = new int[4];
2621     
2622     int indCorr;
2623 
2624     public Mutation(int resID, char chainChar, String newResName) {
2625       newResName = newResName.toUpperCase();
2626       if (newResName.length() != 3) {
2627         logger.log(Level.WARNING, format("Invalid mutation target: %s.", newResName));
2628       }
2629       int isAA = AminoAcidUtils.getAminoAcidNumber(newResName);
2630       int isNA = NucleicAcidUtils.getNucleicAcidNumber(newResName);
2631       if (isAA == -1 && isNA == -1) {
2632         logger.log(Level.WARNING, format("Invalid mutation target: %s.", newResName));
2633       }
2634       this.resID = resID;
2635       this.chainChar = chainChar;
2636       this.resName = newResName;
2637 
2638       String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
2639       indCorr = alphabet.indexOf(chainChar);
2640     }
2641 
2642     
2643 
2644 
2645 
2646 
2647 
2648 
2649     public String isNonAlchemicalAtom(String atomName) {
2650       if (isWtPurine()) {
2651         if (atomName.equals("N9")) {
2652           if (isMtnPyrimidine()) {
2653             return "~N1";
2654           }
2655           return atomName;
2656         } else if (atomName.equals("C4")) {
2657           if (isMtnPyrimidine()) {
2658             return "~C2";
2659           }
2660           return atomName;
2661         }
2662         return null;
2663       }
2664 
2665       if (isWtPyrimidine()) {
2666         if (atomName.equals("N1")) {
2667           if (isMtnPurine()) {
2668             
2669             return "~N9";
2670           }
2671           return atomName;
2672         } else if (atomName.equals("C2")) {
2673           if (isMtnPurine()) {
2674             
2675             return "~C4";
2676           }
2677           return atomName;
2678         }
2679         return null;
2680       }
2681 
2682       return null;
2683     }
2684 
2685     
2686 
2687 
2688 
2689 
2690     public ArrayList<String> getAlchemicalAtoms(boolean isWriting) {
2691       
2692       if (resName.equals(origResName)) {
2693         logger.severe("Desired Mutation residue is the same as the original.");
2694         return null;
2695       }
2696 
2697       boolean purpur;
2698       if (isMtnPurine() && isWtPurine()) {
2699         purpur = true;
2700       } else if (isMtnPyrimidine() && isWtPyrimidine()) {
2701         purpur = false;
2702       } else {
2703         
2704         return null;
2705       }
2706 
2707       String res;
2708 
2709       
2710       if (isWriting) {
2711         res = resName;
2712       } else {
2713         res = origResName;
2714       }
2715 
2716       ArrayList<String> list = new ArrayList<>();
2717       if (purpur) { 
2718         if (res.equals("DAD") || res.equals("DA")) { 
2719           list.add("N6");
2720           list.add("H61");
2721           list.add("H62");
2722           list.add("H2");
2723         } else { 
2724           list.add("H1");
2725           list.add("N2");
2726           list.add("H21");
2727           list.add("H22");
2728           list.add("O6");
2729         }
2730       } else { 
2731         if (res.equals("DTY") || res.equals("DT")) { 
2732           list.add("H3");
2733           list.add("O4");
2734           list.add("C7");
2735           list.add("H71");
2736           list.add("H72");
2737           list.add("H73");
2738         } else { 
2739           list.add("N4");
2740           list.add("H41");
2741           list.add("H42");
2742           list.add("H5");
2743         }
2744       }
2745       return list;
2746     }
2747 
2748     
2749 
2750 
2751 
2752     public boolean isMtnPurine() {
2753       return resName.equals("DA") || resName.equals("DG") || resName.equals("DAD") || resName.equals("DGU");
2754     }
2755 
2756     
2757 
2758 
2759 
2760     public boolean isMtnPyrimidine() {
2761       return resName.equals("DC") || resName.equals("DT") || resName.equals("DCY") || resName.equals("DTY");
2762     }
2763 
2764     
2765 
2766 
2767 
2768     public boolean isWtPurine() {
2769       return origResName.equals("DA") || origResName.equals("DG") || origResName.equals("DAD") || origResName.equals("DGU");
2770     }
2771 
2772     
2773 
2774 
2775 
2776     public boolean isWtPyrimidine() {
2777       return origResName.equals("DT") || origResName.equals("DC") || origResName.equals("DTY") || origResName.equals("DCY");
2778     }
2779 
2780     public void addTors(Atom atom, int index) {
2781       String name = atom.getName();
2782       if (name.equals("O4'")) {
2783         atom.getXYZ(glyco[0]);
2784         glycoAtomIndex[0] = index - indCorr;
2785       } else if (name.equals("C1'")) {
2786         atom.getXYZ(glyco[1]);
2787         glycoAtomIndex[1] = index - indCorr;
2788       } else if (name.equals("N9") || (name.equals("N1") && isMtnPyrimidine())) {
2789         atom.getXYZ(glyco[2]);
2790         glycoAtomIndex[2] = index - indCorr;
2791       } else if ((name.equals("C4") && isMtnPurine()) || (name.equals("C2") && isMtnPyrimidine())) {
2792         atom.getXYZ(glyco[3]);
2793         glycoAtomIndex[3] = index - indCorr;
2794       }
2795     }
2796 
2797     public void calculateTorsion() {
2798       double tors = toDegrees(DoubleMath.dihedralAngle(glyco[0], glyco[1], glyco[2], glyco[3]));
2799       double delta;
2800       if (tors > 0) {
2801         delta = tors - 180.0;
2802       } else {
2803         delta = tors + 180.0;
2804       }
2805       logger.info(format("restrain-torsion %d %d %d %d  5.000  %f 1", glycoAtomIndex[0], glycoAtomIndex[1], glycoAtomIndex[2], glycoAtomIndex[3], delta));
2806     }
2807   }
2808 }