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