1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Module containing the internal relax structural object."""
24
25
26 from copy import deepcopy
27 from numpy import array, dot, float64, linalg, zeros
28 import os
29 from os import F_OK, access, curdir, sep
30 from os.path import abspath
31 from re import search
32 from warnings import warn
33
34
35 from data_store.relax_xml import object_to_xml, xml_to_object
36 from lib import regex
37 from lib.check_types import is_float
38 from lib.errors import RelaxError, RelaxNoneIntError, RelaxNoPdbError
39 from lib.io import file_root, open_read_file
40 from lib.selection import Selection
41 from lib.structure import pdb_read, pdb_write
42 from lib.structure.internal.displacements import Displacements
43 from lib.structure.internal.models import ModelList
44 from lib.structure.internal.molecules import MolContainer
45 from lib.warnings import RelaxWarning
46
47
48
49 CHAIN_ID_LIST = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz'
50
51
53 """The internal relax structural data object.
54
55 The structural data object for this class is a container possessing a number of different arrays corresponding to different structural information. These objects are described in the structural container docstring.
56 """
57
59 """Initialise the structural object."""
60
61
62 self.structural_data = ModelList()
63
64
66 """Find the atom named attached_atom directly bonded to the atom located at the index.
67
68 @param attached_atom: The name of the attached atom to return.
69 @type attached_atom: str
70 @param index: The index of the atom which the attached atom is attached to.
71 @type index: int
72 @param mol: The molecule container.
73 @type mol: MolContainer instance
74 @return: A tuple of information about the bonded atom.
75 @rtype: tuple consisting of the atom number (int), atom name (str), element name (str), and atomic position (Numeric array of len 3)
76 """
77
78
79 bonded_found = False
80
81
82 if not mol.bonded[index]:
83
84 if not hasattr(mol, 'type'):
85 self._mol_type(mol)
86
87
88 if mol.type == 'protein':
89 self._protein_connect(mol)
90
91
92 else:
93 self._find_bonded_atoms(index, mol, radius=2)
94
95
96 matching_list = []
97 for bonded_index in mol.bonded[index]:
98 if regex.search(mol.atom_name[bonded_index], attached_atom):
99 matching_list.append(bonded_index)
100 num_attached = len(matching_list)
101
102
103 if num_attached > 1:
104
105 matching_names = []
106 for i in matching_list:
107 matching_names.append(mol.atom_name[i])
108
109
110 return None, None, None, None, None, 'More than one attached atom found: ' + repr(matching_names)
111
112
113 if num_attached == 0:
114 return None, None, None, None, None, "No attached atom could be found"
115
116
117 index = matching_list[0]
118 bonded_num = mol.atom_num[index]
119 bonded_name = mol.atom_name[index]
120 element = mol.element[index]
121 pos = [mol.x[index], mol.y[index], mol.z[index]]
122 attached_name = mol.atom_name[index]
123
124
125 return bonded_num, bonded_name, element, pos, attached_name, None
126
127
129 """Find all atoms within a sphere and say that they are attached to the central atom.
130
131 The found atoms will be added to the 'bonded' data structure.
132
133
134 @param index: The index of the central atom.
135 @type index: int
136 @param mol: The molecule container.
137 @type mol: MolContainer instance
138 """
139
140
141 centre = array([mol.x[index], mol.y[index], mol.z[index]], float64)
142
143
144 dist_list = []
145 connect_list = {}
146 element_list = {}
147 for i in range(len(mol.atom_num)):
148
149 if mol.element[index] == 'H' and mol.element[i] == 'H':
150 continue
151
152
153 pos = array([mol.x[i], mol.y[i], mol.z[i]], float64)
154
155
156 dist = linalg.norm(centre-pos)
157
158
159 if dist < radius:
160
161 dist_list.append(dist)
162
163
164 connect_list[dist] = i
165
166
167 element_list[dist] = mol.element[i]
168
169
170 max_conn = 1000
171 if mol.element[index] == 'H':
172 max_conn = 1
173 elif mol.element[index] == 'O':
174 max_conn = 2
175 elif mol.element[index] == 'N':
176 max_conn = 3
177 elif mol.element[index] == 'C':
178 max_conn = 4
179
180
181 dist_list.sort()
182
183
184 for i in range(min(max_conn, len(dist_list))):
185 mol.atom_connect(index, connect_list[dist_list[i]])
186
187
189 """Return the chemical name corresponding to the given residue ID.
190
191 The following names are currently returned::
192 ________________________________________________
193 | | |
194 | hetID | Chemical name |
195 |________|_____________________________________|
196 | | |
197 | TNS | Tensor |
198 | COM | Centre of mass |
199 | AXS | Tensor axes |
200 | SIM | Monte Carlo simulation tensor axes |
201 | PIV | Pivot point |
202 | CON | Cone object |
203 | AVE | Average vector |
204 |________|_____________________________________|
205
206 For any other residues, no description is returned.
207
208 @param hetID: The residue ID.
209 @type hetID: str
210 @return: The chemical name.
211 @rtype: str or None
212 """
213
214
215 if hetID == 'TNS':
216 return 'Tensor'
217
218
219 if hetID == 'COM':
220 return 'Centre of mass'
221
222
223 if hetID == 'AXS':
224 return 'Tensor axes'
225
226
227 if hetID == 'SIM':
228 return 'Monte Carlo simulation tensor axes'
229
230
231 if hetID == 'PIV':
232 return 'Pivot point'
233
234
235 if hetID == 'CON':
236 return 'Cone'
237
238
239 if hetID == 'AVE':
240 return 'Average vector'
241
242
244 """Generator function for looping over the models in the Gaussian log file.
245
246 @param file_path: The full path of the Gaussian log file.
247 @type file_path: str
248 @return: The model number and all the records for that model.
249 @rtype: tuple of int and array of str
250 """
251
252
253 file = open_read_file(file_path)
254 lines = file.readlines()
255 file.close()
256
257
258 if lines == []:
259 raise RelaxError("The Gaussian log file is empty.")
260
261
262 found = False
263 str_index = 0
264 total_atom = 0
265 model = 0
266 records = []
267
268
269 for i in range(len(lines)):
270
271 if search("Standard orientation", lines[i]):
272 found = True
273 str_index = 0
274 continue
275
276
277 if found and str_index > 4 and search("---------", lines[i]):
278
279 yield records
280
281
282 records = []
283 found = False
284
285
286 if not found:
287 continue
288
289
290 records.append(lines[i])
291
292
293 str_index += 1
294
295
297 """Loop over and parse the PDB connectivity annotation records.
298
299 These are the records identified in the U{PDB version 3.30 documentation<http://www.wwpdb.org/documentation/format33/sect6.html>}.
300
301
302 @param lines: The lines of the PDB file excluding the sections prior to the connectivity annotation section.
303 @type lines: list of str
304 @return: The remaining PDB lines with the connectivity annotation records stripped.
305 @rtype: list of str
306 """
307
308
309 records = [
310 'SSBOND',
311 'LINK ',
312 'CISPEP'
313 ]
314
315
316 for i in range(len(lines)):
317
318 if lines[i][:6] not in records:
319 break
320
321
322 return lines[i:]
323
324
326 """Generator function for looping over the models in the PDB file.
327
328 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect9.html}.
329
330
331 @param lines: The lines of the coordinate section.
332 @type lines: list of str
333 @return: The model number and all the records for that model.
334 @rtype: tuple of int and array of str
335 """
336
337
338 model = None
339 records = []
340
341
342 for i in range(len(lines)):
343
344 if lines[i][:5] == 'MODEL':
345 try:
346 model = int(lines[i].split()[1])
347 except:
348 raise RelaxError("The MODEL record " + repr(lines[i]) + " is corrupt, cannot read the PDB file.")
349
350
351 if not (lines[i][:4] == 'ATOM' or lines[i][:6] == 'HETATM') and not len(records):
352 continue
353
354
355 if lines[i][:6] == 'ENDMDL':
356
357 yield model, records
358
359
360 records = []
361
362
363 continue
364
365
366 records.append(lines[i])
367
368
369 if len(records):
370 yield model, records
371
372
374 """Loop over and parse the PDB hetrogen records.
375
376 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect4.html}.
377
378
379 @param lines: The lines of the PDB file excluding the sections prior to the hetrogen section.
380 @type lines: list of str
381 @return: The remaining PDB lines with the hetrogen records stripped.
382 @rtype: list of str
383 """
384
385
386 records = [
387 'HET ',
388 'FORMUL',
389 'HETNAM',
390 'HETSYN'
391 ]
392
393
394 for i in range(len(lines)):
395
396 if lines[i][:6] not in records:
397 break
398
399
400 return lines[i:]
401
402
404 """Loop over and parse the PDB miscellaneous records.
405
406 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect7.html}.
407
408
409 @param lines: The lines of the PDB file excluding the sections prior to the miscellaneous section.
410 @type lines: list of str
411 @return: The remaining PDB lines with the miscellaneous records stripped.
412 @rtype: list of str
413 """
414
415
416 records = [
417 'SITE '
418 ]
419
420
421 for i in range(len(lines)):
422
423 if lines[i][:6] not in records:
424 break
425
426
427 return lines[i:]
428
429
431 """Loop over and parse the PDB primary structure records.
432
433 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect3.html}.
434
435
436 @param lines: The lines of the PDB file excluding the title section.
437 @type lines: list of str
438 @return: The remaining PDB lines with the primary structure records stripped.
439 @rtype: list of str
440 """
441
442
443 records = [
444 'DBREF ',
445 'DBREF1',
446 'DBREF2',
447 'SEQADV',
448 'SEQRES',
449 'MODRES'
450 ]
451
452
453 for i in range(len(lines)):
454
455 if lines[i][:6] not in records:
456 break
457
458
459 return lines[i:]
460
461
463 """Loop over and parse the PDB secondary structure records.
464
465 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect5.html}.
466
467
468 @param lines: The lines of the PDB file excluding the sections prior to the secondary structure section.
469 @type lines: list of str
470 @return: The remaining PDB lines with the secondary structure records stripped.
471 @rtype: list of str
472 """
473
474
475 records = [
476 'HELIX ',
477 'SHEET ',
478 'TURN '
479 ]
480
481
482 for i in range(len(lines)):
483
484 if lines[i][:6] not in records:
485 break
486
487
488 if lines[i][:5] == 'HELIX':
489
490 record_type, ser_num, helix_id, init_res_name, init_chain_id, init_seq_num, init_icode, end_res_name, end_chain_id, end_seq_num, end_icode, helix_class, comment, length = pdb_read.helix(lines[i])
491
492
493 if not hasattr(self, 'helices'):
494 self.helices = []
495 self.helices.append([helix_id, init_chain_id, init_res_name, init_seq_num, end_chain_id, end_res_name, end_seq_num, helix_class, length])
496
497
498 if lines[i][:5] == 'SHEET':
499
500 record_type, strand, sheet_id, num_strands, init_res_name, init_chain_id, init_seq_num, init_icode, end_res_name, end_chain_id, end_seq_num, end_icode, sense, cur_atom, cur_res_name, cur_chain_id, cur_res_seq, cur_icode, prev_atom, prev_res_name, prev_chain_id, prev_res_seq, prev_icode = pdb_read.sheet(lines[i])
501
502
503 if not hasattr(self, 'sheets'):
504 self.sheets = []
505 self.sheets.append([strand, sheet_id, num_strands, init_res_name, init_chain_id, init_seq_num, init_icode, end_res_name, end_chain_id, end_seq_num, end_icode, sense, cur_atom, cur_res_name, cur_chain_id, cur_res_seq, cur_icode, prev_atom, prev_res_name, prev_chain_id, prev_res_seq, prev_icode])
506
507
508 return lines[i:]
509
510
512 """Loop over and parse the PDB title records.
513
514 These are the records identified in the PDB version 3.30 documentation at U{http://www.wwpdb.org/documentation/format33/sect2.html}.
515
516
517 @param lines: All lines of the PDB file.
518 @type lines: list of str
519 @return: The remaining PDB lines with the title records stripped.
520 @rtype: list of str
521 """
522
523
524 records = [
525 'HEADER',
526 'OBSLTE',
527 'TITLE ',
528 'SPLT ',
529 'CAVEAT',
530 'COMPND',
531 'SOURCE',
532 'KEYWDS',
533 'EXPDTA',
534 'NUMMDL',
535 'MDLTYP',
536 'AUTHOR',
537 'REVDAT',
538 'SPRSDE',
539 'JRNL ',
540 'REMARK'
541 ]
542
543
544 for i in range(len(lines)):
545
546 if lines[i][:6] not in records:
547 break
548
549
550 return lines[i:]
551
552
581
582
584 """Generator function for looping over the models in the XYZ file.
585
586 @param file_path: The full path of the XYZ file.
587 @type file_path: str
588 @return: The model number and all the records for that model.
589 @rtype: tuple of int and array of str
590 """
591
592
593 file = open_read_file(file_path)
594 lines = file.readlines()
595 file.close()
596
597
598 if lines == []:
599 raise RelaxError("The XYZ file is empty.")
600
601
602 total_atom = 0
603 model = 0
604 records = []
605
606
607 for i in range(len(lines)):
608 num=0
609 word = lines[i].split()
610
611 if (i==0) and (len(word)==1):
612 try:
613 total_atom = int(word[0])
614 num = 1
615 except:
616 raise RelaxError("The MODEL record " + repr(lines[i]) + " is corrupt, cannot read the XYZ file.")
617
618
619 if (len(records) == total_atom):
620
621 yield records
622
623
624 records = []
625
626
627 if (len(word) != 4):
628 continue
629
630
631 records.append(lines[i])
632
633
634 if len(records):
635 yield records
636
637
639 """Generator function for looping over the molecules in the PDB records of a model.
640
641 @param records: The list of PDB records for the model, or if no models exist the entire PDB file.
642 @type records: list of str
643 @return: The molecule number and all the records for that molecule.
644 @rtype: tuple of int and list of str
645 """
646
647
648 if records == []:
649 raise RelaxError("There are no PDB records for this model.")
650
651
652 mol_num = 1
653 mol_records = []
654 end = False
655
656
657 for i in range(len(records)):
658
659 if records[i][:3] == 'END':
660 break
661
662
663 if records[i][:6] == 'MASTER':
664 break
665
666
667 if records[i][:6] == 'ENDMDL':
668 end = True
669
670
671 elif i < len(records)-1 and records[i][:3] == 'TER' and not records[i+1][:6] == 'HETATM':
672 end = True
673
674
675 elif i < len(records)-1 and records[i][:6] == 'HETATM' and records[i+1][:4] == 'ATOM':
676 end = True
677
678
679 if end:
680
681 yield mol_num, mol_records
682
683
684 mol_records = []
685
686
687 mol_num = mol_num + 1
688
689
690 end = False
691
692
693 continue
694
695
696 mol_records.append(records[i])
697
698
699 if len(mol_records):
700 yield mol_num, mol_records
701
702
704 """Convert the PDB chain ID into the molecule index in a regular way.
705
706 @keyword chain_id: The PDB chain ID string.
707 @type chain_id: str
708 @return: The corresponding molecule index.
709 @rtype: int
710 """
711
712
713 mol_index = 0
714
715
716 if chain_id:
717 mol_index = CHAIN_ID_LIST.index(chain_id)
718
719
720 return mol_index
721
722
724 """Convert the residue info into a dictionary of unique residues with numbers as keys.
725
726 @keyword res_nums: The list of residue numbers.
727 @type res_nums: list of int
728 @keyword res_names: The list of residue names matching the numbers.
729 @type res_names: list of str
730 @return: The dictionary of residue names with residue numbers as keys.
731 @rtype: dict of str
732 """
733
734
735 data = {}
736
737
738 for i in range(len(res_nums)):
739
740 if res_nums[i] in data:
741 continue
742
743
744 data[res_nums[i]] = res_names[i]
745
746
747 return data
748
749
751 """Check the validity of the data arrays in the given structure object.
752
753 @param struct: The structural object.
754 @type struct: Structure_container instance
755 """
756
757
758 num = len(struct.atom_name)
759
760
761 if len(struct.bonded) != num and len(struct.chain_id) != num and len(struct.element) != num and len(struct.pdb_record) != num and len(struct.res_name) != num and len(struct.res_num) != num and len(struct.seg_id) != num and len(struct.x) != num and len(struct.y) != num and len(struct.z) != num:
762 raise RelaxError("The structural data is invalid.")
763
764
766 """Determine the type of molecule.
767
768 @param mol: The molecule data container.
769 @type mol: MolContainer instance
770 """
771
772
773 aa = ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLU', 'GLN', 'GLY', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL']
774
775
776 mol.type = 'other'
777
778
779 for res in mol.res_name:
780
781 if res in aa:
782
783 mol.type = 'protein'
784 return
785
786
788 """Set up the connectivities for the protein.
789
790 @param mol: The molecule data container.
791 @type mol: MolContainer instance
792 """
793
794
795 curr_res_num = None
796 res_atoms = []
797
798
799 for i in range(len(mol.atom_num)):
800
801 if mol.res_num[i] != curr_res_num:
802
803 if len(res_atoms):
804 self._protein_intra_connect(mol, res_atoms)
805
806
807 curr_res_num = mol.res_num[i]
808
809
810 res_atoms = []
811
812
813 res_atoms.append(i)
814
815
816 if i == len(mol.atom_num) - 1 and len(res_atoms):
817 self._protein_intra_connect(mol, res_atoms)
818
819
821 """Set up the connectivities for the protein.
822
823 @param mol: The molecule data container.
824 @type mol: MolContainer instance
825 @param res_atoms: The list of atom indices corresponding to the residue.
826 @type res_atoms: list of int
827 """
828
829
830 indices = {
831 'N': None,
832 'C': None,
833 'O': None,
834 'CA': None,
835 'HN': None,
836 'H': None,
837 'HA': None
838 }
839
840
841 for index in res_atoms:
842 if mol.atom_name[index] in indices:
843 indices[mol.atom_name[index]] = index
844
845
846 pairs = [
847 ['N', 'HN'],
848 ['N', 'H'],
849 ['N', 'CA'],
850 ['CA', 'HA'],
851 ['CA', 'C'],
852 ['C', 'O']
853 ]
854
855
856 for pair in pairs:
857 if indices[pair[0]] != None and indices[pair[1]] != None:
858 mol.atom_connect(indices[pair[0]], indices[pair[1]])
859
860
862 """Convert the data into a format for writing to file.
863
864 @param data: The data to convert to the required format.
865 @type data: anything
866 @keyword format: The format to convert to. This can be 'str', 'float', or 'int'.
867 @type format: str
868 @return: The converted version of the data.
869 @rtype: str
870 """
871
872
873 if format == 'str':
874
875 if data == None:
876 data = ''
877
878
879 if not isinstance(data, str):
880 data = repr(data)
881
882
883 if format == 'float':
884
885 if data == None:
886 data = 0.0
887
888
889 if not isinstance(data, float):
890 data = float(data)
891
892
893 return data
894
895
896 - def _trim_helix(self, helix=None, trim_res_list=[], res_data=None):
897 """Trim the given helix based on the list of deleted residue numbers.
898
899 @keyword helix: The single helix metadata structure.
900 @type helix: list
901 @keyword trim_res_list: The list of residue numbers which no longer exist.
902 @type trim_res_list: list of int
903 @keyword res_data: The dictionary of residue names with residue numbers as keys.
904 @type res_data: dict of str
905 @return: The trimmed helix metadata structure, or None if the whole helix is to be deleted.
906 @rtype: list or None
907 """
908
909
910 start_res = helix[3]
911 end_res = helix[6]
912
913
914 trim_res_list_rev = deepcopy(trim_res_list)
915 trim_res_list_rev.reverse()
916
917
918 helix_res = list(range(start_res, end_res+1))
919
920
921 for res_num in trim_res_list:
922 if res_num == start_res:
923
924 helix_res.pop(0)
925
926
927 if len(helix_res) == 0:
928 break
929
930
931 start_res = helix_res[0]
932
933
934 if len(helix_res) == 0:
935 return None
936
937
938 for res_num in trim_res_list_rev:
939 if res_num == end_res:
940 helix_res.pop(-1)
941 end_res = helix_res[-1]
942
943
944 if start_res != helix[3]:
945 helix[3] = start_res
946 helix[2] = res_data[start_res]
947 if end_res != helix[6]:
948 helix[6] = end_res
949 helix[5] = res_data[end_res]
950
951
952 helix[-1] = len(helix_res)
953
954
955 return helix
956
957
958 - def _trim_sheet(self, sheet=None, trim_res_list=[], res_data=None):
959 """Trim the given sheet based on the list of deleted residue numbers.
960
961 @keyword sheet: The single sheet metadata structure.
962 @type sheet: list
963 @keyword trim_res_list: The list of residue numbers which no longer exist.
964 @type trim_res_list: list of int
965 @keyword res_data: The dictionary of residue names with residue numbers as keys.
966 @type res_data: dict of str
967 @return: The trimmed sheet metadata structure, or None if the whole sheet is to be deleted.
968 @rtype: list or None
969 """
970
971
972 start_res = sheet[5]
973 end_res = sheet[9]
974
975
976 trim_res_list_rev = deepcopy(trim_res_list)
977 trim_res_list_rev.reverse()
978
979
980 sheet_res = list(range(start_res, end_res+1))
981
982
983 for res_num in trim_res_list:
984 if res_num == start_res:
985
986 sheet_res.pop(0)
987
988
989 if len(sheet_res) == 0:
990 break
991
992
993 start_res = sheet_res[0]
994
995
996 if len(sheet_res) == 0:
997 return None
998
999
1000 for res_num in trim_res_list_rev:
1001 if res_num == end_res:
1002 sheet_res.pop(-1)
1003 end_res = sheet_res[-1]
1004
1005
1006 if start_res != sheet[5]:
1007 sheet[5] = start_res
1008 sheet[3] = res_data[start_res]
1009 if end_res != sheet[9]:
1010 sheet[9] = end_res
1011 sheet[7] = res_data[end_res]
1012
1013
1014 return sheet
1015
1016
1017 - def add_atom(self, mol_name=None, atom_name=None, res_name=None, res_num=None, pos=[None, None, None], element=None, atom_num=None, chain_id=None, segment_id=None, pdb_record=None):
1018 """Add a new atom to the structural data object.
1019
1020 @keyword mol_name: The name of the molecule.
1021 @type mol_name: str
1022 @keyword atom_name: The atom name, e.g. 'H1'.
1023 @type atom_name: str or None
1024 @keyword res_name: The residue name.
1025 @type res_name: str or None
1026 @keyword res_num: The residue number.
1027 @type res_num: int or None
1028 @keyword pos: The position vector of coordinates. If a rank-2 array is supplied, the length of the first dimension must match the number of models.
1029 @type pos: rank-1 or rank-2 array or list of float
1030 @keyword element: The element symbol.
1031 @type element: str or None
1032 @keyword atom_num: The atom number.
1033 @type atom_num: int or None
1034 @keyword chain_id: The chain identifier.
1035 @type chain_id: str or None
1036 @keyword segment_id: The segment identifier.
1037 @type segment_id: str or None
1038 @keyword pdb_record: The optional PDB record name, e.g. 'ATOM' or 'HETATM'.
1039 @type pdb_record: str or None
1040 """
1041
1042
1043 if len(self.structural_data) == 0:
1044 self.add_model()
1045
1046
1047 if is_float(pos[0]):
1048 if len(pos) != 3:
1049 raise RelaxError("The single atomic position %s must be a 3D list." % pos)
1050 else:
1051 if len(pos) != len(self.structural_data):
1052 raise RelaxError("The %s atomic positions does not match the %s models present." % (len(pos), len(self.structural_data)))
1053
1054
1055 for i in range(len(self.structural_data)):
1056
1057 model = self.structural_data[i]
1058
1059
1060 mol = self.get_molecule(mol_name, model=model.num)
1061
1062
1063 if mol == None:
1064 self.add_molecule(name=mol_name)
1065 mol = self.get_molecule(mol_name, model=model.num)
1066
1067
1068 if is_float(pos[0]):
1069 model_pos = pos
1070 else:
1071 model_pos = pos[i]
1072
1073
1074 mol.atom_add(atom_name=atom_name, res_name=res_name, res_num=res_num, pos=model_pos, element=element, atom_num=atom_num, chain_id=chain_id, segment_id=segment_id, pdb_record=pdb_record)
1075
1076
1077 - def add_model(self, model=None, coords_from=None):
1078 """Add a new model to the store.
1079
1080 The new model will be constructured with the structural information from the other models currently present. The coords_from argument allows the atomic positions to be taken from a certain model. If this argument is not set, then the atomic positions from the first model will be used.
1081
1082 @keyword model: The number of the model to create.
1083 @type model: int or None
1084 @keyword coords_from: The model number to take the coordinates from.
1085 @type coords_from: int or None
1086 @return: The model container.
1087 @rtype: ModelContainer instance
1088 """
1089
1090
1091 if model != None:
1092 for i in range(len(self.structural_data)):
1093 if model == self.structural_data[i].num:
1094 raise RelaxError("The model '%s' already exists." % model)
1095
1096
1097 self.structural_data.add_item(model_num=model)
1098
1099
1100 if coords_from == None:
1101 coords_from = self.structural_data[0].num
1102
1103
1104 for mol_name, res_num, res_name, atom_num, atom_name, element, pos in self.atom_loop(model_num=coords_from, mol_name_flag=True, res_num_flag=True, res_name_flag=True, atom_num_flag=True, atom_name_flag=True, element_flag=True, pos_flag=True):
1105
1106 self.add_atom(mol_name=mol_name, atom_name=atom_name, res_name=res_name, res_num=res_num, pos=pos, element=element, atom_num=atom_num)
1107
1108
1109 return self.structural_data[-1]
1110
1111
1113 """Add a new molecule to the store.
1114
1115 @keyword name: The molecule identifier string.
1116 @type name: str
1117 """
1118
1119
1120 if len(self.structural_data) == 0:
1121 self.add_model()
1122
1123
1124 for i in range(len(self.structural_data)):
1125
1126 self.structural_data[i].mol.add_item(mol_name=name, mol_cont=MolContainer())
1127
1128
1129 - def are_bonded(self, atom_id1=None, atom_id2=None):
1130 """Determine if two atoms are directly bonded to each other.
1131
1132 @keyword atom_id1: The molecule, residue, and atom identifier string of the first atom.
1133 @type atom_id1: str
1134 @keyword atom_id2: The molecule, residue, and atom identifier string of the second atom.
1135 @type atom_id2: str
1136 @return: True if the atoms are directly bonded.
1137 @rtype: bool
1138 """
1139
1140
1141 sel_obj1 = Selection(atom_id1)
1142 sel_obj2 = Selection(atom_id2)
1143
1144
1145 for mol in self.structural_data[0].mol:
1146 for i in range(len(mol.atom_num)):
1147 if not len(mol.bonded[i]):
1148 self._find_bonded_atoms(i, mol, radius=2)
1149
1150
1151 for mol in self.structural_data[0].mol:
1152
1153 if not sel_obj1.contains_mol(mol.mol_name):
1154 continue
1155 if not sel_obj2.contains_mol(mol.mol_name):
1156 continue
1157
1158
1159 index1 = None
1160 for i in range(len(mol.atom_num)):
1161
1162 if sel_obj1.contains_spin(mol.atom_num[i], mol.atom_name[i], mol.res_num[i], mol.res_name[i], mol.mol_name):
1163 index1 = i
1164 break
1165
1166
1167 index2 = None
1168 for i in range(len(mol.atom_num)):
1169
1170 if sel_obj2.contains_spin(mol.atom_num[i], mol.atom_name[i], mol.res_num[i], mol.res_name[i], mol.mol_name):
1171 index2 = i
1172 break
1173
1174
1175 if index1 < len(mol.bonded):
1176 if index2 in mol.bonded[index1]:
1177 return True
1178 else:
1179 return False
1180
1181
1182 - def atom_loop(self, atom_id=None, str_id=None, model_num=None, mol_name_flag=False, res_num_flag=False, res_name_flag=False, atom_num_flag=False, atom_name_flag=False, element_flag=False, pos_flag=False, index_flag=False, ave=False):
1183 """Generator function for looping over all atoms in the internal relax structural object.
1184
1185 This method should be designed as a U{generator<http://www.python.org/dev/peps/pep-0255/>}. It should loop over all atoms of the system yielding the following atomic information, if the corresponding flag is True, in tuple form:
1186
1187 1. Model number.
1188 2. Molecule name.
1189 3. Residue number.
1190 4. Residue name.
1191 5. Atom number.
1192 6. Atom name.
1193 7. The element name (its atomic symbol and optionally the isotope, e.g. 'N', 'Mg', '17O', '13C', etc).
1194 8. The position of the atom in Euclidean space.
1195
1196
1197 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be yielded.
1198 @type atom_id: str
1199 @keyword str_id: The structure identifier. This can be the file name, model number, or structure number. If None, then all structures will be looped over.
1200 @type str_id: str, int, or None
1201 @keyword model_num: Only loop over a specific model.
1202 @type model_num: int or None
1203 @keyword mol_name_flag: A flag which if True will cause the molecule name to be yielded.
1204 @type mol_name_flag: bool
1205 @keyword res_num_flag: A flag which if True will cause the residue number to be yielded.
1206 @type res_num_flag: bool
1207 @keyword res_name_flag: A flag which if True will cause the residue name to be yielded.
1208 @type res_name_flag: bool
1209 @keyword atom_num_flag: A flag which if True will cause the atom number to be yielded.
1210 @type atom_num_flag: bool
1211 @keyword atom_name_flag: A flag which if True will cause the atom name to be yielded.
1212 @type atom_name_flag: bool
1213 @keyword element_flag: A flag which if True will cause the element name to be yielded.
1214 @type element_flag: bool
1215 @keyword pos_flag: A flag which if True will cause the atomic position to be yielded.
1216 @type pos_flag: bool
1217 @keyword index_flag: A flag which if True will cause the atomic index to be yielded.
1218 @type index_flag: bool
1219 @keyword ave: A flag which if True will result in this method returning the average atom properties across all loaded structures.
1220 @type ave: bool
1221 @return: A tuple of atomic information, as described in the docstring.
1222 @rtype: tuple consisting of optional molecule name (str), residue number (int), residue name (str), atom number (int), atom name(str), element name (str), and atomic position (array of len 3).
1223 """
1224
1225
1226 if not len(self.structural_data):
1227 raise RelaxNoPdbError
1228
1229
1230 sel_obj = None
1231 if atom_id:
1232 sel_obj = Selection(atom_id)
1233
1234
1235 model = self.structural_data[0]
1236
1237
1238 for mol_index in range(len(model.mol)):
1239 mol = model.mol[mol_index]
1240
1241
1242 if sel_obj and not sel_obj.contains_mol(mol.mol_name):
1243 continue
1244
1245
1246 for i in range(len(mol.atom_name)):
1247
1248 if sel_obj and not sel_obj.contains_spin(mol.atom_num[i], mol.atom_name[i], mol.res_num[i], mol.res_name[i], mol.mol_name):
1249 continue
1250
1251
1252 res_num = mol.res_num[i]
1253 res_name = mol.res_name[i]
1254 atom_num = mol.atom_num[i]
1255 atom_name = mol.atom_name[i]
1256 element = mol.element[i]
1257
1258
1259 if pos_flag:
1260
1261 if ave:
1262
1263 pos = zeros(3, float64)
1264
1265
1266 for model in self.model_loop(model=model_num):
1267
1268 mol2 = model.mol[mol_index]
1269
1270
1271 if mol2.atom_num[i] != atom_num:
1272 raise RelaxError("The loaded structures do not contain the same atoms. The average structural properties can not be calculated.")
1273
1274
1275 pos = pos + array([mol2.x[i], mol2.y[i], mol2.z[i]], float64)
1276
1277
1278 pos = pos / len(self.structural_data)
1279
1280
1281 else:
1282
1283 pos = []
1284
1285
1286 for model in self.model_loop(model=model_num):
1287
1288 mol2 = model.mol[mol_index]
1289
1290
1291 pos.append([mol2.x[i], mol2.y[i], mol2.z[i]])
1292
1293
1294 pos = array(pos, float64)
1295
1296
1297 mol_name = mol.mol_name
1298
1299
1300 atomic_tuple = ()
1301 if mol_name_flag:
1302 atomic_tuple = atomic_tuple + (mol_name,)
1303 if res_num_flag:
1304 atomic_tuple = atomic_tuple + (res_num,)
1305 if res_name_flag:
1306 atomic_tuple = atomic_tuple + (res_name,)
1307 if atom_num_flag:
1308 atomic_tuple = atomic_tuple + (atom_num,)
1309 if atom_name_flag:
1310 atomic_tuple = atomic_tuple + (atom_name,)
1311 if element_flag:
1312 atomic_tuple = atomic_tuple + (element,)
1313 if pos_flag:
1314 atomic_tuple = atomic_tuple + (pos,)
1315 if index_flag:
1316 atomic_tuple += (i,)
1317
1318
1319 if len(atomic_tuple) == 1:
1320 atomic_tuple = atomic_tuple[0]
1321 yield atomic_tuple
1322
1323
1324 - def bond_vectors(self, attached_atom=None, model_num=None, mol_name=None, res_num=None, res_name=None, spin_num=None, spin_name=None, return_name=False, return_warnings=False):
1325 """Find the bond vector between the atoms of 'attached_atom' and 'atom_id'.
1326
1327 @keyword attached_atom: The name of the bonded atom.
1328 @type attached_atom: str
1329 @keyword model_num: The model of which to return the vectors from. If not supplied and multiple models exist, then vectors from all models will be returned.
1330 @type model_num: None or int
1331 @keyword mol_name: The name of the molecule that attached_atom belongs to.
1332 @type mol_name: str
1333 @keyword res_num: The number of the residue that attached_atom belongs to.
1334 @type res_num: str
1335 @keyword res_name: The name of the residue that attached_atom belongs to.
1336 @type res_name: str
1337 @keyword spin_num: The number of the spin that attached_atom is attached to.
1338 @type spin_num: str
1339 @keyword spin_name: The name of the spin that attached_atom is attached to.
1340 @type spin_name: str
1341 @keyword return_name: A flag which if True will cause the name of the attached atom to be returned together with the bond vectors.
1342 @type return_name: bool
1343 @keyword return_warnings: A flag which if True will cause warning messages to be returned.
1344 @type return_warnings: bool
1345 @return: The list of bond vectors for each model.
1346 @rtype: list of numpy arrays (or a tuple if return_name or return_warnings are set)
1347 """
1348
1349
1350 vectors = []
1351 attached_name = None
1352 warnings = None
1353
1354
1355 model = self.structural_data[0]
1356
1357
1358 for mol_index in range(len(model.mol)):
1359
1360 mol = model.mol[mol_index]
1361
1362
1363 if mol_name and mol_name != mol.mol_name:
1364 continue
1365
1366
1367 index = None
1368 for i in range(len(mol.atom_name)):
1369
1370 if (res_num != None and mol.res_num[i] != res_num) or (res_name != None and mol.res_name[i] != res_name):
1371 continue
1372
1373
1374 if (spin_num != None and mol.atom_num[i] != spin_num) or (spin_name != None and mol.atom_name[i] != spin_name):
1375 continue
1376
1377
1378 index = i
1379 break
1380
1381
1382 if index != None:
1383
1384 for model in self.model_loop(model=model_num):
1385
1386 mol = model.mol[mol_index]
1387
1388
1389 bonded_num, bonded_name, element, pos, attached_name, warnings = self._bonded_atom(attached_atom, index, mol)
1390
1391
1392 if (bonded_num, bonded_name, element) == (None, None, None):
1393 continue
1394
1395
1396 vector = array(pos, float64) - array([mol.x[index], mol.y[index], mol.z[index]], float64)
1397
1398
1399 vectors.append(vector)
1400
1401
1402 else:
1403 warnings = "Cannot find the atom in the structure"
1404
1405
1406 data = (vectors,)
1407 if return_name:
1408 data = data + (attached_name,)
1409 if return_warnings:
1410 data = data + (warnings,)
1411
1412
1413 return data
1414
1415
1416 - def connect_atom(self, mol_name=None, index1=None, index2=None):
1417 """Connect two atoms in the structural data object.
1418
1419 @keyword mol_name: The name of the molecule.
1420 @type mol_name: str
1421 @keyword index1: The global index of the first atom.
1422 @type index1: str
1423 @keyword index2: The global index of the first atom.
1424 @type index2: str
1425 """
1426
1427
1428 if self.get_molecule(mol_name) == None:
1429 self.add_molecule(name=mol_name)
1430
1431
1432 for model in self.structural_data:
1433
1434 mol = self.get_molecule(mol_name)
1435
1436
1437 mol.atom_connect(index1=index1, index2=index2)
1438
1439
1440 - def delete(self, atom_id=None):
1441 """Deletion of structural information.
1442
1443 @keyword atom_id: The molecule, residue, and atom identifier string. This matches the spin ID string format. If not given, then all structural data will be deleted.
1444 @type atom_id: str or None
1445 """
1446
1447
1448 if atom_id == None:
1449
1450 print("Deleting the following structural data:\n")
1451 print(self.structural_data)
1452
1453
1454 del self.structural_data
1455
1456
1457 self.structural_data = ModelList()
1458
1459
1460 else:
1461
1462 sel_obj = None
1463 if atom_id:
1464 sel_obj = Selection(atom_id)
1465
1466
1467 del_res_nums = []
1468 for model in self.model_loop():
1469
1470 for mol_index in range(len(model.mol)):
1471 mol = model.mol[mol_index]
1472
1473
1474 if sel_obj and not sel_obj.contains_mol(mol.mol_name):
1475 continue
1476
1477
1478 indices = []
1479 for i in self.atom_loop(atom_id=atom_id, model_num=model.num, index_flag=True):
1480 indices.append(i)
1481
1482
1483 res_data = self._residue_data(res_nums=mol.res_num, res_names=mol.res_name)
1484
1485
1486 indices.reverse()
1487 for i in indices:
1488 mol.atom_num.pop(i)
1489 mol.atom_name.pop(i)
1490 mol.bonded.pop(i)
1491 mol.chain_id.pop(i)
1492 mol.element.pop(i)
1493 mol.pdb_record.pop(i)
1494 mol.res_name.pop(i)
1495 res_num = mol.res_num.pop(i)
1496 mol.seg_id.pop(i)
1497 mol.x.pop(i)
1498 mol.y.pop(i)
1499 mol.z.pop(i)
1500
1501
1502 if res_num not in mol.res_num and res_num not in del_res_nums:
1503 del_res_nums.append(res_num)
1504
1505
1506 if not len(del_res_nums):
1507 return
1508
1509
1510 del_res_nums.reverse()
1511
1512
1513 del_helix_indices = []
1514 for i in range(len(self.helices)):
1515
1516 helix = self._trim_helix(helix=self.helices[i], trim_res_list=del_res_nums, res_data=res_data)
1517
1518
1519 if helix != None:
1520 self.helices[i] = helix
1521
1522
1523 else:
1524 del_helix_indices.append(i)
1525
1526
1527 del_helix_indices.reverse()
1528 for i in del_helix_indices:
1529 self.helices.pop(i)
1530
1531
1532 del_sheet_indices = []
1533 for i in range(len(self.sheets)):
1534
1535 sheet = self._trim_sheet(sheet=self.sheets[i], trim_res_list=del_res_nums, res_data=res_data)
1536
1537
1538 if sheet != None:
1539 self.sheets[i] = sheet
1540
1541
1542 else:
1543 del_sheet_indices.append(i)
1544
1545
1546 del_sheet_indices.reverse()
1547 for i in del_sheet_indices:
1548 self.sheets.pop(i)
1549
1550
1552 """Report if the structural data structure is empty or not.
1553
1554 @return: True if empty, False otherwise.
1555 @rtype: bool
1556 """
1557
1558
1559 if len(self.structural_data) == 0:
1560 return True
1561 else:
1562 return False
1563
1564
1565 - def from_xml(self, str_node, dir=None, file_version=1):
1566 """Recreate the structural object from the XML structural object node.
1567
1568 @param str_node: The structural object XML node.
1569 @type str_node: xml.dom.minicompat.Element instance
1570 @keyword dir: The name of the directory containing the results file.
1571 @type dir: str
1572 @keyword file_version: The relax XML version of the XML file.
1573 @type file_version: int
1574 """
1575
1576
1577 xml_to_object(str_node, self, file_version=file_version, blacklist=['model', 'displacements'])
1578
1579
1580 model_nodes = str_node.getElementsByTagName('model')
1581 self.structural_data.from_xml(model_nodes, file_version=file_version)
1582
1583
1584 disp_nodes = str_node.getElementsByTagName('displacements')
1585 if len(disp_nodes):
1586
1587 self.displacements = Displacements()
1588
1589
1590 self.displacements.from_xml(disp_nodes[0], file_version=file_version)
1591
1592
1594 """Return or create the model.
1595
1596 @param model: The model number.
1597 @type model: int or None
1598 @return: The ModelContainer corresponding to the model number or that newly created.
1599 @rtype: ModelContainer instance
1600 """
1601
1602
1603 if model == None and self.num_models() > 1:
1604 raise RelaxError("The target model cannot be determined as there are %s models already present." % self.num_modes())
1605
1606
1607 if model == None:
1608
1609 if self.num_models():
1610 self.structural_data.add_item()
1611
1612
1613 model_cont = self.structural_data[0]
1614
1615
1616 else:
1617
1618 found = False
1619 for model_cont in self.structural_data:
1620 if model_cont.num == model:
1621 found = True
1622 break
1623
1624
1625 if not found:
1626 self.structural_data.add_item(model)
1627 model_cont = self.structural_data[-1]
1628
1629
1631 """Return the molecule.
1632
1633 Only one model can be specified.
1634
1635
1636 @param molecule: The molecule name.
1637 @type molecule: int or None
1638 @keyword model: The model number.
1639 @type model: int or None
1640 @raises RelaxError: If the model is not specified and there is more than one model loaded.
1641 @return: The MolContainer corresponding to the molecule name and model number.
1642 @rtype: MolContainer instance or None
1643 """
1644
1645
1646 if model == None and self.num_models() > 1:
1647 raise RelaxError("The target molecule cannot be determined as there are %s models already present." % self.num_models())
1648
1649
1650 if not isinstance(model, int) and not model == None:
1651 raise RelaxNoneIntError
1652
1653
1654 if not len(self.structural_data):
1655 return
1656
1657
1658 for model_cont in self.model_loop(model):
1659
1660 for mol in model_cont.mol:
1661
1662 if mol.mol_name == molecule:
1663 return mol
1664
1665
1667 """Check if the molecule name exists.
1668
1669 @param name: The molecule name.
1670 @type name: str
1671 @return: True if the molecule exists, False otherwise.
1672 @rtype: bool
1673 """
1674
1675
1676 if not len(self.structural_data):
1677 return False
1678
1679
1680 for model_cont in self.model_loop():
1681
1682 for mol in model_cont.mol:
1683
1684 if mol.mol_name == name:
1685 return True
1686
1687
1688 return False
1689
1690
1691 - def load_gaussian(self, file_path, set_mol_name=None, set_model_num=None, verbosity=False):
1692 """Method for loading structures from a Gaussian log file.
1693
1694 @param file_path: The full path of the Gaussian log file.
1695 @type file_path: str
1696 @keyword set_mol_name: Set the names of the molecules which are loaded. If set to None, then the molecules will be automatically labelled based on the file name or other information.
1697 @type set_mol_name: None, str, or list of str
1698 @keyword set_model_num: Set the model number of the loaded molecule. If set to None, then the Gaussian log model numbers will be preserved, if they exist.
1699 @type set_model_num: None, int, or list of int
1700 @keyword verbosity: A flag which if True will cause messages to be printed.
1701 @type verbosity: bool
1702 @return: The status of the loading of the Gaussian log file.
1703 @rtype: bool
1704 """
1705
1706
1707 if verbosity:
1708 print("\nInternal relax Gaussian log parser.")
1709
1710
1711 if not access(file_path, F_OK):
1712
1713 return False
1714
1715
1716 path, file_name = os.path.split(file_path)
1717
1718
1719 path_abs = abspath(curdir) + sep + path
1720
1721
1722 if set_mol_name:
1723 if not isinstance(set_mol_name, list):
1724 set_mol_name = [set_mol_name]
1725 else:
1726 set_mol_name = [file_root(file_name) + '_mol1']
1727
1728
1729 if set_model_num:
1730 if not isinstance(set_model_num, list):
1731 set_model_num = [set_model_num]
1732 else:
1733 set_model_num = [1]
1734
1735
1736 for model_records in self._parse_models_gaussian(file_path):
1737 pass
1738
1739
1740 mol = MolContainer()
1741
1742
1743 mol.fill_object_from_gaussian(model_records)
1744
1745
1746 self.pack_structs([[mol]], orig_model_num=[1], set_model_num=set_model_num, orig_mol_num=[0], set_mol_name=set_mol_name, file_name=file_name, file_path=path, file_path_abs=path_abs)
1747
1748
1749 return True
1750
1751
1752 - def load_pdb(self, file_path, read_mol=None, set_mol_name=None, read_model=None, set_model_num=None, alt_loc=None, verbosity=False, merge=False):
1753 """Method for loading structures from a PDB file.
1754
1755 @param file_path: The full path of the PDB file.
1756 @type file_path: str
1757 @keyword read_mol: The molecule(s) to read from the file, independent of model. The molecules are determined differently by the different parsers, but are numbered consecutively from 1. If set to None, then all molecules will be loaded.
1758 @type read_mol: None, int, or list of int
1759 @keyword set_mol_name: Set the names of the molecules which are loaded. If set to None, then the molecules will be automatically labelled based on the file name or other information.
1760 @type set_mol_name: None, str, or list of str
1761 @keyword read_model: The PDB model to extract from the file. If set to None, then all models will be loaded.
1762 @type read_model: None, int, or list of int
1763 @keyword set_model_num: Set the model number of the loaded molecule. If set to None, then the PDB model numbers will be preserved, if they exist.
1764 @type set_model_num: None, int, or list of int
1765 @keyword alt_loc: The PDB ATOM record 'Alternate location indicator' field value to select which coordinates to use.
1766 @type alt_loc: str or None
1767 @keyword verbosity: A flag which if True will cause messages to be printed.
1768 @type verbosity: bool
1769 @keyword merge: A flag which if set to True will try to merge the PDB structure into the currently loaded structures.
1770 @type merge: bool
1771 @return: The status of the loading of the PDB file.
1772 @rtype: bool
1773 """
1774
1775
1776 if verbosity:
1777 print("\nInternal relax PDB parser.")
1778
1779
1780 if not access(file_path, F_OK):
1781
1782 return False
1783
1784
1785 path, file = os.path.split(file_path)
1786
1787
1788 path_abs = abspath(curdir) + sep + path
1789
1790
1791 if read_mol and not isinstance(read_mol, list):
1792 read_mol = [read_mol]
1793 if set_mol_name and not isinstance(set_mol_name, list):
1794 set_mol_name = [set_mol_name]
1795 if read_model and not isinstance(read_model, list):
1796 read_model = [read_model]
1797 if set_model_num and not isinstance(set_model_num, list):
1798 set_model_num = [set_model_num]
1799
1800
1801 pdb_file = open_read_file(file_path)
1802 pdb_lines = pdb_file.readlines()
1803 pdb_file.close()
1804
1805
1806 if pdb_lines == []:
1807 raise RelaxError("The PDB file is empty.")
1808
1809
1810 pdb_lines = self._parse_pdb_title(pdb_lines)
1811 pdb_lines = self._parse_pdb_prim_struct(pdb_lines)
1812 pdb_lines = self._parse_pdb_hetrogen(pdb_lines)
1813 pdb_lines = self._parse_pdb_ss(pdb_lines)
1814 pdb_lines = self._parse_pdb_connectivity_annotation(pdb_lines)
1815 pdb_lines = self._parse_pdb_misc(pdb_lines)
1816 pdb_lines = self._parse_pdb_transform(pdb_lines)
1817
1818
1819 model_index = 0
1820 orig_model_num = []
1821 mol_conts = []
1822 for model_num, model_records in self._parse_pdb_coord(pdb_lines):
1823
1824 if read_model and model_num not in read_model:
1825 continue
1826
1827
1828 orig_model_num.append(model_num)
1829
1830
1831 mol_conts.append([])
1832 mol_index = 0
1833 orig_mol_num = []
1834 new_mol_name = []
1835 for mol_num, mol_records in self._parse_mols(model_records):
1836
1837 if read_mol and mol_num not in read_mol:
1838 continue
1839
1840
1841 if set_mol_name:
1842 new_mol_name.append(set_mol_name[mol_index])
1843 else:
1844
1845 num_struct = 0
1846 if self.structural_data != None and len(self.structural_data) and (not set_model_num or (model_index <= len(set_model_num) and set_model_num[model_index] == self.structural_data[0].num)):
1847 num_struct = len(self.structural_data[0].mol)
1848
1849
1850 new_mol_name.append(file_root(file) + '_mol' + repr(mol_num+num_struct))
1851
1852
1853 orig_mol_num.append(mol_num)
1854
1855
1856 mol = MolContainer()
1857
1858
1859 mol.fill_object_from_pdb(mol_records, alt_loc_select=alt_loc)
1860
1861
1862 mol_conts[model_index].append(mol)
1863
1864
1865 mol_index = mol_index + 1
1866
1867
1868 model_index = model_index + 1
1869
1870
1871 if not len(mol_conts):
1872 warn(RelaxWarning("No structural data could be read from the file '%s'." % file_path))
1873 return False
1874
1875
1876 self.pack_structs(mol_conts, orig_model_num=orig_model_num, set_model_num=set_model_num, orig_mol_num=orig_mol_num, set_mol_name=new_mol_name, file_name=file, file_path=path, file_path_abs=path_abs, merge=merge)
1877
1878
1879 return True
1880
1881
1882 - def load_xyz(self, file_path, read_mol=None, set_mol_name=None, read_model=None, set_model_num=None, verbosity=False):
1883 """Method for loading structures from a XYZ file.
1884
1885 @param file_path: The full path of the XYZ file.
1886 @type file_path: str
1887 @keyword read_mol: The molecule(s) to read from the file, independent of model. The molecules are determined differently by the different parsers, but are numbered consecutively from 1. If set to None, then all molecules will be loaded.
1888 @type read_mol: None, int, or list of int
1889 @keyword set_mol_name: Set the names of the molecules which are loaded. If set to None, then the molecules will be automatically labelled based on the file name or other information.
1890 @type set_mol_name: None, str, or list of str
1891 @keyword read_model: The XYZ model to extract from the file. If set to None, then all models will be loaded.
1892 @type read_model: None, int, or list of int
1893 @keyword set_model_num: Set the model number of the loaded molecule. If set to None, then the XYZ model numbers will be preserved, if they exist.
1894 @type set_model_num: None, int, or list of int
1895 @keyword verbosity: A flag which if True will cause messages to be printed.
1896 @type verbosity: bool
1897 @return: The status of the loading of the XYZ file.
1898 @rtype: bool
1899 """
1900
1901
1902 if verbosity:
1903 print("\nInternal relax XYZ parser.")
1904
1905
1906 if not access(file_path, F_OK):
1907
1908 return False
1909
1910
1911 path, file = os.path.split(file_path)
1912
1913
1914 path_abs = abspath(curdir) + sep + path
1915
1916
1917 if read_mol and not isinstance(read_mol, list):
1918 read_mol = [read_mol]
1919 if set_mol_name and not isinstance(set_mol_name, list):
1920 set_mol_name = [set_mol_name]
1921 if read_model and not isinstance(read_model, list):
1922 read_model = [read_model]
1923 if set_model_num and not isinstance(set_model_num, list):
1924 set_model_num = [set_model_num]
1925
1926
1927 mol_index = 0
1928 model_index = 0
1929 xyz_model_increment = 0
1930 orig_model_num = []
1931 mol_conts = []
1932 orig_mol_num = []
1933 new_mol_name = []
1934 for model_records in self._parse_models_xyz(file_path):
1935
1936 xyz_model_increment = xyz_model_increment +1
1937
1938
1939 if read_model and xyz_model_increment not in read_model:
1940 continue
1941
1942
1943 orig_model_num.append(model_index)
1944
1945
1946 if read_mol and mol_index not in read_mol:
1947 continue
1948
1949
1950 if set_mol_name:
1951 new_mol_name.append(set_mol_name[mol_index])
1952 else:
1953 if mol_index == 0:
1954
1955 new_mol_name.append(file_root(file) + '_mol' + repr(mol_index+1))
1956
1957
1958 orig_mol_num.append(mol_index)
1959
1960
1961 mol = MolContainer()
1962
1963
1964 mol.fill_object_from_xyz(model_records)
1965
1966
1967 mol_conts.append([])
1968 mol_conts[model_index].append(mol)
1969
1970
1971 mol_index = mol_index + 1
1972
1973
1974 model_index = model_index + 1
1975
1976
1977 orig_mol_num = [0]
1978 self.pack_structs(mol_conts, orig_model_num=orig_model_num, set_model_num=set_model_num, orig_mol_num=orig_mol_num, set_mol_name=new_mol_name, file_name=file, file_path=path, file_path_abs=path_abs)
1979
1980
1981 return True
1982
1983
1985 """Generator method for looping over the models in numerical order.
1986
1987 @keyword model: Limit the loop to a single number.
1988 @type model: int
1989 @return: The model structural object.
1990 @rtype: ModelContainer container
1991 """
1992
1993
1994 if model:
1995 for i in range(len(self.structural_data)):
1996 if self.structural_data[i].num == model:
1997 yield self.structural_data[i]
1998
1999
2000 else:
2001
2002 model_nums = []
2003 for i in range(len(self.structural_data)):
2004 if self.structural_data[i].num != None:
2005 model_nums.append(self.structural_data[i].num)
2006
2007
2008 if model_nums:
2009 model_nums.sort()
2010
2011
2012 for model_num in model_nums:
2013
2014 for i in range(len(self.structural_data)):
2015
2016 if self.structural_data[i].num == model_num:
2017 yield self.structural_data[i]
2018
2019
2020 if not model_nums:
2021 yield self.structural_data[0]
2022
2023
2025 """Method for returning the number of models.
2026
2027 @return: The number of models in the structural object.
2028 @rtype: int
2029 """
2030
2031 return len(self.structural_data)
2032
2033
2035 """Method for returning the number of molecules.
2036
2037 @return: The number of molecules in the structural object.
2038 @rtype: int
2039 """
2040
2041
2042 if self.empty():
2043 return 0
2044
2045
2046 self.validate()
2047
2048
2049 return len(self.structural_data[0].mol)
2050
2051
2052 - def pack_structs(self, data_matrix, orig_model_num=None, set_model_num=None, orig_mol_num=None, set_mol_name=None, file_name=None, file_path=None, file_path_abs=None, merge=False):
2053 """From the given structural data, expand the structural data data structure.
2054
2055 @param data_matrix: A matrix of structural objects.
2056 @type data_matrix: list of lists of structural objects
2057 @keyword orig_model_num: The original model numbers (for storage).
2058 @type orig_model_num: list of int
2059 @keyword set_model_num: The new model numbers (for model renumbering).
2060 @type set_model_num: list of int
2061 @keyword orig_mol_num: The original molecule numbers (for storage).
2062 @type orig_mol_num: list of int
2063 @keyword set_mol_name: The new molecule names.
2064 @type set_mol_name: list of str
2065 @keyword file_name: The name of the file from which the molecular data has been extracted.
2066 @type file_name: None or str
2067 @keyword file_path: The full path to the file specified by 'file_name'.
2068 @type file_path: None or str
2069 @keyword file_path_abs: The absolute path to the file specified by 'file_name'. This is a fallback mechanism in case results or save files are located somewhere other than the working directory.
2070 @type file_path_abs: None or str
2071 @keyword merge: A flag which if set to True will try to merge the structure into the currently loaded structures.
2072 @type merge: bool
2073 """
2074
2075
2076 if len(orig_model_num) != len(data_matrix):
2077 raise RelaxError("Structural data mismatch, %s original models verses %s in the structural data." % (len(orig_model_num), len(data_matrix)))
2078
2079
2080 if len(orig_mol_num) != len(data_matrix[0]):
2081 raise RelaxError("Structural data mismatch, %s original molecules verses %s in the structural data." % (len(orig_mol_num), len(data_matrix[0])))
2082
2083
2084 if not set_model_num:
2085 set_model_num = orig_model_num
2086
2087
2088 if len(set_model_num) != len(data_matrix):
2089 raise RelaxError("Failure of the mapping of new model numbers, %s new model numbers verses %s models in the structural data." % (len(set_model_num), len(data_matrix)))
2090
2091
2092 if len(set_mol_name) != len(data_matrix[0]):
2093 raise RelaxError("Failure of the mapping of new molecule names, %s new molecule names verses %s molecules in the structural data." % (len(set_mol_name), len(data_matrix[0])))
2094
2095
2096 for i in range(len(set_model_num)):
2097
2098 if merge:
2099 continue
2100
2101
2102 if not set_model_num[i] in self.structural_data.current_models:
2103 continue
2104
2105
2106 index = self.structural_data.current_models.index(set_model_num[i])
2107 for j in range(len(self.structural_data[index].mol)):
2108 if self.structural_data[index].mol[j].mol_name in set_mol_name:
2109 raise RelaxError("The molecule '%s' of model %s already exists." % (self.structural_data[i].mol[j].mol_name, self.structural_data[i].num))
2110
2111
2112 for i in range(len(set_model_num)):
2113
2114 if set_model_num[i] not in self.structural_data.current_models:
2115
2116 self.structural_data.add_item(set_model_num[i])
2117
2118
2119 model = self.structural_data[-1]
2120
2121
2122 else:
2123 model = self.structural_data[self.structural_data.current_models.index(set_model_num[i])]
2124
2125
2126 for j in range(len(set_mol_name)):
2127
2128 if merge:
2129 print("Merging with model %s of molecule '%s' (from the original molecule number %s of model %s)" % (set_model_num[i], set_mol_name[j], orig_mol_num[j], orig_model_num[i]))
2130 else:
2131 print("Adding molecule '%s' to model %s (from the original molecule number %s of model %s)" % (set_mol_name[j], set_model_num[i], orig_mol_num[j], orig_model_num[i]))
2132
2133
2134 index = len(model.mol)
2135 if merge:
2136 index -= 1
2137
2138
2139 if model.num != self.structural_data[0].num and self.structural_data[0].mol[index].mol_name != set_mol_name[j]:
2140 raise RelaxError("The new molecule name of '%s' in model %s does not match the corresponding molecule's name of '%s' in model %s." % (set_mol_name[j], set_model_num[i], self.structural_data[0].mol[index].mol_name, self.structural_data[0].num))
2141
2142
2143 if merge:
2144 mol = model.mol.merge_item(mol_name=set_mol_name[j], mol_cont=data_matrix[i][j])
2145 else:
2146 mol = model.mol.add_item(mol_name=set_mol_name[j], mol_cont=data_matrix[i][j])
2147
2148
2149 mol.mol_name = set_mol_name[j]
2150 mol.file_name = file_name
2151 mol.file_path = file_path
2152 mol.file_path_abs = file_path_abs
2153 mol.file_mol_num = orig_mol_num[j]
2154 mol.file_model = orig_model_num[i]
2155
2156
2157 - def rotate(self, R=None, origin=None, model=None, atom_id=None):
2158 """Rotate the structural information about the given origin.
2159
2160 @keyword R: The forwards rotation matrix.
2161 @type R: numpy 3D, rank-2 array
2162 @keyword origin: The origin of the rotation.
2163 @type origin: numpy 3D, rank-1 array
2164 @keyword model: The model to rotate. If None, all models will be rotated.
2165 @type model: int
2166 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be used.
2167 @type atom_id: str or None
2168 """
2169
2170
2171 sel_obj = None
2172 if atom_id:
2173 sel_obj = Selection(atom_id)
2174
2175
2176 for model_cont in self.model_loop(model):
2177
2178 for mol in model_cont.mol:
2179
2180 if sel_obj and not sel_obj.contains_mol(mol.mol_name):
2181 continue
2182
2183
2184 for i in range(len(mol.atom_num)):
2185
2186 if sel_obj and not sel_obj.contains_spin(mol.atom_num[i], mol.atom_name[i], mol.res_num[i], mol.res_name[i], mol.mol_name):
2187 continue
2188
2189
2190 vect = array([mol.x[i], mol.y[i], mol.z[i]], float64) - origin
2191
2192
2193 rot_vect = dot(R, vect)
2194
2195
2196 pos = rot_vect + origin
2197 mol.x[i] = pos[0]
2198 mol.y[i] = pos[1]
2199 mol.z[i] = pos[2]
2200
2201
2202 - def target_mol_name(self, set=None, target=None, index=None, mol_num=None, file=None):
2203 """Add the new molecule name to the target data structure.
2204
2205 @keyword set: The list of new molecule names. If not supplied, the names will be generated from the file name.
2206 @type set: None or list of str
2207 @keyword target: The target molecule name data structure to which the new name will be appended.
2208 @type target: list
2209 @keyword index: The molecule index, matching the set argument.
2210 @type index: int
2211 @keyword mol_num: The molecule number.
2212 @type mol_num: int
2213 @keyword file: The name of the file, excluding all directories.
2214 @type file: str
2215 """
2216
2217
2218 if set:
2219 target.append(set[index])
2220 else:
2221
2222 target.append(file_root(file) + '_mol' + repr(mol_num))
2223
2224
2225 - def translate(self, T=None, model=None, atom_id=None):
2226 """Displace the structural information by the given translation vector.
2227
2228 @keyword T: The translation vector.
2229 @type T: numpy 3D, rank-1 array
2230 @keyword model: The model to rotate. If None, all models will be rotated.
2231 @type model: int
2232 @keyword atom_id: The molecule, residue, and atom identifier string. Only atoms matching this selection will be used.
2233 @type atom_id: str or None
2234 """
2235
2236
2237 sel_obj = None
2238 if atom_id:
2239 sel_obj = Selection(atom_id)
2240
2241
2242 for model_cont in self.model_loop(model):
2243
2244 for mol in model_cont.mol:
2245
2246 if sel_obj and not sel_obj.contains_mol(mol.mol_name):
2247 continue
2248
2249
2250 for i in range(len(mol.atom_num)):
2251
2252 if sel_obj and not sel_obj.contains_spin(mol.atom_num[i], mol.atom_name[i], mol.res_num[i], mol.res_name[i], mol.mol_name):
2253 continue
2254
2255
2256 mol.x[i] = mol.x[i] + T[0]
2257 mol.y[i] = mol.y[i] + T[1]
2258 mol.z[i] = mol.z[i] + T[2]
2259
2260
2261 - def to_xml(self, doc, element):
2262 """Prototype method for converting the structural object to an XML representation.
2263
2264 @param doc: The XML document object.
2265 @type doc: xml.dom.minidom.Document instance
2266 @param element: The element to add the alignment tensors XML element to.
2267 @type element: XML element object
2268 """
2269
2270
2271 str_element = doc.createElement('structure')
2272 element.appendChild(str_element)
2273
2274
2275 str_element.setAttribute('desc', 'Structural information')
2276
2277
2278 if not self.structural_data.is_empty():
2279 self.structural_data.to_xml(doc, str_element)
2280
2281
2282 metadata = ['helices', 'sheets']
2283 for name in metadata:
2284
2285 if not hasattr(self, name):
2286 continue
2287
2288
2289 obj = getattr(self, name)
2290
2291
2292 sub_elem = doc.createElement(name)
2293 str_element.appendChild(sub_elem)
2294
2295
2296 object_to_xml(doc, sub_elem, value=obj)
2297
2298
2299 if hasattr(self, 'displacements'):
2300
2301 disp_element = doc.createElement('displacements')
2302 str_element.appendChild(disp_element)
2303
2304
2305 disp_element.setAttribute('desc', 'The rotational and translational displacements between models')
2306
2307
2308 self.displacements.to_xml(doc, disp_element)
2309
2310
2312 """Check that the models are consistent with each other.
2313
2314 This checks that the primary structure is identical between the models.
2315 """
2316
2317
2318 print("Validating models:")
2319
2320
2321 for i in range(len(self.structural_data)):
2322
2323 if len(self.structural_data[0].mol) != len(self.structural_data[i].mol):
2324 raise RelaxError("The number of molecules, %i, in model %i does not match the %i molecules of the first model." % (len(self.structural_data[i].mol), self.structural_data[i].num, len(self.structural_data[0].mol)))
2325
2326
2327 for j in range(len(self.structural_data[i].mol)):
2328
2329 mol = self.structural_data[i].mol[j]
2330 mol_ref = self.structural_data[0].mol[j]
2331
2332
2333 if mol.mol_name != mol_ref.mol_name:
2334 raise RelaxError("The molecule name '%s' of model %i does not match the name '%s' of the first model." % (mol.mol_name, self.structural_data[i].num, mol_ref.mol_name))
2335
2336
2337 for k in range(len(mol.atom_name)):
2338
2339 atom = "%-6s%5s %4s%1s%3s %1s%4s%1s %8s%8s%8s%6.2f%6.2f %4s%2s%2s" % ('ATOM', mol.atom_num[k], self._translate(mol.atom_name[k]), '', self._translate(mol.res_name[k]), self._translate(mol.chain_id[k]), self._translate(mol.res_num[k]), '', '#', '#', '#', 1.0, 0, self._translate(mol.seg_id[k]), self._translate(mol.element[k]), '')
2340 atom_ref = "%-6s%5s %4s%1s%3s %1s%4s%1s %8s%8s%8s%6.2f%6.2f %4s%2s%2s" % ('ATOM', mol_ref.atom_num[k], self._translate(mol_ref.atom_name[k]), '', self._translate(mol_ref.res_name[k]), self._translate(mol_ref.chain_id[k]), self._translate(mol_ref.res_num[k]), '', '#', '#', '#', 1.0, 0, self._translate(mol_ref.seg_id[k]), self._translate(mol_ref.element[k]), '')
2341
2342
2343 if atom != atom_ref:
2344 print(atom)
2345 print(atom_ref)
2346 raise RelaxError("The atoms of model %i do not match the first model." % self.structural_data[i].num)
2347
2348
2349 print("\tAll models are consistent")
2350
2351
2353 """Method for the creation of a PDB file from the structural data.
2354
2355 A number of PDB records including HET, HETNAM, FORMUL, HELIX, SHEET, HETATM, TER, CONECT, MASTER, and END are created. To create the non-standard residue records HET, HETNAM, and FORMUL, the data structure 'het_data' is created. It is an array of arrays where the first dimension corresponds to a different residue and the second dimension has the elements:
2356
2357 0. Residue number.
2358 1. Residue name.
2359 2. Chain ID.
2360 3. Total number of atoms in the residue.
2361 4. Number of H atoms in the residue.
2362 5. Number of C atoms in the residue.
2363
2364
2365 @param file: The PDB file object. This object must be writable.
2366 @type file: file object
2367 @keyword model_num: The model to place into the PDB file. If not supplied, then all models will be placed into the file.
2368 @type model_num: None or int
2369 """
2370
2371
2372 self.validate()
2373
2374
2375 print("\nCreating the PDB records\n")
2376
2377
2378 print("REMARK")
2379 pdb_write.remark(file, num=4, remark="This file complies with format v. 3.30, Jul-2011.")
2380 pdb_write.remark(file, num=40, remark="Created by relax (http://www.nmr-relax.com).")
2381 num_remark = 2
2382
2383
2384 model_records = False
2385 for model in self.model_loop():
2386 if hasattr(model, 'num') and model.num != None:
2387 model_records = True
2388
2389
2390
2391
2392
2393
2394
2395 het_data = []
2396 het_data_coll = []
2397
2398
2399 index = 0
2400 for mol in self.structural_data[0].mol:
2401
2402 self._validate_data_arrays(mol)
2403
2404
2405 het_data.append([])
2406
2407
2408 for i in range(len(mol.atom_name)):
2409
2410 if mol.pdb_record[i] != 'HETATM' or mol.res_name[i] == None:
2411 continue
2412
2413
2414
2415 if not het_data[index] or not mol.res_num[i] == het_data[index][-1][0]:
2416 het_data[index].append([mol.res_num[i], mol.res_name[i], CHAIN_ID_LIST[index], 0, []])
2417
2418
2419 if het_data[index][-1][2] == None:
2420 het_data[index][-1][2] = ''
2421
2422
2423 het_data[index][-1][3] = het_data[index][-1][3] + 1
2424
2425
2426 entry = False
2427 for j in range(len(het_data[index][-1][4])):
2428 if mol.element[i] == het_data[index][-1][4][j][0]:
2429 entry = True
2430
2431
2432 if not entry:
2433 het_data[index][-1][4].append([mol.element[i], 0])
2434
2435
2436 for j in range(len(het_data[index][-1][4])):
2437 if mol.element[i] == het_data[index][-1][4][j][0]:
2438 het_data[index][-1][4][j][1] = het_data[index][-1][4][j][1] + 1
2439
2440
2441 for i in range(len(het_data[index])):
2442
2443 found = False
2444 for j in range(len(het_data_coll)):
2445
2446 if het_data[index][i][0] != het_data_coll[j][0]:
2447 continue
2448
2449
2450 if het_data_coll[j][2] != het_data[index][i][2]:
2451 continue
2452
2453
2454 if het_data_coll[j][1] != het_data[index][i][1]:
2455 raise RelaxError("The " + repr(het_data[index][i][1]) + " residue name of hetrogen " + repr(het_data[index][i][0]) + " " + het_data[index][i][1] + " of structure " + repr(index) + " does not match the " + repr(het_data_coll[j][1]) + " name of the previous structures.")
2456 elif het_data_coll[j][3] != het_data[index][i][3]:
2457 raise RelaxError("The " + repr(het_data[index][i][3]) + " atoms of hetrogen " + repr(het_data_coll[j][0]) + " " + het_data_coll[j][1] + " of structure " + repr(index) + " does not match the " + repr(het_data_coll[j][3]) + " of the previous structures.")
2458 elif het_data_coll[j][4] != het_data[index][i][4]:
2459 raise RelaxError("The atom counts " + repr(het_data[index][i][4]) + " for the hetrogen residue " + repr(het_data_coll[j][0]) + " " + het_data_coll[j][1] + " of structure " + repr(index) + " do not match the counts " + repr(het_data_coll[j][4]) + " of the previous structures.")
2460
2461
2462 found = True
2463
2464
2465 if not found:
2466 het_data_coll.append(het_data[index][i])
2467
2468
2469 index = index + 1
2470
2471
2472
2473
2474
2475
2476 print("HET")
2477
2478
2479 for het in het_data_coll:
2480 pdb_write.het(file, het_id=het[1], chain_id=het[2], seq_num=het[0], num_het_atoms=het[3])
2481
2482
2483
2484
2485
2486
2487 print("HETNAM")
2488
2489
2490 residues = []
2491 for het in het_data_coll:
2492
2493 if het[1] in residues:
2494 continue
2495 else:
2496 residues.append(het[1])
2497
2498
2499 chemical_name = self._get_chemical_name(het[1])
2500 if not chemical_name:
2501 chemical_name = 'Unknown'
2502
2503
2504 pdb_write.hetnam(file, het_id=het[1], text=chemical_name)
2505
2506
2507
2508
2509
2510
2511 print("FORMUL")
2512
2513
2514 residues = []
2515 for i in range(len(het_data_coll)):
2516
2517 het = het_data_coll[i]
2518
2519
2520 if het[1] in residues:
2521 continue
2522 else:
2523 residues.append(het[1])
2524
2525
2526 formula = ''
2527
2528
2529 for atom_count in het[4]:
2530 formula = formula + atom_count[0] + repr(atom_count[1])
2531
2532
2533 pdb_write.formul(file, comp_num=i+1, het_id=het[1], text=formula)
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543 if hasattr(self, 'helices') and len(self.helices):
2544
2545 print("HELIX")
2546
2547
2548 index = 1
2549 for helix_id, init_chain_id, init_res_name, init_seq_num, end_chain_id, end_res_name, end_seq_num, helix_class, length in self.helices:
2550 pdb_write.helix(file, ser_num=index, helix_id=helix_id, init_chain_id=init_chain_id, init_res_name=init_res_name, init_seq_num=init_seq_num, end_chain_id=end_chain_id, end_res_name=end_res_name, end_seq_num=end_seq_num, helix_class=helix_class, length=length)
2551 index += 1
2552
2553
2554
2555
2556 if hasattr(self, 'sheets') and len(self.sheets):
2557
2558 print("SHEET")
2559
2560
2561 index = 1
2562 for strand, sheet_id, num_strands, init_res_name, init_chain_id, init_seq_num, init_icode, end_res_name, end_chain_id, end_seq_num, end_icode, sense, cur_atom, cur_res_name, cur_chain_id, cur_res_seq, cur_icode, prev_atom, prev_res_name, prev_chain_id, prev_res_seq, prev_icode in self.sheets:
2563 pdb_write.sheet(file, strand=strand, sheet_id=sheet_id, num_strands=num_strands, init_res_name=init_res_name, init_chain_id=init_chain_id, init_seq_num=init_seq_num, init_icode=init_icode, end_res_name=end_res_name, end_chain_id=end_chain_id, end_seq_num=end_seq_num, end_icode=end_icode, sense=sense, cur_atom=cur_atom, cur_res_name=cur_res_name, cur_chain_id=cur_chain_id, cur_res_seq=cur_res_seq, cur_icode=cur_icode, prev_atom=prev_atom, prev_res_name=prev_res_name, prev_chain_id=prev_chain_id, prev_res_seq=prev_res_seq, prev_icode=prev_icode)
2564 index += 1
2565
2566
2567
2568
2569
2570
2571
2572 for model in self.model_loop(model_num):
2573
2574 num_hetatm = 0
2575 num_atom = 0
2576 num_ter = 0
2577 ser_num = 1
2578
2579
2580
2581
2582 if model_records:
2583
2584 print("\nMODEL %s" % model.num)
2585
2586
2587 pdb_write.model(file, serial=model.num)
2588
2589
2590
2591
2592
2593
2594 index = 0
2595 for mol in model.mol:
2596
2597 print("ATOM, HETATM, TER")
2598
2599
2600 atom_record = False
2601 for i in range(len(mol.atom_name)):
2602
2603 if mol.pdb_record[i] in [None, 'ATOM']:
2604 atom_record = True
2605
2606
2607 atom_num = mol.atom_num[i]
2608 if atom_num == None:
2609 atom_num = i + 1
2610
2611
2612
2613 if len(mol.atom_name[i]) == 1:
2614 atom_name = " %s" % mol.atom_name[i]
2615 else:
2616 atom_name = "%s" % mol.atom_name[i]
2617
2618
2619 pdb_write.atom(file, serial=atom_num, name=atom_name, res_name=mol.res_name[i], chain_id=CHAIN_ID_LIST[index], res_seq=mol.res_num[i], x=mol.x[i], y=mol.y[i], z=mol.z[i], occupancy=1.0, temp_factor=0, element=mol.element[i])
2620 num_atom += 1
2621 ser_num += 1
2622
2623
2624 ter_num = atom_num + 1
2625 ter_name = mol.res_name[i]
2626 ter_chain_id = CHAIN_ID_LIST[index]
2627 ter_res_num = mol.res_num[i]
2628
2629
2630 if atom_record:
2631 pdb_write.ter(file, serial=ser_num, res_name=ter_name, chain_id=CHAIN_ID_LIST[index], res_seq=ter_res_num)
2632 num_ter += 1
2633 ser_num += 1
2634
2635
2636 count_shift = False
2637 for i in range(len(mol.atom_name)):
2638
2639 if mol.pdb_record[i] == 'HETATM':
2640
2641 atom_num = mol.atom_num[i]
2642 if atom_num == None:
2643 atom_num = i + 1
2644
2645
2646 if atom_record and atom_num == ter_num:
2647 count_shift = True
2648 if atom_record and count_shift:
2649 atom_num += 1
2650
2651
2652 pdb_write.hetatm(file, serial=ser_num, name=self._translate(mol.atom_name[i]), res_name=mol.res_name[i], chain_id=CHAIN_ID_LIST[index], res_seq=mol.res_num[i], x=mol.x[i], y=mol.y[i], z=mol.z[i], occupancy=1.0, temp_factor=0.0, element=mol.element[i])
2653 num_hetatm += 1
2654 ser_num += 1
2655
2656
2657 index += 1
2658
2659
2660
2661
2662
2663 if model_records:
2664 print("ENDMDL")
2665 pdb_write.endmdl(file)
2666
2667
2668
2669
2670
2671
2672 print("CONECT")
2673
2674
2675 num_conect = 0
2676
2677
2678 atom_counts = [0]
2679 index = 0
2680 for mol in self.structural_data[0].mol:
2681 if index == 0:
2682 atom_counts.append(len(mol.atom_name))
2683 else:
2684 atom_counts.append(atom_counts[index] + len(mol.atom_name))
2685 index += 1
2686
2687
2688 index = 0
2689 for mol in self.structural_data[0].mol:
2690
2691 for i in range(len(mol.atom_name)):
2692
2693 if not len(mol.bonded[i]):
2694 continue
2695
2696
2697 flush = 0
2698 bonded_index = 0
2699 bonded = ['', '', '', '']
2700 bonded_shifted = ['', '', '', '']
2701
2702
2703 for j in range(len(mol.bonded[i])):
2704
2705 if j == len(mol.bonded[i])-1:
2706 flush = True
2707
2708
2709 if bonded_index == 3:
2710 flush = True
2711
2712
2713 bonded[bonded_index] = mol.bonded[i][j]
2714
2715
2716 bonded_index = bonded_index + 1
2717
2718
2719 if flush:
2720
2721 for k in range(4):
2722 if bonded[k] != '':
2723 if mol.atom_num[bonded[k]] != None:
2724 bonded[k] = mol.atom_num[bonded[k]]
2725 else:
2726 bonded[k] = bonded[k] + 1
2727 bonded_shifted[k] = bonded[k] + atom_counts[index]
2728
2729
2730 pdb_write.conect(file, serial=i+1+atom_counts[index], bonded1=bonded_shifted[0], bonded2=bonded_shifted[1], bonded3=bonded_shifted[2], bonded4=bonded_shifted[3])
2731
2732
2733 flush = False
2734 bonded_index = 0
2735 bonded = ['', '', '', '']
2736
2737
2738 num_conect = num_conect + 1
2739
2740
2741 index += 1
2742
2743
2744
2745
2746
2747 print("\nMASTER")
2748 pdb_write.master(file, num_het=len(het_data_coll), num_coord=num_atom+num_hetatm, num_ter=num_ter, num_conect=num_conect)
2749
2750
2751
2752
2753
2754 print("END")
2755 pdb_write.end(file)
2756
2757
2759 """Check the integrity of the structural data.
2760
2761 The number of molecules must be the same in all models.
2762 """
2763
2764
2765 num_mols = len(self.structural_data[0].mol)
2766
2767
2768 for i in range(1, len(self.structural_data)):
2769
2770 model_i = self.structural_data[i]
2771
2772
2773 if num_mols != len(model_i.mol):
2774 raise RelaxError("The structural object is not valid - the number of molecules is not the same for all models.")
2775
2776
2777 for j in range(len(model_i.mol)):
2778 if model_i.mol[j].mol_name != self.structural_data[0].mol[j].mol_name:
2779 raise RelaxError("The molecule name '%s' of model %s does not match the corresponding molecule '%s' of model %s." % (model_i.mol[j].mol_name, model_i.num, self.structural_data[0].mol[j].mol_name, self.structural_data[0].num))
2780