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