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
789 if (!altLoc.equals(' ') && !altLoc.equals('A') && !altLoc.equals(currentAltLoc)) {
790 break;
791 }
792
793 resName = line.substring(17, 20).trim();
794 chainID = line.substring(21, 22).charAt(0);
795 segID = getSegID(chainID);
796 resSeq = Hybrid36.decode(4, line.substring(22, 26));
797
798 char insertionCode = line.charAt(26);
799 if (insertionCode != ' ' && !containsInsCode) {
800 containsInsCode = true;
801 logger.warning(
802 " FFX support for files with " + "insertion codes is experimental. "
803 + "Residues will be renumbered to " + "eliminate insertion codes (52A "
804 + "becomes 53, 53 becomes 54, etc)");
805 }
806
807 int offset = insertionCodeCount.getOrDefault(chainID, 0);
808 String pdbResNum = format("%c%d%c", chainID, resSeq, insertionCode);
809 if (!pdbToNewResMap.containsKey(pdbResNum)) {
810 if (insertionCode != ' ') {
811 ++offset;
812 insertionCodeCount.put(chainID, offset);
813 }
814 resSeq += offset;
815 if (offset != 0) {
816 logger.info(
817 format(" Chain %c " + "residue %s-%s renumbered to %c %s-%d", chainID,
818 pdbResNum.substring(1).trim(), resName, chainID, resName, resSeq));
819 }
820 String newNum = format("%c%d", chainID, resSeq);
821 pdbToNewResMap.put(pdbResNum, newNum);
822 } else {
823 resSeq += offset;
824 }
825
826 printAtom = false;
827 if (mutate) {
828 boolean doBreak = false;
829 for (Mutation mtn : mutations) {
830 if (chainID == mtn.chainChar && resSeq == mtn.resID) {
831 mtn.origResName = resName;
832 resName = mtn.resName;
833 String atomName = name.toUpperCase();
834
835 int isAA = AminoAcidUtils.getAminoAcidNumber(resName);
836 int isNA = NucleicAcidUtils.getNucleicAcidNumber(resName);
837
838 if ((isNA != -1 && naBackboneNames.contains(atomName)) || (isAA != -1 && backboneNames.contains(atomName))) {
839 printAtom = true;
840 } else {
841
842 ArrayList<String> alchAtoms = mtn.getAlchemicalAtoms(false);
843 if (alchAtoms == null) {
844
845 String newName = mtn.isNonAlchemicalAtom(atomName);
846 if (newName != null) {
847 printAtom = true;
848 if (newName.startsWith("~")) {
849
850 name = newName.substring(1);
851 logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
852 } else {
853
854 name = newName;
855 }
856 doBreak = false;
857 } else if (!atomName.contains("'")) {
858 logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
859 doBreak = true;
860 } else {
861 printAtom = true;
862 doBreak = false;
863 }
864 } else {
865 if (alchAtoms.contains(atomName) && !atomName.contains("'")) {
866 logger.info(format(" DELETING atom %d %s of %s %d in chain %s", serial, atomName, resName, resSeq, chainID));
867 doBreak = true;
868 } else {
869 printAtom = true;
870 doBreak = false;
871 }
872 }
873 break;
874 }
875 }
876 }
877 if (doBreak) {
878 break;
879 }
880 }
881
882 if (constantPH) {
883 AminoAcid3 aa3 = AminoAcidUtils.getAminoAcid(resName.toUpperCase());
884 if (constantPHResidueMap.containsKey(aa3)) {
885 String atomName = name.toUpperCase();
886 AminoAcid3 aa3PH = constantPHResidueMap.get(aa3);
887 resName = aa3PH.name();
888 if (constantPhBackboneNames.contains(atomName)) {
889 logger.info(format(" %s-%d %s", resName, resSeq, atomName));
890 } else if (!atomName.startsWith("H")) {
891 logger.info(format(" %s-%d %s", resName, resSeq, atomName));
892 } else {
893 logger.info(format(" %s-%d %s skipped", resName, resSeq, atomName));
894 break;
895 }
896 }
897 } else if (rotamerTitration) {
898 AminoAcid3 aa3 = AminoAcidUtils.getAminoAcid(resName.toUpperCase());
899 if (rotamerResidueMap.containsKey(aa3) && resNumberList.contains(resSeq)) {
900 AminoAcid3 aa3rotamer = rotamerResidueMap.get(aa3);
901 resName = aa3rotamer.name();
902 }
903 }
904 d = new double[3];
905 d[0] = parseDouble(line.substring(30, 38).trim());
906 d[1] = parseDouble(line.substring(38, 46).trim());
907 d[2] = parseDouble(line.substring(46, 54).trim());
908 occupancy = 1.0;
909 tempFactor = 1.0;
910 try {
911 occupancy = parseDouble(line.substring(54, 60).trim());
912 tempFactor = parseDouble(line.substring(60, 66).trim());
913 } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
914
915 if (printMissingFields) {
916 logger.info(" Missing occupancy and b-factors set to 1.0.");
917 printMissingFields = false;
918 } else if (logger.isLoggable(Level.FINE)) {
919 logger.fine(" Missing occupancy and b-factors set to 1.0.");
920 }
921 }
922
923 double bfactor = properties.getDouble("reset-bfactors", -1.0);
924 if (bfactor >= 0.0) {
925 tempFactor = bfactor;
926 }
927
928 newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy, tempFactor, segID);
929
930
931 if (modRes.containsKey(resName.toUpperCase())) {
932 newAtom.setModRes(true);
933 }
934 returnedAtom = (Atom) activeMolecularAssembly.addMSNode(newAtom);
935 if (returnedAtom != newAtom) {
936
937 atoms.put(serial, returnedAtom);
938 if (logger.isLoggable(Level.FINE)) {
939 logger.fine(returnedAtom + " has been retained over\n" + newAtom);
940 }
941 } else {
942
943 atoms.put(serial, newAtom);
944
945 if (newAtom.getIndex() == 0) {
946 newAtom.setXyzIndex(xyzIndex++);
947 }
948 if (printAtom) {
949 logger.info(newAtom.toString());
950 }
951 }
952 break;
953 }
954 break;
955 case HETATM:
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973 serial = Hybrid36.decode(5, line.substring(6, 11));
974 name = line.substring(12, 16).trim();
975 altLoc = line.substring(16, 17).toUpperCase().charAt(0);
976 if (!altLocs.contains(altLoc)) {
977 altLocs.add(altLoc);
978 }
979 if (!altLoc.equals(' ') && !altLoc.equals(currentAltLoc)) {
980 break;
981 }
982
983
984
985 resName = line.substring(17, 20).trim();
986 chainID = line.substring(21, 22).charAt(0);
987 segID = getSegID(chainID);
988 resSeq = Hybrid36.decode(4, line.substring(22, 26));
989
990 char insertionCode = line.charAt(26);
991 if (insertionCode != ' ' && !containsInsCode) {
992 containsInsCode = true;
993 logger.warning(" FFX support for files with " + "insertion codes is experimental. "
994 + "Residues will be renumbered to " + "eliminate insertion codes (52A "
995 + "becomes 53, 53 becomes 54, etc)");
996 }
997
998 int offset = insertionCodeCount.getOrDefault(chainID, 0);
999 String pdbResNum = format("%c%d%c", chainID, resSeq, insertionCode);
1000 if (!pdbToNewResMap.containsKey(pdbResNum)) {
1001 if (insertionCode != ' ') {
1002 ++offset;
1003 insertionCodeCount.put(chainID, offset);
1004 }
1005 resSeq += offset;
1006 if (offset != 0) {
1007 logger.info(
1008 format(" Chain %c " + "molecule %s-%s renumbered to %c %s-%d", chainID,
1009 pdbResNum.substring(1).trim(), resName, chainID, resName, resSeq));
1010 }
1011 String newNum = format("%c%d", chainID, resSeq);
1012 pdbToNewResMap.put(pdbResNum, newNum);
1013 } else {
1014 resSeq += offset;
1015 }
1016
1017 d = new double[3];
1018 d[0] = parseDouble(line.substring(30, 38).trim());
1019 d[1] = parseDouble(line.substring(38, 46).trim());
1020 d[2] = parseDouble(line.substring(46, 54).trim());
1021 occupancy = 1.0;
1022 tempFactor = 1.0;
1023 try {
1024 occupancy = parseDouble(line.substring(54, 60).trim());
1025 tempFactor = parseDouble(line.substring(60, 66).trim());
1026 } catch (NumberFormatException | StringIndexOutOfBoundsException e) {
1027
1028 if (printMissingFields) {
1029 logger.info(" Missing occupancy and b-factors set to 1.0.");
1030 printMissingFields = false;
1031 } else if (logger.isLoggable(Level.FINE)) {
1032 logger.fine(" Missing occupancy and b-factors set to 1.0.");
1033 }
1034 }
1035
1036 double bfactor = properties.getDouble("reset-bfactors", -1.0);
1037 if (bfactor >= 0.0) {
1038 tempFactor = bfactor;
1039 }
1040
1041 newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy, tempFactor, segID);
1042 newAtom.setHetero(true);
1043
1044 if (modRes.containsKey(resName.toUpperCase())) {
1045 newAtom.setModRes(true);
1046 }
1047 returnedAtom = (Atom) activeMolecularAssembly.addMSNode(newAtom);
1048 if (returnedAtom != newAtom) {
1049
1050 atoms.put(serial, returnedAtom);
1051 if (logger.isLoggable(Level.FINE)) {
1052 logger.fine(returnedAtom + " has been retained over\n" + newAtom);
1053 }
1054 } else {
1055
1056 atoms.put(serial, newAtom);
1057 newAtom.setXyzIndex(xyzIndex++);
1058 }
1059 break;
1060 case CRYST1:
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075 if (line.length() < 55) {
1076 logger.severe(" CRYST1 record is improperly formatted.");
1077 }
1078 double aaxis = parseDouble(line.substring(6, 15).trim());
1079 double baxis = parseDouble(line.substring(15, 24).trim());
1080 double caxis = parseDouble(line.substring(24, 33).trim());
1081 double alpha = parseDouble(line.substring(33, 40).trim());
1082 double beta = parseDouble(line.substring(40, 47).trim());
1083 double gamma = parseDouble(line.substring(47, 54).trim());
1084 int limit = min(line.length(), 66);
1085 String sg = line.substring(55, limit).trim();
1086 properties.addProperty("a-axis", aaxis);
1087 properties.addProperty("b-axis", baxis);
1088 properties.addProperty("c-axis", caxis);
1089 properties.addProperty("alpha", alpha);
1090 properties.addProperty("beta", beta);
1091 properties.addProperty("gamma", gamma);
1092 properties.addProperty("spacegroup", SpaceGroupInfo.pdb2ShortName(sg));
1093 break;
1094 case CONECT:
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107 conects.add(line);
1108 break;
1109 case LINK:
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131 char a1 = line.charAt(16);
1132 char a2 = line.charAt(46);
1133 if (a1 != a2) {
1134
1135
1136 break;
1137 }
1138 if (currentAltLoc == 'A') {
1139 if ((a1 == ' ' || a1 == 'A') && (a2 == ' ' || a2 == 'A')) {
1140 links.add(line);
1141 }
1142 } else if (a1 == currentAltLoc && a2 == currentAltLoc) {
1143 links.add(line);
1144 }
1145 break;
1146 case SSBOND:
1147
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 ssbonds.add(line);
1176 break;
1177 case HELIX:
1178
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 case SHEET:
1217
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 structs.add(line);
1252 break;
1253 case MODEL:
1254 break;
1255 case MTRIX1:
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272 StringBuilder MTRX1 = new StringBuilder(line.substring(11, 55));
1273 properties.addProperty("MTRIX1", MTRX1);
1274 break;
1275 case MTRIX2:
1276 StringBuilder MTRX2 = new StringBuilder(line.substring(11, 55));
1277 properties.addProperty("MTRIX2", MTRX2);
1278 break;
1279 case MTRIX3:
1280 StringBuilder MTRX3 = new StringBuilder(line.substring(11, 55));
1281 properties.addProperty("MTRIX3", MTRX3);
1282 break;
1283 case REMARK:
1284 remarkLines.add(line.trim());
1285 if (line.contains("Lambda:")) {
1286 Matcher m = lambdaPattern.matcher(line);
1287 if (m.find()) {
1288 lastReadLambda = Double.parseDouble(m.group(1));
1289 }
1290 }
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305 if (line.length() >= 68) {
1306 String remarkType = line.substring(7, 10).trim();
1307 if (remarkType.matches("\\d+") && parseInt(remarkType) == 350 && line.substring(13,
1308 18).equalsIgnoreCase("BIOMT")) {
1309 properties.addProperty("BIOMTn", new StringBuilder(line.substring(24, 68)));
1310 }
1311 }
1312 break;
1313 default:
1314 break;
1315 }
1316 line = br.readLine();
1317 }
1318
1319 } catch (FileNotFoundException fileNotFoundException) {
1320 logger.log(Level.SEVERE, " PDB file not found", fileNotFoundException);
1321 }
1322 }
1323 xyzIndex--;
1324 setFileRead(true);
1325 } catch (IOException e) {
1326 logger.exiting(PDBFilter.class.getName(), "readFile", e);
1327 return false;
1328 }
1329
1330
1331 List<Bond> ssBondList = locateDisulfideBonds(ssbonds, activeMolecularAssembly, pdbToNewResMap);
1332
1333
1334
1335 int pdbAtoms = activeMolecularAssembly.getAtomArray().length;
1336 removeExcessHydrogens();
1337
1338 buildMissingResidues(xyzIndex, activeMolecularAssembly, seqRes, dbRef);
1339
1340
1341 bondList = assignAtomTypes(activeMolecularAssembly, fileStandard);
1342
1343
1344 buildDisulfideBonds(ssBondList, activeMolecularAssembly, bondList);
1345
1346
1347 int currentN = activeMolecularAssembly.getAtomArray().length;
1348 boolean renumber = forceField.getBoolean("renumber-pdb", false);
1349 if (pdbAtoms != currentN) {
1350 logger.info(format(" Renumbering PDB file due to built atoms (%d vs %d)", currentN, pdbAtoms));
1351 numberAtoms(activeMolecularAssembly);
1352 } else if (renumber) {
1353 logger.info(" Renumbering PDB file due to renumber-pdb flag.");
1354 numberAtoms(activeMolecularAssembly);
1355 }
1356 return true;
1357 }
1358
1359 public void removeExcessHydrogens(){
1360 logger.info(" Removing excess Hydrogens");
1361 for(Residue residue: activeMolecularAssembly.getResidueList()){
1362 if(residue.getName().equals("ACE") || residue.getName().equals("NME") || residue.getResidueType() != Residue.ResidueType.AA){
1363 break;
1364 }
1365 String trueResName = residue.getAtomByName("CA", true).getResidueName();
1366 Atom atom;
1367 switch (trueResName) {
1368 case "HID", "GLU" -> {
1369
1370 atom = residue.getAtomByName("HE2", true);
1371 }
1372 case "HIE" -> {
1373
1374 atom = residue.getAtomByName("HD1", true);
1375 }
1376 case "ASP" -> {
1377
1378 atom = residue.getAtomByName("HD2", true);
1379 }
1380 case "LYD" -> {
1381
1382 atom = residue.getAtomByName("HZ3", true);
1383 }
1384 case "CYD" -> {
1385
1386 atom = residue.getAtomByName("HG", true);
1387 }
1388 default -> {
1389 atom = null;
1390 }
1391
1392 }
1393 if(atom != null){
1394 int index = activeMolecularAssembly.getResidueList().indexOf(residue);
1395 MSNode atoms = residue.getAtomNode();
1396 atoms.remove(atom);
1397 residue.setName(trueResName);
1398 activeMolecularAssembly.getResidueList().set(index, residue);
1399 }
1400 }
1401 }
1402
1403
1404 @Override
1405 public boolean readNext() {
1406 return readNext(false);
1407 }
1408
1409
1410 @Override
1411 public boolean readNext(boolean resetPosition) {
1412 return readNext(resetPosition, false);
1413 }
1414
1415
1416 @Override
1417 public boolean readNext(boolean resetPosition, boolean print) {
1418 return readNext(resetPosition, print, true);
1419 }
1420
1421
1422 @Override
1423 public boolean readNext(boolean resetPosition, boolean print, boolean parse) {
1424 modelsRead = resetPosition ? 1 : modelsRead + 1;
1425 if (!parse) {
1426 if (print) {
1427 logger.info(format(" Skipped Model %d.", modelsRead));
1428 }
1429 return true;
1430 }
1431 remarkLines = new ArrayList<>(remarkLines.size());
1432
1433
1434 Pattern modelPatt = Pattern.compile("^MODEL\\s+(\\d+)");
1435 boolean eof = true;
1436 for (MolecularAssembly system : systems) {
1437 try {
1438 BufferedReader currentReader;
1439 if (readers.containsKey(system)) {
1440 currentReader = readers.get(system);
1441 try {
1442 if (!currentReader.ready()) {
1443 currentReader = new BufferedReader(new FileReader(readFile));
1444
1445 currentReader.mark(0);
1446 readers.remove(system);
1447 readers.put(system, currentReader);
1448 } else if (resetPosition) {
1449
1450 currentReader.reset();
1451 }
1452 } catch (Exception exception) {
1453
1454
1455 currentReader = new BufferedReader(new FileReader(readFile));
1456
1457 currentReader.mark(0);
1458 readers.remove(system);
1459 readers.put(system, currentReader);
1460 }
1461 } else {
1462 currentReader = new BufferedReader(new FileReader(readFile));
1463
1464 currentReader.mark(0);
1465 readers.put(system, currentReader);
1466 }
1467
1468
1469 String line = currentReader.readLine();
1470 while (line != null) {
1471 line = line.trim();
1472 Matcher m = modelPatt.matcher(line);
1473 if (m.find()) {
1474 int modelNum = parseInt(m.group(1));
1475 if (modelNum == modelsRead) {
1476 if (print) {
1477 logger.log(Level.INFO, format(" Reading model %d for %s", modelNum, currentFile));
1478 }
1479 eof = false;
1480 break;
1481 }
1482 }
1483 line = currentReader.readLine();
1484 }
1485 if (eof) {
1486 if (logger.isLoggable(Level.FINEST)) {
1487 logger.log(Level.FINEST, format("\n End of file reached for %s", readFile));
1488 }
1489 currentReader.close();
1490 return false;
1491 }
1492
1493
1494 currentChainID = null;
1495 currentSegID = null;
1496 boolean modelDone = false;
1497 line = currentReader.readLine();
1498 while (line != null) {
1499 line = line.trim();
1500 String recID = line.substring(0, Math.min(6, line.length())).trim();
1501 try {
1502 Record record = Record.valueOf(recID);
1503 boolean hetatm = true;
1504 switch (record) {
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529 case ATOM:
1530 hetatm = false;
1531 case HETATM:
1532 String name = line.substring(12, 16).trim();
1533 if (name.toUpperCase().contains("1H") || name.toUpperCase().contains("2H")
1534 || name.toUpperCase().contains("3H")) {
1535
1536 fileStandard = VERSION3_2;
1537 }
1538 Character altLoc = line.substring(16, 17).toUpperCase().charAt(0);
1539 if (!altLoc.equals(' ') && !altLoc.equals(currentAltLoc)) {
1540 break;
1541 }
1542
1543
1544
1545 String resName = line.substring(17, 20).trim();
1546 Character chainID = line.substring(21, 22).charAt(0);
1547 String segID = getExistingSegID(chainID);
1548 int resSeq = Hybrid36.decode(4, line.substring(22, 26));
1549 double[] d = new double[3];
1550 d[0] = parseDouble(line.substring(30, 38).trim());
1551 d[1] = parseDouble(line.substring(38, 46).trim());
1552 d[2] = parseDouble(line.substring(46, 54).trim());
1553 double occupancy = 1.0;
1554 double tempFactor = 1.0;
1555 Atom newAtom = new Atom(0, name, altLoc, d, resName, resSeq, chainID, occupancy,
1556 tempFactor, segID);
1557 newAtom.setHetero(hetatm);
1558
1559 if (modRes.containsKey(resName.toUpperCase())) {
1560 newAtom.setModRes(true);
1561 }
1562
1563 Atom returnedAtom = activeMolecularAssembly.findAtom(newAtom);
1564 if (returnedAtom != null) {
1565 returnedAtom.setXYZ(d);
1566 double[] retXYZ = new double[3];
1567 returnedAtom.getXYZ(retXYZ);
1568 } else {
1569 String message = format(" Could not find atom %s in assembly", newAtom);
1570 if (dieOnMissingAtom) {
1571 logger.severe(message);
1572 } else {
1573 logger.warning(message);
1574 }
1575 }
1576 break;
1577 case CRYST1:
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592 logger.fine(" Crystal record found.");
1593 if (line.length() < 55) {
1594 logger.severe(" CRYST1 record is improperly formatted.");
1595 }
1596 double aaxis = parseDouble(line.substring(6, 15).trim());
1597 double baxis = parseDouble(line.substring(15, 24).trim());
1598 double caxis = parseDouble(line.substring(24, 33).trim());
1599 double alpha = parseDouble(line.substring(33, 40).trim());
1600 double beta = parseDouble(line.substring(40, 47).trim());
1601 double gamma = parseDouble(line.substring(47, 54).trim());
1602 int limit = min(line.length(), 66);
1603 String sg = line.substring(55, limit).trim();
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619 Crystal crystal = activeMolecularAssembly.getCrystal();
1620 SpaceGroup spaceGroup = SpaceGroupDefinitions.spaceGroupFactory(sg);
1621 if (Objects.equals(crystal.spaceGroup.shortName, spaceGroup.shortName)) {
1622 crystal.changeUnitCellParameters(aaxis, baxis, caxis, alpha, beta, gamma);
1623 } else {
1624
1625 logger.warning(format(" Original space group %s could not be changed to %s",
1626 crystal.spaceGroup.shortName, spaceGroup.shortName));
1627 }
1628 break;
1629 case ENDMDL:
1630 case END:
1631
1632 logger.log(Level.FINE, format(" Model %d successfully read", modelsRead));
1633 modelDone = true;
1634 break;
1635 case REMARK:
1636 remarkLines.add(line.trim());
1637 if (line.contains("Lambda:")) {
1638 Matcher m = lambdaPattern.matcher(line);
1639 if (m.find()) {
1640 lastReadLambda = Double.parseDouble(m.group(1));
1641 }
1642 }
1643 break;
1644 default:
1645 break;
1646 }
1647 } catch (Exception ex) {
1648
1649 }
1650 if (modelDone) {
1651 break;
1652 }
1653 line = currentReader.readLine();
1654 }
1655 return true;
1656 } catch (IOException ex) {
1657 logger.info(
1658 format(" Exception in parsing frame %d of %s:" + " %s", modelsRead, system.toString(),
1659 ex));
1660 }
1661 }
1662 return false;
1663 }
1664
1665
1666
1667
1668
1669
1670
1671 public void setAltID(MolecularAssembly molecularAssembly, Character altLoc) {
1672 setMolecularSystem(molecularAssembly);
1673 currentAltLoc = altLoc;
1674 }
1675
1676
1677
1678
1679
1680
1681 public void setLogWrites(boolean logWrites) {
1682 this.logWrites = logWrites;
1683 }
1684
1685
1686
1687
1688
1689
1690 public void setModelNumbering(int modelsWritten) {
1691 this.modelsWritten = modelsWritten;
1692 }
1693
1694 public void setLMN(int[] lmn) {
1695 if(lmn[0] >= 1 && lmn[1] >= 1 && lmn[2] >= 1){
1696 this.lmn = lmn;
1697 }else{
1698
1699 this.lmn = new int[]{1,1,1};
1700 }
1701 }
1702
1703
1704
1705
1706
1707
1708 public void setSymOp(int symOp) {
1709 this.nSymOp = symOp;
1710 }
1711
1712
1713
1714
1715
1716
1717
1718 public boolean writeFileAsP1(File file) {
1719
1720 final int l = lmn[0];
1721 final int m = lmn[1];
1722 final int n = lmn[2];
1723 final int numReplicates = l * m * n;
1724 Crystal crystal = activeMolecularAssembly.getCrystal();
1725 int nSymOps = crystal.getUnitCell().spaceGroup.getNumberOfSymOps();
1726
1727 if (nSymOps == 1 && l <= 1 && m <= 1 && n <= 1) {
1728
1729 if (!writeFile(file, false)) {
1730 logger.info(format(" Save failed for %s", activeMolecularAssembly));
1731 return false;
1732 } else {
1733 return true;
1734 }
1735 } else {
1736 Polymer[] polymers = activeMolecularAssembly.getChains();
1737 int chainCount = 0;
1738 for (Polymer polymer : polymers) {
1739 Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1740 polymer.setChainID(chainID);
1741 polymer.setSegID(chainID.toString());
1742 }
1743 nSymOp = 0;
1744 logWrites = false;
1745 boolean writeEnd = false;
1746 if (!writeFile(file, false, false, writeEnd)) {
1747 logger.info(format(" Save failed for %s", activeMolecularAssembly));
1748 return false;
1749 } else {
1750 for (int i = 0; i < l; i++) {
1751 for (int j = 0; j < m; j++) {
1752 for (int k = 0; k < n; k++) {
1753 lValue = i;
1754 mValue = j;
1755 nValue = k;
1756 for (int iSym = 0; iSym < nSymOps; iSym++) {
1757 nSymOp = iSym;
1758 for (Polymer polymer : polymers) {
1759 Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1760 polymer.setChainID(chainID);
1761 polymer.setSegID(chainID.toString());
1762 }
1763
1764 writeEnd = iSym == nSymOps - 1 && i == l - 1 && j == m - 1 && k == n - 1;
1765 if (!writeFile(file, true, false, writeEnd)) {
1766 logger.info(format(" Save failed for %s", activeMolecularAssembly));
1767 return false;
1768 }
1769 }
1770 }
1771 }
1772 }
1773 }
1774
1775
1776 chainCount = 0;
1777 for (Polymer polymer : polymers) {
1778 Character chainID = Polymer.CHAIN_IDS.charAt(chainCount++);
1779 polymer.setChainID(chainID);
1780 polymer.setSegID(chainID.toString());
1781 }
1782
1783 }
1784
1785 return true;
1786 }
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797 public boolean writeFile(File saveFile, boolean append, boolean printLinear, boolean writeEnd) {
1798 return writeFile(saveFile, append, Collections.emptySet(), writeEnd, true);
1799 }
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813 public boolean writeFile(File saveFile, boolean append, Set<Atom> toExclude, boolean writeEnd,
1814 boolean versioning) {
1815 return writeFile(saveFile, append, toExclude, writeEnd, versioning, null);
1816 }
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831 public boolean writeFile(File saveFile, boolean append, Set<Atom> toExclude, boolean writeEnd,
1832 boolean versioning, String[] extraLines) {
1833
1834 List<Atom> deuteriumAtoms = new ArrayList<>();
1835 for(Atom atom: activeMolecularAssembly.getAtomArray()){
1836 if(atom.getName().startsWith("D")){
1837 String name = atom.getName().replace("D","H");
1838 atom.setName(name);
1839 deuteriumAtoms.add(atom);
1840 }
1841 }
1842 if (standardizeAtomNames) {
1843 logger.info(" Setting atom names to PDB standard.");
1844 renameAtomsToPDBStandard(activeMolecularAssembly);
1845 }
1846
1847 for(Atom atom: activeMolecularAssembly.getAtomArray()){
1848 if(deuteriumAtoms.contains(atom) && atom.getName().startsWith("H")){
1849 String name = atom.getName().replace("H","D");
1850 atom.setName(name);
1851 }
1852 }
1853 final Set<Atom> atomExclusions = toExclude == null ? Collections.emptySet() : toExclude;
1854 if (saveFile == null) {
1855 return false;
1856 }
1857 if (vdwH) {
1858 logger.info(" Saving hydrogen to van der Waals centers instead of nuclear locations.");
1859 }
1860 if (nSymOp > -1) {
1861 logger.info(format(" Saving atoms using the symmetry operator:\n%s\n",
1862 activeMolecularAssembly.getCrystal().getUnitCell().spaceGroup.getSymOp(nSymOp)
1863 .toString()));
1864 }
1865
1866
1867 StringBuilder sb = new StringBuilder("ATOM ");
1868 StringBuilder anisouSB = new StringBuilder("ANISOU");
1869 StringBuilder terSB = new StringBuilder("TER ");
1870 StringBuilder model = null;
1871 for (int i = 6; i < 80; i++) {
1872 sb.append(' ');
1873 anisouSB.append(' ');
1874 terSB.append(' ');
1875 }
1876
1877 File newFile = saveFile;
1878 if (!append) {
1879 if (versioning) {
1880 newFile = version(saveFile);
1881 }
1882 } else if (modelsWritten >= 0) {
1883 model = new StringBuilder(format("MODEL %-4d", ++modelsWritten));
1884 model.append(repeat(" ", 65));
1885 }
1886 activeMolecularAssembly.setFile(newFile);
1887 if (activeMolecularAssembly.getName() == null) {
1888 activeMolecularAssembly.setName(newFile.getName());
1889 }
1890 if (logWrites) {
1891 logger.log(Level.INFO, " Saving {0}", newFile.getName());
1892 }
1893
1894 try (FileWriter fw = new FileWriter(newFile, append);
1895 BufferedWriter bw = new BufferedWriter(fw)) {
1896
1897
1898
1899
1900 String[] headerLines = activeMolecularAssembly.getHeaderLines();
1901 for (String line : headerLines) {
1902 bw.write(format("%s\n", line));
1903 }
1904 if (extraLines != null) {
1905 if (rotamerTitration && extraLines[0].contains("REMARK")) {
1906 for (String line : extraLines) {
1907 bw.write(line + "\n");
1908 }
1909 } else {
1910 for (String line : extraLines) {
1911 bw.write(format("REMARK 999 %s\n", line));
1912 }
1913 }
1914 }
1915 if (model != null) {
1916 bw.write(model.toString());
1917 bw.newLine();
1918 }
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933 if (nSymOp < 0) {
1934
1935 Crystal crystal = activeMolecularAssembly.getCrystal();
1936 if (crystal != null && !crystal.aperiodic()) {
1937 Crystal c = crystal.getUnitCell();
1938 if (lmn[0] > 0 || lmn[1] > 0 || lmn[2] > 0) {
1939 c.a = c.a * lmn[0];
1940 c.b = c.b * lmn[1];
1941 c.c = c.c * lmn[2];
1942 }
1943 bw.write(c.toCRYST1());
1944 }
1945 } else if (nSymOp == 0) {
1946
1947 Crystal crystal = activeMolecularAssembly.getCrystal();
1948 if (crystal != null && !crystal.aperiodic()) {
1949 Crystal c = crystal.getUnitCell();
1950 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");
1951 bw.write(p1.toCRYST1());
1952 }
1953 }
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978 int serNum = 1;
1979 Polymer[] polymers = activeMolecularAssembly.getChains();
1980 if (polymers != null) {
1981 for (Polymer polymer : polymers) {
1982 List<Residue> residues = polymer.getResidues();
1983 for (Residue residue : residues) {
1984 if (residue.getName().equalsIgnoreCase("CYS")) {
1985 List<Atom> cysAtoms = residue.getAtomList().stream()
1986 .filter(a -> !atomExclusions.contains(a)).toList();
1987 Atom SG1 = null;
1988 for (Atom atom : cysAtoms) {
1989 String atName = atom.getName().toUpperCase();
1990 if (atName.equals("SG") || atName.equals("SH")
1991 || atom.getAtomType().atomicNumber == 16) {
1992 SG1 = atom;
1993 break;
1994 }
1995 }
1996 List<Bond> bonds = SG1.getBonds();
1997 for (Bond bond : bonds) {
1998 Atom SG2 = bond.get1_2(SG1);
1999 if (SG2.getAtomType().atomicNumber == 16 && !atomExclusions.contains(SG2)) {
2000 if (SG1.getIndex() < SG2.getIndex()) {
2001 bond.energy(false);
2002 bw.write(format("SSBOND %3d CYS %1s %4s CYS %1s %4s %36s %5.2f\n", serNum++,
2003 SG1.getChainID().toString(), Hybrid36.encode(4, SG1.getResidueNumber()),
2004 SG2.getChainID().toString(), Hybrid36.encode(4, SG2.getResidueNumber()), "",
2005 bond.getValue()));
2006 }
2007 }
2008 }
2009 }
2010 }
2011 }
2012 }
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034 MolecularAssembly[] molecularAssemblies = this.getMolecularAssemblyArray();
2035 int serial = 1;
2036 if (nSymOp > 0) {
2037 serial = serialP1;
2038 }
2039
2040
2041 if (polymers != null) {
2042 for (Polymer polymer : polymers) {
2043 currentSegID = polymer.getName();
2044 currentChainID = polymer.getChainID();
2045 sb.setCharAt(21, currentChainID);
2046
2047 List<Residue> residues = polymer.getResidues();
2048 for (Residue residue : residues) {
2049 String resName = residue.getName();
2050 if (resName.length() > 3) {
2051 resName = resName.substring(0, 3);
2052 }
2053 int resID = residue.getResidueNumber();
2054 sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2055 sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2056
2057 List<Atom> residueAtoms = residue.getAtomList().stream()
2058 .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2059 boolean altLocFound = false;
2060 for (Atom atom : residueAtoms) {
2061 if (mutate) {
2062 for (Mutation mtn : mutations) {
2063 if (resID == mtn.resID) {
2064 ArrayList<String> alchAtoms = mtn.getAlchemicalAtoms(true);
2065 if (alchAtoms != null) {
2066 if (residue.getBackboneAtoms().contains(atom) && alchAtoms.contains(atom.getName())) {
2067 logger.info(format(" MUTATION atom is %d chain %s",serial, currentChainID));
2068 }
2069 } else {
2070
2071 if (residue.getBackboneAtoms().contains(atom)) {
2072 logger.info(format(" MUTATION atom is %d chain %s",serial, currentChainID));
2073 }
2074 }
2075 }
2076 }
2077 }
2078 writeAtom(atom, serial++, sb, anisouSB, bw);
2079 Character altLoc = atom.getAltLoc();
2080 if (altLoc != null && !altLoc.equals(' ')) {
2081 altLocFound = true;
2082 }
2083 }
2084
2085 if (altLocFound) {
2086 for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2087 MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2088 Polymer altPolymer = altMolecularAssembly.getPolymer(currentChainID, currentSegID, false);
2089 Residue altResidue = altPolymer.getResidue(resName, resID, false, Residue.ResidueType.AA);
2090 if (altResidue == null) {
2091 resName = AminoAcid3.UNK.name();
2092 altResidue = altPolymer.getResidue(resName, resID, false, Residue.ResidueType.AA);
2093 }
2094 residueAtoms = altResidue.getAtomList().stream()
2095 .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2096 for (Atom atom : residueAtoms) {
2097 if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2098 .equals('A')) {
2099 sb.replace(17, 20, padLeft(atom.getResidueName().toUpperCase(), 3));
2100 writeAtom(atom, serial++, sb, anisouSB, bw);
2101 }
2102 }
2103 }
2104 }
2105 }
2106 terSB.replace(6, 11, format("%5s", Hybrid36.encode(5, serial++)));
2107 terSB.replace(12, 16, " ");
2108 terSB.replace(16, 26, sb.substring(16, 26));
2109 bw.write(terSB.toString());
2110 bw.newLine();
2111 }
2112 }
2113 sb.replace(0, 6, "HETATM");
2114 sb.setCharAt(21, 'A');
2115
2116 Character chainID = 'A';
2117 if (polymers != null) {
2118 chainID = polymers[0].getChainID();
2119 }
2120 activeMolecularAssembly.setChainIDAndRenumberMolecules(chainID);
2121
2122
2123 List<MSNode> molecules = activeMolecularAssembly.getMolecules();
2124 int numMolecules = molecules.size();
2125 for (int i = 0; i < numMolecules; i++) {
2126 Molecule molecule = (Molecule) molecules.get(i);
2127 chainID = molecule.getChainID();
2128 sb.setCharAt(21, chainID);
2129 String resName = molecule.getResidueName();
2130 int resID = molecule.getResidueNumber();
2131 if (resName.length() > 3) {
2132 resName = resName.substring(0, 3);
2133 }
2134 sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2135 sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2136
2137 List<Atom> moleculeAtoms = molecule.getAtomList().stream()
2138 .filter(a -> !atomExclusions.contains(a)).collect(Collectors.toList());
2139 boolean altLocFound = false;
2140 for (Atom atom : moleculeAtoms) {
2141 writeAtom(atom, serial++, sb, anisouSB, bw);
2142 Character altLoc = atom.getAltLoc();
2143 if (altLoc != null && !altLoc.equals(' ')) {
2144 altLocFound = true;
2145 }
2146 }
2147
2148 if (altLocFound) {
2149 for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2150 MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2151 MSNode altmolecule = altMolecularAssembly.getMolecules().get(i);
2152 moleculeAtoms = altmolecule.getAtomList();
2153 for (Atom atom : moleculeAtoms) {
2154 if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2155 .equals('A')) {
2156 writeAtom(atom, serial++, sb, anisouSB, bw);
2157 }
2158 }
2159 }
2160 }
2161 }
2162
2163 List<MSNode> ions = activeMolecularAssembly.getIons();
2164 for (int i = 0; i < ions.size(); i++) {
2165 Molecule ion = (Molecule) ions.get(i);
2166 chainID = ion.getChainID();
2167 sb.setCharAt(21, chainID);
2168 String resName = ion.getResidueName();
2169 int resID = ion.getResidueNumber();
2170 if (resName.length() > 3) {
2171 resName = resName.substring(0, 3);
2172 }
2173 sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2174 sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2175
2176 List<Atom> ionAtoms = ion.getAtomList().stream().filter(a -> !atomExclusions.contains(a))
2177 .collect(Collectors.toList());
2178 boolean altLocFound = false;
2179 for (Atom atom : ionAtoms) {
2180 writeAtom(atom, serial++, sb, anisouSB, bw);
2181 Character altLoc = atom.getAltLoc();
2182 if (altLoc != null && !altLoc.equals(' ')) {
2183 altLocFound = true;
2184 }
2185 }
2186
2187 if (altLocFound) {
2188 for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2189 MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2190 MSNode altion = altMolecularAssembly.getIons().get(i);
2191 ionAtoms = altion.getAtomList();
2192 for (Atom atom : ionAtoms) {
2193 if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2194 .equals('A')) {
2195 writeAtom(atom, serial++, sb, anisouSB, bw);
2196 }
2197 }
2198 }
2199 }
2200 }
2201
2202 List<MSNode> water = activeMolecularAssembly.getWater();
2203 for (int i = 0; i < water.size(); i++) {
2204 Molecule wat = (Molecule) water.get(i);
2205 chainID = wat.getChainID();
2206 sb.setCharAt(21, chainID);
2207 String resName = wat.getResidueName();
2208 int resID = wat.getResidueNumber();
2209 if (resName.length() > 3) {
2210 resName = resName.substring(0, 3);
2211 }
2212 sb.replace(17, 20, padLeft(resName.toUpperCase(), 3));
2213 sb.replace(22, 26, format("%4s", Hybrid36.encode(4, resID)));
2214 List<Atom> waterAtoms = wat.getAtomList().stream().filter(a -> !atomExclusions.contains(a))
2215 .collect(Collectors.toList());
2216 boolean altLocFound = false;
2217 for (Atom atom : waterAtoms) {
2218 writeAtom(atom, serial++, sb, anisouSB, bw);
2219 Character altLoc = atom.getAltLoc();
2220 if (altLoc != null && !altLoc.equals(' ')) {
2221 altLocFound = true;
2222 }
2223 }
2224
2225 if (altLocFound) {
2226 for (int ma = 1; ma < molecularAssemblies.length; ma++) {
2227 MolecularAssembly altMolecularAssembly = molecularAssemblies[ma];
2228 MSNode altwater = altMolecularAssembly.getWater().get(i);
2229 waterAtoms = altwater.getAtomList();
2230 for (Atom atom : waterAtoms) {
2231 if (atom.getAltLoc() != null && !atom.getAltLoc().equals(' ') && !atom.getAltLoc()
2232 .equals('A')) {
2233 writeAtom(atom, serial++, sb, anisouSB, bw);
2234 }
2235 }
2236 }
2237 }
2238 }
2239
2240 if (model != null) {
2241 bw.write("ENDMDL");
2242 bw.newLine();
2243 }
2244
2245 if (writeEnd) {
2246 bw.write("END");
2247 bw.newLine();
2248 }
2249
2250 if (nSymOp >= 0) {
2251 serialP1 = serial;
2252 }
2253
2254 } catch (Exception e) {
2255 String message = "Exception writing to file: " + saveFile;
2256 logger.log(Level.WARNING, message, e);
2257 return false;
2258 }
2259 return true;
2260 }
2261
2262
2263
2264
2265
2266
2267 @Override
2268 public boolean writeFile(File saveFile, boolean append) {
2269 return writeFile(saveFile, append, false, true);
2270 }
2271
2272 public boolean writeFile(File saveFile, boolean append, String[] extraLines) {
2273 return writeFile(saveFile, append, Collections.emptySet(), false, !append, extraLines);
2274 }
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286 public boolean writeFile(File saveFile, boolean append, boolean versioning) {
2287 return writeFile(saveFile, append, Collections.emptySet(), true, versioning);
2288 }
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298 public boolean writeFileWithHeader(File saveFile, String header, boolean append) {
2299
2300 List<Atom> deuteriumAtoms = new ArrayList<>();
2301 for(Atom atom: activeMolecularAssembly.getAtomArray()){
2302 if(atom.getName().startsWith("D")){
2303 String name = atom.getName().replace("D","H");
2304 atom.setName(name);
2305 deuteriumAtoms.add(atom);
2306 }
2307 }
2308 if (standardizeAtomNames) {
2309 logger.info(" Setting atom names to PDB standard.");
2310 renameAtomsToPDBStandard(activeMolecularAssembly);
2311 }
2312
2313 for(Atom atom: activeMolecularAssembly.getAtomArray()){
2314 if(deuteriumAtoms.contains(atom) && atom.getName().startsWith("H")){
2315 String name = atom.getName().replace("H","D");
2316 atom.setName(name);
2317 }
2318 }
2319 activeMolecularAssembly.setFile(saveFile);
2320 activeMolecularAssembly.setName(saveFile.getName());
2321
2322 try (FileWriter fw = new FileWriter(saveFile, append); BufferedWriter bw = new BufferedWriter(
2323 fw)) {
2324 bw.write(header);
2325 bw.newLine();
2326 } catch (Exception e) {
2327 String message = " Exception writing to file: " + saveFile;
2328 logger.log(Level.WARNING, message, e);
2329 return false;
2330 }
2331 if (writeFile(saveFile, true)) {
2332 logger.log(Level.INFO, " Wrote PDB to file {0}", saveFile.getPath());
2333 return true;
2334 } else {
2335 logger.log(Level.INFO, " Error writing to file {0}", saveFile.getPath());
2336 return false;
2337 }
2338 }
2339
2340
2341
2342
2343
2344
2345
2346
2347 public boolean writeFileWithHeader(File saveFile, String header) {
2348 return writeFileWithHeader(saveFile, header, true);
2349 }
2350
2351
2352
2353
2354
2355
2356
2357
2358 public boolean writeFileWithHeader(File saveFile, StringBuilder header) {
2359 return writeFileWithHeader(saveFile, header.toString());
2360 }
2361
2362
2363
2364
2365
2366
2367
2368 private String getExistingSegID(Character c) {
2369 if (c.equals(' ')) {
2370 c = 'A';
2371 }
2372
2373
2374 if (c.equals(currentChainID)) {
2375 return currentSegID;
2376 } else {
2377 currentChainID = null;
2378 }
2379
2380 List<String> segIDs = segidMap.get(c);
2381 if (segIDs != null) {
2382 if (segIDs.size() > 1) {
2383 if (currentSegID == null) {
2384 currentChainID = c;
2385 currentSegID = segIDs.get(0);
2386 return segIDs.get(0);
2387 } else if (currentSegID.length() == 1) {
2388 currentChainID = c;
2389 currentSegID = segIDs.get(1);
2390 return segIDs.get(1);
2391 } else if (currentSegID.length() == 2) {
2392 String s = currentSegID.substring(0,1);
2393 int num = -2;
2394 try {
2395 num = Integer.parseInt(s);
2396 } catch (NumberFormatException e) {
2397 logger.severe(" SegID of length 2 does not start with an integer.");
2398 }
2399 currentChainID = c;
2400 currentSegID = segIDs.get(num+1);
2401 return segIDs.get(num+1);
2402 } else {
2403 logger.info(" Too many repeated chains. Using single letter for segID.");
2404 }
2405 }
2406 return segIDs.get(0);
2407 } else {
2408 logger.log(Level.INFO, format(" Creating SegID for to chain %s", c));
2409 return getSegID(c);
2410 }
2411 }
2412
2413
2414
2415
2416
2417
2418
2419 private String getSegID(Character c) {
2420 if (c.equals(' ')) {
2421 c = 'A';
2422 }
2423
2424
2425 if (c.equals(currentChainID)) {
2426 return currentSegID;
2427 }
2428
2429
2430 int count = 0;
2431 for (String segID : segIDs) {
2432 if (segID.endsWith(c.toString())) {
2433 count++;
2434 }
2435 }
2436
2437
2438 String newSegID;
2439 if (count == 0) {
2440 newSegID = c.toString();
2441 } else {
2442 newSegID = count + c.toString();
2443 }
2444 segIDs.add(newSegID);
2445 currentChainID = c;
2446 currentSegID = newSegID;
2447
2448 if (segidMap.containsKey(c)) {
2449 segidMap.get(c).add(newSegID);
2450 } else {
2451 List<String> newChainList = new ArrayList<>();
2452 newChainList.add(newSegID);
2453 segidMap.put(c, newChainList);
2454 }
2455
2456 return newSegID;
2457 }
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469 private void writeAtom(Atom atom, int serial, StringBuilder sb, StringBuilder anisouSB,
2470 BufferedWriter bw) throws IOException {
2471 String name = atom.getName();
2472 int nameLength = name.length();
2473 if (nameLength > 4) {
2474 name = name.substring(0, 4);
2475 } else if (nameLength == 1) {
2476 name = name + " ";
2477 } else if (nameLength == 2) {
2478 if (atom.getAtomType().valence == 0) {
2479 name = name + " ";
2480 } else {
2481 name = name + " ";
2482 }
2483 }
2484 double[] xyz = vdwH ? atom.getRedXYZ() : atom.getXYZ(null);
2485 if (nSymOp >= 0) {
2486 Crystal crystal = activeMolecularAssembly.getCrystal().getUnitCell();
2487 SymOp symOp = crystal.spaceGroup.getSymOp(nSymOp);
2488 double[] newXYZ = new double[xyz.length];
2489 crystal.applySymOp(xyz, newXYZ, symOp);
2490 if (lValue > 0 || mValue > 0 || nValue > 0) {
2491 double[] translation = new double[] {lValue, mValue, nValue};
2492 crystal.getUnitCell().toCartesianCoordinates(translation, translation);
2493 newXYZ[0] += translation[0];
2494 newXYZ[1] += translation[1];
2495 newXYZ[2] += translation[2];
2496 }
2497 xyz = newXYZ;
2498 }
2499 sb.replace(6, 16, format("%5s " + padLeft(name.toUpperCase(), 4), Hybrid36.encode(5, serial)));
2500 Character altLoc = atom.getAltLoc();
2501 sb.setCharAt(16, Objects.requireNonNullElse(altLoc, ' '));
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516 StringBuilder decimals = new StringBuilder();
2517 for (int i = 0; i < 3; i++) {
2518 try {
2519 decimals.append(StringUtils.fwFpDec(xyz[i], 8, 3));
2520 } catch (IllegalArgumentException ex) {
2521 String newValue = StringUtils.fwFpTrunc(xyz[i], 8, 3);
2522 logger.info(format(" XYZ %d coordinate %8.3f for atom %s "
2523 + "overflowed bounds of 8.3f string specified by PDB "
2524 + "format; truncating value to %s", i, xyz[i], atom, newValue));
2525 decimals.append(newValue);
2526 }
2527 }
2528 try {
2529 decimals.append(StringUtils.fwFpDec(atom.getOccupancy(), 6, 2));
2530 } catch (IllegalArgumentException ex) {
2531 logger.severe(
2532 format(" Occupancy %f for atom %s is impossible; " + "value must be between 0 and 1",
2533 atom.getOccupancy(), atom));
2534 }
2535 try {
2536 decimals.append(StringUtils.fwFpDec(atom.getTempFactor(), 6, 2));
2537 } catch (IllegalArgumentException ex) {
2538 String newValue = StringUtils.fwFpTrunc(atom.getTempFactor(), 6, 2);
2539 logger.info(format(" Atom temp factor %6.2f for atom %s overflowed "
2540 + "bounds of 6.2f string specified by PDB format; truncating " + "value to %s",
2541 atom.getTempFactor(), atom, newValue));
2542 decimals.append(newValue);
2543 }
2544 sb.replace(30, 66, decimals.toString());
2545
2546 name = Atom.ElementSymbol.values()[atom.getAtomicNumber() - 1].toString();
2547 name = name.toUpperCase();
2548 if (atom.isDeuterium()) {
2549 name = "D";
2550 }
2551 sb.replace(76, 78, padLeft(name, 2));
2552 sb.replace(78, 80, format("%2d", 0));
2553 bw.write(sb.toString());
2554 bw.newLine();
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573 double[] anisou = atom.getAnisou(null);
2574 if (anisou != null) {
2575 anisouSB.replace(6, 80, sb.substring(6, 80));
2576 anisouSB.replace(28, 70,
2577 format("%7d%7d%7d%7d%7d%7d", (int) (anisou[0] * 1e4), (int) (anisou[1] * 1e4),
2578 (int) (anisou[2] * 1e4), (int) (anisou[3] * 1e4), (int) (anisou[4] * 1e4),
2579 (int) (anisou[5] * 1e4)));
2580 bw.write(anisouSB.toString());
2581 bw.newLine();
2582 }
2583 }
2584
2585
2586 private enum Record {
2587 ANISOU, ATOM, CONECT, CRYST1, DBREF, END, MODEL, ENDMDL, HELIX, HETATM, LINK, MTRIX1, MTRIX2, MTRIX3, MODRES, SEQRES, SHEET, SSBOND, REMARK
2588 }
2589
2590
2591 public enum PDBFileStandard {
2592 VERSION2_3, VERSION3_0, VERSION3_1, VERSION3_2, VERSION3_3
2593 }
2594
2595 public static class Mutation {
2596
2597
2598 final int resID;
2599
2600 final String resName;
2601
2602 final char chainChar;
2603
2604 String origResName;
2605
2606 public Mutation(int resID, char chainChar, String newResName) {
2607 newResName = newResName.toUpperCase();
2608 if (newResName.length() != 3) {
2609 logger.log(Level.WARNING, format("Invalid mutation target: %s.", newResName));
2610 }
2611 int isAA = AminoAcidUtils.getAminoAcidNumber(newResName);
2612 int isNA = NucleicAcidUtils.getNucleicAcidNumber(newResName);
2613 if (isAA == -1 && isNA == -1) {
2614 logger.log(Level.WARNING, format("Invalid mutation target: %s.", newResName));
2615 }
2616 this.resID = resID;
2617 this.chainChar = chainChar;
2618 this.resName = newResName;
2619 }
2620
2621
2622
2623
2624
2625
2626
2627
2628 public String isNonAlchemicalAtom(String atomName) {
2629 if (isWtPurine()) {
2630 if (atomName.equals("N9")) {
2631 if (isMtnPyrimidine()) {
2632 return "~N1";
2633 }
2634 return atomName;
2635 } else if (atomName.equals("C4")) {
2636 if (isMtnPyrimidine()) {
2637 return "~C2";
2638 }
2639 return atomName;
2640 }
2641 return null;
2642 }
2643
2644 if (isWtPyrimidine()) {
2645 if (atomName.equals("N1")) {
2646 if (isMtnPurine()) {
2647
2648 return "~N9";
2649 }
2650 return atomName;
2651 } else if (atomName.equals("C2")) {
2652 if (isMtnPurine()) {
2653
2654 return "~C4";
2655 }
2656 return atomName;
2657 }
2658 return null;
2659 }
2660
2661 return null;
2662 }
2663
2664
2665
2666
2667
2668
2669 public ArrayList<String> getAlchemicalAtoms(boolean isWriting) {
2670
2671 if (resName.equals(origResName)) {
2672 logger.severe("Desired Mutation residue is the same as the original.");
2673 return null;
2674 }
2675
2676 boolean purpur;
2677 if (isMtnPurine() && isWtPurine()) {
2678 purpur = true;
2679 } else if (isMtnPyrimidine() && isWtPyrimidine()) {
2680 purpur = false;
2681 } else {
2682
2683 return null;
2684 }
2685
2686 String res;
2687
2688
2689 if (isWriting) {
2690 res = resName;
2691 } else {
2692 res = origResName;
2693 }
2694
2695 ArrayList<String> list = new ArrayList<>();
2696 if (purpur) {
2697 if (res.equals("DAD") || res.equals("DA")) {
2698 list.add("N6");
2699 list.add("H61");
2700 list.add("H62");
2701 list.add("H2");
2702 } else {
2703 list.add("H1");
2704 list.add("N2");
2705 list.add("H21");
2706 list.add("H22");
2707 list.add("O6");
2708 }
2709 } else {
2710 if (res.equals("DTY") || res.equals("DT")) {
2711 list.add("H3");
2712 list.add("O4");
2713 list.add("C7");
2714 list.add("H71");
2715 list.add("H72");
2716 list.add("H73");
2717 } else {
2718 list.add("N4");
2719 list.add("H41");
2720 list.add("H42");
2721 list.add("H5");
2722 }
2723 }
2724 return list;
2725 }
2726
2727
2728
2729
2730
2731 public boolean isMtnPurine() {
2732 return resName.equals("DA") || resName.equals("DG") || resName.equals("DAD") || resName.equals("DGU");
2733 }
2734
2735
2736
2737
2738
2739 public boolean isMtnPyrimidine() {
2740 return resName.equals("DC") || resName.equals("DT") || resName.equals("DCY") || resName.equals("DTY");
2741 }
2742
2743
2744
2745
2746
2747 public boolean isWtPurine() {
2748 return origResName.equals("DA") || origResName.equals("DG") || origResName.equals("DAD") || origResName.equals("DGU");
2749 }
2750
2751
2752
2753
2754
2755 public boolean isWtPyrimidine() {
2756 return origResName.equals("DT") || origResName.equals("DC") || origResName.equals("DTY") || origResName.equals("DCY");
2757 }
2758 }
2759 }