dcatoolkit 0.2.1__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: dcatoolkit
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Collection of useful modules and representations for managing DCA output data.
5
5
  Author-email: Raheel Syed Ahmed <raheelsyedahmed@gmail.com>
6
6
  Maintainer-email: Raheel Syed Ahmed <raheelsyedahmed@gmail.com>
@@ -52,6 +52,7 @@ Requires-Dist: pdoc; extra == "docs"
52
52
  Requires-Dist: numpydoc; extra == "docs"
53
53
  Provides-Extra: lint
54
54
  Requires-Dist: ruffle; extra == "lint"
55
+ Dynamic: license-file
55
56
 
56
57
  # dcatoolkit
57
58
  Collection of useful modules and representations for managing DCA output data.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dcatoolkit"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "Collection of useful modules and representations for managing DCA output data."
9
9
  keywords = ["dca", "toolkit", "DI", "coevolution"]
10
10
 
@@ -1,5 +1,5 @@
1
1
 
2
- __version__ = "0.2.1"
2
+ __version__ = "0.2.3"
3
3
  from .representation import Pairs, DirectInformationData, StructureInformation, ResidueAlignment, MMCIFInformation, PDBInformation
4
4
  from .analytics import MSATools
5
5
 
@@ -1,8 +1,11 @@
1
1
  import re
2
2
  from collections import Counter
3
- from typing import Optional, Union
3
+ from collections.abc import Callable
4
+ from typing import Optional, Union, Literal
4
5
  import string
5
6
  import io
7
+ import numpy as np
8
+ import numpy.typing as npt
6
9
 
7
10
  class MSATools:
8
11
  """
@@ -122,6 +125,27 @@ class MSATools:
122
125
  kept_entries.append((header, sequence))
123
126
  return kept_entries
124
127
 
128
+ def gap_proportion(self, agg_func: Callable[..., float | int]=np.mean, axis: Literal[0, 1] = 0) -> float | int:
129
+ """
130
+ Evaluates gap frequency per alignment position, or column, in the MSA.
131
+
132
+ Parameters
133
+ ----------
134
+ agg_func: function = np.mean
135
+ The aggregation function applied to get the expected result, usually a mean, max, or min value, of the gap frequencies present per alignment position in the MSA.
136
+ axis: int or str
137
+ When axis is 0, the aggregation function is applied per column for entries from every row in that column. When axis is 1, the aggregation function is applied per row for entries from every column in that row.
138
+
139
+ Return
140
+ ------
141
+ float
142
+ A numerical value determined by the aggregation function supplied over the gap frequencies of the alignment positions in the MSA.
143
+ """
144
+ sequence_matrix = self.as_matrix()
145
+ non_alpha_counts = np.sum(~np.char.isalpha(sequence_matrix), axis)
146
+ non_alpha_counts = non_alpha_counts / sequence_matrix.shape[axis]
147
+ return agg_func(non_alpha_counts)
148
+
125
149
  def write(self, destination: Union[str, io.IOBase]) -> None:
126
150
  """
127
151
  Writes this MSA's headers and sequences to the destination specified.
@@ -149,6 +173,28 @@ class MSATools:
149
173
  destination.write(sequence)
150
174
  destination.write("\n")
151
175
 
176
+ def as_matrix(self) -> npt.NDArray:
177
+ """
178
+ Represents the MSA as a numpy matrix of sequences.
179
+
180
+ Returns
181
+ -------
182
+ npt.NDArray
183
+ A matrix of "number of sequences" rows and "number of alignment positions" columns. Each cell is the sequence character for that sequence at that position.
184
+ """
185
+ return np.array([list(seq) for _, seq in self.MSA])
186
+
187
+ def __str__(self) -> str:
188
+ """
189
+ Returns the sequences present in the loaded MSA in string format.
190
+
191
+ Returns
192
+ -------
193
+ str
194
+ Each sequence in the instance separated with newline characters.
195
+ """
196
+ return "\n".join([seq for _, seq in self.MSA])
197
+
152
198
  def __len__(self):
153
199
  """
154
200
  Returns the number of sequences, and equivalently, the number of headers in the MSA.
@@ -6,6 +6,7 @@ import biotite.structure as struc
6
6
  import biotite.structure.io.pdbx as pdbx
7
7
  import biotite.structure.io.pdb as pdb
8
8
  import biotite.database.rcsb as rcsb
9
+ from biotite.sequence import ProteinSequence
9
10
 
10
11
  from collections.abc import Iterable
11
12
  from typing import Optional, Union, Literal, overload
@@ -195,6 +196,8 @@ class ResidueAlignment:
195
196
  The sequence of the domain in the query HMM corresponding to this alignment.
196
197
  protein_text : str
197
198
  The sequence of the protein target sequence corresponding to this alignment.
199
+ valid_residues : list of tuple of int, str
200
+ A list of tuples that contain first residue index then residue name (e.g. [(1, 'A'), (2, 'W'), (3, 'C')] )
198
201
 
199
202
  Attributes
200
203
  ----------
@@ -205,12 +208,73 @@ class ResidueAlignment:
205
208
  protein_to_domain : dict[int, int]
206
209
  A dictionary allowing for mapping from indices corresponding to the protein target sequence to the query HMM and Multiple Sequence Alignment.
207
210
  """
208
- def __init__(self, domain_name: str, protein_name: str, domain_start: int, protein_start: int, domain_text: str, protein_text: str) -> None:
211
+ def __init__(self, domain_name: str, protein_name: str, domain_start: int, protein_start: int, domain_text: str, protein_text: str, valid_residues: Optional[list[tuple[int, str]]]=None) -> None:
209
212
  self.domain_name = domain_name
210
213
  self.protein_name = protein_name
211
- self.set_reference_mapping(domain_start, protein_start, domain_text, protein_text)
214
+ self.valid_residues = valid_residues
215
+ if valid_residues:
216
+ self._set_restricted_reference_mapping(domain_start, protein_start, domain_text, protein_text, valid_residues)
217
+ else:
218
+ self._set_reference_mapping(domain_start, protein_start, domain_text, protein_text)
212
219
 
213
- def set_reference_mapping(self, domain_start: int, protein_start: int, domain_text: str, protein_text: str) -> None:
220
+ def _set_restricted_reference_mapping(self, domain_start: int, protein_start: int, domain_text: str, protein_text: str, valid_residues: list[tuple[int, str]]) -> None:
221
+ """
222
+ Set values for reference_mapping and mapping dictionaries, domain_to_protein and protein_to_domain.
223
+
224
+ Parameters
225
+ ----------
226
+ valid_residues : list of tuple of int, str
227
+ List of valid residues, non-missing residues in a structure, in the format of (seq_id, residue_name). These are iteratively selected in the order of the sequence to map to.
228
+
229
+ Notes
230
+ -----
231
+ For details on `domain_start`, `protein_start`, `domain_text`, `protein_text`, please refer to the `ResidueAlignment` docstring.
232
+
233
+ Returns
234
+ -------
235
+ None
236
+ """
237
+ invalid_chars = [".", "_", "-"]
238
+ # Convert text to list variant for iteration
239
+ domain_sequence = list(domain_text)
240
+ protein_sequence = list(protein_text)
241
+ # Store mapping values per iteration here.
242
+ mapping_entries = []
243
+
244
+ # Go through aligned sequences and append data concerning domain index, domain residue, protein residue, and protein index per valid aligned residues.
245
+ for domain_aa, protein_aa in zip(domain_sequence, protein_sequence):
246
+ mapping_entry = []
247
+ if domain_aa not in invalid_chars:
248
+ mapping_entry.append(domain_start)
249
+ domain_start += 1
250
+ else:
251
+ mapping_entry.append(pd.NA)
252
+ mapping_entry.append(domain_aa)
253
+
254
+ if protein_aa not in invalid_chars:
255
+ while len(valid_residues) >= protein_start:
256
+ prot_index, valid_residue = valid_residues.pop(protein_start-1)
257
+ if protein_aa.lower() == valid_residue.lower():
258
+ mapping_entry.append(protein_aa)
259
+ mapping_entry.append(prot_index)
260
+ break
261
+ else:
262
+ # Default case for if valid residues are missing towards the end.
263
+ mapping_entry.append(protein_aa)
264
+ mapping_entry.append(pd.NA)
265
+ else:
266
+ mapping_entry.append(protein_aa)
267
+ mapping_entry.append(pd.NA)
268
+ mapping_entries.append(mapping_entry)
269
+
270
+ self.reference_mapping = pd.DataFrame(mapping_entries, columns=['domain_index', 'domain_residue', 'protein_residue', 'protein_index'])
271
+ self.reference_mapping = self.reference_mapping.astype({'domain_index': pd.Int32Dtype(), 'protein_index': pd.Int32Dtype(), 'domain_residue': pd.StringDtype(), 'protein_residue': pd.StringDtype()})
272
+ reference_mapping_notna = self.reference_mapping.dropna()
273
+
274
+ self.domain_to_protein = dict(zip(reference_mapping_notna.domain_index, reference_mapping_notna.protein_index))
275
+ self.protein_to_domain = dict(zip(reference_mapping_notna.protein_index, reference_mapping_notna.domain_index))
276
+
277
+ def _set_reference_mapping(self, domain_start: int, protein_start: int, domain_text: str, protein_text: str) -> None:
214
278
  """
215
279
  Set values for reference_mapping and mapping dictionaries, domain_to_protein and protein_to_domain.
216
280
 
@@ -222,25 +286,26 @@ class ResidueAlignment:
222
286
  -------
223
287
  None
224
288
  """
289
+ invalid_chars = [".", "_", "-"]
225
290
  # Convert text to list variant for iteration
226
291
  domain_sequence = list(domain_text)
227
292
  protein_sequence = list(protein_text)
228
-
293
+ # Store mapping values per iteration here.
229
294
  mapping_entries = []
230
295
 
231
- for i in range(len(domain_sequence)):
296
+ for domain_aa, protein_aa in zip(domain_sequence, protein_sequence):
232
297
  mapping_entry = []
233
298
  # Check to see if domain residue is valid, if so, we can assign the proper index.
234
- if domain_sequence[i] != '.':
299
+ if domain_aa not in invalid_chars:
235
300
  mapping_entry.append(domain_start)
236
301
  domain_start += 1
237
302
  else:
238
303
  mapping_entry.append(pd.NA)
239
304
  # Assign the values of the residues mapped together.
240
- mapping_entry.append(domain_sequence[i])
241
- mapping_entry.append(protein_sequence[i])
305
+ mapping_entry.append(domain_aa)
306
+ mapping_entry.append(protein_aa)
242
307
  # Check to see if protein residue is valid, if so, we can assign the proper index.
243
- if protein_sequence[i] != '-':
308
+ if protein_aa not in invalid_chars:
244
309
  mapping_entry.append(protein_start)
245
310
  protein_start += 1
246
311
  else:
@@ -282,7 +347,7 @@ class ResidueAlignment:
282
347
  89
283
348
  """
284
349
  # Read the alignment file and parse the important information from each alignment entry.
285
- alignment_entries = ResidueAlignment.read_align_file(align_filepath)
350
+ alignment_entries = ResidueAlignment._read_align_file(align_filepath)
286
351
  hmm_entry, protein_entry = alignment_entries
287
352
  domain_name, domain_start, domain_text, _ = hmm_entry
288
353
  protein_name, protein_start, protein_text, _ = protein_entry
@@ -294,7 +359,7 @@ class ResidueAlignment:
294
359
  return ResidueAlignment(domain_name, protein_name, domain_start, protein_start, domain_text, protein_text)
295
360
 
296
361
  @staticmethod
297
- def read_align_file(align_filepath: str) -> list[list[str]]:
362
+ def _read_align_file(align_filepath: str) -> list[list[str]]:
298
363
  """
299
364
  Reads standard align file, where a scan file is selected for a particular domain and processed into an align file format. Details are present in produce_align_from_scan().
300
365
 
@@ -546,7 +611,7 @@ class DirectInformationData:
546
611
  return results
547
612
 
548
613
  @staticmethod
549
- def get_dist_commands(model1: str | int, model2: str | int, chain1: str, chain2: str, pairs: npt.NDArray, ca_only: bool=True, auth_res_ids=False) -> list[str]:
614
+ def get_dist_commands(model1: str | int, model2: str | int, chain1: str, chain2: str, pairs: npt.NDArray, ca_only: bool=True, auth_res_ids: bool=False) -> list[str]:
550
615
  """
551
616
  Get UCSF Chimera commands for displaying distance commands for usage in displaying distances between residue pairs. Options are present for alpha-carbon to alpha-carbon distance or for specified atom to specified atom distance.
552
617
 
@@ -770,9 +835,9 @@ class MMCIFInformation(StructureInformation):
770
835
  self.full_sequences = pdbx.get_sequence(pdbx_file)
771
836
  non_hetero_structure = self.structure[self.structure.hetero == False]
772
837
  self.non_missing_sequences = {str(chain): str(sequence) for (chain, sequence) in list(zip(struc.get_chains(non_hetero_structure), struc.to_sequence(non_hetero_structure)[0]))}
773
- self.generate_auth_info()
838
+ self._generate_auth_info()
774
839
 
775
- def generate_auth_info(self) -> None:
840
+ def _generate_auth_info(self) -> None:
776
841
  """
777
842
  Ran as part of constructor function. Generates information needed to access auth information including auth_seq_id and auth_asym_id, which correspond to alternative chain ids and alternative residue indices.
778
843
 
@@ -786,44 +851,51 @@ class MMCIFInformation(StructureInformation):
786
851
  """
787
852
  if len(self.pdbx_file.keys()) > 0:
788
853
  self.first_block = list(self.pdbx_file)[0]
789
- self.atom_site_category = self.pdbx_file[self.first_block].get('atom_site')
790
- self.chain_auth_dict = {}
791
- self.auth_chain_dict = {}
792
- self.res_auth_dict = {}
793
- if self.atom_site_category:
794
- group_pdbs = []
795
- seq_ids = []
796
- asym_ids = []
797
- auth_seq_ids = []
798
- auth_asym_ids = []
799
- model_nums = []
800
- for col_name, col in self.atom_site_category.items():
801
- if col_name == 'group_PDB':
802
- group_pdbs = col.as_array()
803
- elif col_name == 'label_seq_id':
804
- seq_ids = col.as_array()
805
- elif col_name == 'label_asym_id':
806
- asym_ids = col.as_array()
807
- elif col_name == 'auth_seq_id':
808
- auth_seq_ids = col.as_array()
809
- elif col_name == 'auth_asym_id':
810
- auth_asym_ids = col.as_array()
811
- elif col_name == 'pdbx_PDB_model_num':
812
- model_nums = col.as_array()
813
-
814
- atom_site_data = np.unique(np.column_stack((group_pdbs, seq_ids, asym_ids, auth_seq_ids, auth_asym_ids, model_nums)), axis=0)
815
- atom_site_data = atom_site_data[atom_site_data[:,5] == str(self.model_num)]
816
- self.atom_data = atom_site_data[atom_site_data[:,0] == "ATOM"]
817
- self.het_atom_data = atom_site_data[atom_site_data[:,0] == "HETATM"]
818
- self.unique_chains = np.unique(self.atom_data[:,2])
854
+ atom_site_category = self.pdbx_file[self.first_block].get('atom_site')
855
+ self.chain_auth_dict: dict[str, str] = {}
856
+ self.auth_chain_dict: dict[str, str] = {}
857
+ if atom_site_category:
858
+ categories = ['group_PDB', 'label_seq_id', 'label_asym_id', 'auth_seq_id', 'auth_asym_id', 'pdbx_PDB_model_num']
859
+ atom_site_data = np.column_stack([atom_site_category[category].as_array() for category in categories])
860
+ _, idx = np.unique(atom_site_data, axis=0, return_index=True)
861
+ atom_site_data = atom_site_data[np.sort(idx)]
862
+ atom_data = atom_site_data[atom_site_data[:,0] == "ATOM"]
863
+ self.unique_chains = np.unique(atom_data[:,2])
819
864
  for unique_chain in self.unique_chains:
820
- unique_entry = self.atom_data[self.atom_data[:,2] == unique_chain][0]
865
+ unique_entry = atom_data[atom_data[:,2] == unique_chain][0]
821
866
  self.chain_auth_dict[unique_entry[2]] = unique_entry[4]
822
867
  self.auth_chain_dict[unique_entry[4]] = unique_entry[2]
823
- self.res_auth_dict[unique_entry[2]] = unique_entry[[1,3]].astype('int')
868
+ self.atom_site_df = pd.DataFrame(np.column_stack([atom_site_category[category].as_array() for category in atom_site_category.keys()]), columns=atom_site_category.keys())
869
+ type_conversion_dict = {'label_seq_id': 'int64', 'auth_seq_id': 'int64', 'id': 'int64', 'Cartn_x': 'float', 'Cartn_y': 'float','Cartn_z': 'float', 'B_iso_or_equiv': 'float'}
870
+ self.atom_df = self.atom_site_df[self.atom_site_df['group_PDB'] == 'ATOM'].astype(type_conversion_dict)
871
+
872
+ def get_start_res_id(self, chain_id: str, get_auth_res_ids: bool=False, auth_chain_id_supplied: bool=False) -> int:
873
+ """
874
+ Gets starting residue id of the specified chain excluding heteroatom group entries.
875
+
876
+ Parameters
877
+ ----------
878
+ chain_id : str
879
+ The chain id supplied and selected for from the structure.
880
+ get_auth_res_ids : bool
881
+ True if you want alt_ids for residues indices, False if cif residue indexing is needed.
882
+ auth_chain_id_supplied : bool
883
+ If True, the chain_id supplied is the auth chain id found on the RCSB website.
884
+
885
+ Returns
886
+ -------
887
+ int
888
+ The residue id of the first atom in the chain provided.
889
+ """
890
+ if auth_chain_id_supplied:
891
+ chain_df = self.atom_df[self.atom_df['auth_asym_id'] == chain_id]
824
892
  else:
825
- self.atom_site_category = None
826
-
893
+ chain_df = self.atom_df[self.atom_df['label_asym_id'] == chain_id]
894
+ if get_auth_res_ids:
895
+ return chain_df['auth_seq_id'][0]
896
+ else:
897
+ return chain_df['label_seq_id'][0]
898
+
827
899
  def get_full_sequence(self, chain_id: str, auth_chain_id_supplied: bool=False) -> str:
828
900
  """
829
901
  Get full sequence, including missing residues, from the specified chain off of RCSB.
@@ -867,7 +939,7 @@ class MMCIFInformation(StructureInformation):
867
939
  else:
868
940
  return self.non_missing_sequences[chain_id]
869
941
 
870
- def get_chain_specific_structure(self, ca_only: bool, chain1: str, chain2: str, remove_hetero=True, auth_chain_id_supplied: bool=False) -> tuple:
942
+ def get_chain_specific_structure(self, ca_only: bool, chain_id: str, remove_hetero=True, auth_chain_id_supplied: bool=False):
871
943
  """
872
944
  Subsets structure attribute to select for chain specific portions of the structure.
873
945
 
@@ -875,10 +947,8 @@ class MMCIFInformation(StructureInformation):
875
947
  ----------
876
948
  ca_only : bool
877
949
  If true, the structure will also be subsetted for atom entries where the atom_name annotation is "CA" (referring to alpha-carbons)
878
- chain1 : str
879
- Chain id corresponding to the first column of residues in the structure.
880
- chain2 : str
881
- Chain id corresponding to the second column of residues in the structure.
950
+ chain_id : str
951
+ The name of the chain to be selected for within the structure.
882
952
  remove_hetero : bool, default=True
883
953
  If true, the structure will also be subsetted for atom entries where the hetero annotation is False, thus removing heteroatoms.
884
954
  auth_chain_id_supplied : bool
@@ -890,9 +960,7 @@ class MMCIFInformation(StructureInformation):
890
960
  Two AtomArrays that refer to atoms in the first chain and second chain, respectively without accounting for the presence of heteroatoms if `remove_hetero` is True.
891
961
  """
892
962
  if auth_chain_id_supplied:
893
- chain1 = self.auth_chain_dict[chain1]
894
- chain2 = self.auth_chain_dict[chain2]
895
-
963
+ chain_id = self.auth_chain_dict[chain_id]
896
964
  selected_structure = self.structure
897
965
  if remove_hetero:
898
966
  # Remove hetero atoms via hetero column of structure ndarray
@@ -900,64 +968,110 @@ class MMCIFInformation(StructureInformation):
900
968
  if ca_only:
901
969
  # Consider selection of alpha-carbon atoms only
902
970
  selected_structure = selected_structure[selected_structure.atom_name == "CA"]
903
- chain1_structure = selected_structure[selected_structure.chain_id == chain1]
904
- chain2_structure = selected_structure[selected_structure.chain_id == chain2]
905
- return (chain1_structure, chain2_structure)
906
-
907
- def generate_dist_matrix(self, ca_only: bool, chain1: str, chain2: str, auth_chain_id_supplied: bool=False):
971
+ chain_structure = selected_structure[selected_structure.chain_id == chain_id]
972
+ return chain_structure
973
+
974
+ def get_chain_site_data(self, ca_only: bool, chain_id: str, remove_hetero=True, auth_chain_id_supplied: bool=False):
908
975
  """
909
- Generates distance matrix between two chains in the structure attribute.
976
+ Subsets the atom_site dataframe to get atom information where the conditions are met.
910
977
 
911
978
  Parameters
912
979
  ----------
913
980
  ca_only : bool
914
- If True, only atoms that have the name "CA" are selected in the chains the distance matrix is calculated between.
915
- chain1 : str
916
- Chain id corresponding to the first column of residues in the structure.
917
- chain2 : str
918
- Chain id corresponding to the first column of residues in the structure.
981
+ If true, the dataframe will also be subsetted for atom entries where the label_atom_id annotation is "CA" (referring to alpha-carbons)
982
+ chain_id : str
983
+ The name of the chain to be selected for within the dataframe.
984
+ remove_hetero : bool, default=True
985
+ If true, the dataframe will also be subsetted for atom entries where the group_PDB annotation is ATOM rather than HETATM, thus removing heteroatoms.
986
+ auth_chain_id_supplied : bool
987
+ If True, the chain_id supplied is the auth chain id found on the RCSB website.
988
+ """
989
+ atom_df = self.atom_df.copy()
990
+ if ca_only:
991
+ atom_df = atom_df[atom_df['label_atom_id'] == 'CA']
992
+ if remove_hetero:
993
+ atom_df = atom_df[atom_df['group_PDB'] == 'ATOM']
994
+ if auth_chain_id_supplied:
995
+ return atom_df[atom_df['auth_asym_id'] == chain_id]
996
+ else:
997
+ return atom_df[atom_df['label_asym_id'] == chain_id]
998
+
999
+ def get_seq_id_mapping(self, chain_id: str, seq_to_auth: bool, auth_chain_id_supplied: bool=False) -> dict[int, int]:
1000
+ """
1001
+ Gets mapping from auth seq ids to label seq ids or vice-versa.
1002
+
1003
+ Parameters
1004
+ ----------
1005
+ chain_id : str
1006
+ Chain id of the chain addressed for determining residue index mappings.
1007
+ seq_to_auth : bool
1008
+ If True, this indicates the mapping uses the label_seq_id as a key and the auth_seq_id as a value. Otherwise, keys and values are switched.
919
1009
  auth_chain_id_supplied : bool
920
1010
  If True, the chain_id supplied is the auth chain id found on the RCSB website.
921
1011
 
922
1012
  Returns
923
1013
  -------
924
- tuple of biotite.structure.AtomArray, biotite.structure.AtomArray, numpy.ndarray
925
- Tuple containing the chain 1 structure, the chain 2 structure, and the distance matrix of chain 1 and chain 2's pairwise distances.
1014
+ dict of int, int
1015
+ Dictionary with either label seq id or auth seq id as a key and the other as a value. The directionality is dependent on seq_to_auth.
926
1016
  """
927
- chain1_structure, chain2_structure = self.get_chain_specific_structure(ca_only, chain1, chain2, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
928
- dist_matrix = cdist(chain1_structure.coord, chain2_structure.coord)
929
- return (chain1_structure, chain2_structure, dist_matrix)
1017
+ chain_df = self.get_chain_site_data(ca_only=True, chain_id=chain_id, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1018
+ if seq_to_auth:
1019
+ return dict(zip(chain_df['label_seq_id'], chain_df['auth_seq_id']))
1020
+ else:
1021
+ return dict(zip(chain_df['auth_seq_id'], chain_df['label_seq_id']))
930
1022
 
931
- def get_shift_values(self, chain1: str, chain2: str, auth_chain_id_supplied: bool=False) -> tuple[int, int]:
1023
+ def get_valid_chain_residues(self, chain_id: str, auth_seq_id: bool=False, auth_chain_id_supplied: bool=False) -> list[tuple[int, str]]:
932
1024
  """
933
- Get shift values needed for production of auth residue ids.
1025
+ Gets valid indexing for residues of a specified chain. This is directly analogous to get_non_missing_sequence, does not contain missing residues, and provides the corresponding indices as well.
1026
+
1027
+ Parameters
1028
+ ----------
1029
+ chain_id : str
1030
+ Chain id of the chain to be selected from the structure. This chain's sequence and corresponding residue indices are what are exclusively selected for.
1031
+ auth_seq_id: bool
1032
+ If True, the seq_ids that are the first element of the tuples in the returned list are auth_seq_ids.
1033
+ auth_chain_id_supplied : bool
1034
+ If True, the chain_id supplied is the auth chain id found on the RCSB website.
934
1035
 
1036
+ Returns
1037
+ -------
1038
+ list of tuple of int, str
1039
+ A list of residue information in sequential order reflecting the structure. The list consists of tuple elements where each tuple is the residue index and its corresponding one-letter amino acid.
1040
+ """
1041
+ chain_structure = self.get_chain_specific_structure(ca_only=True, chain_id=chain_id, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1042
+ res_ids = chain_structure.res_id.tolist()
1043
+ res_names = chain_structure.res_name
1044
+ if auth_seq_id:
1045
+ seq_id_mapping = self.get_seq_id_mapping(chain_id=chain_id, seq_to_auth=True, auth_chain_id_supplied=auth_chain_id_supplied)
1046
+ auth_res_ids = [seq_id_mapping[res_id] for res_id in res_ids]
1047
+ return list(zip(auth_res_ids, map(lambda symbol: ProteinSequence.convert_letter_3to1(symbol), res_names)))
1048
+ else:
1049
+ return list(zip(res_ids, map(lambda symbol: ProteinSequence.convert_letter_3to1(symbol), res_names)))
1050
+
1051
+ def generate_dist_matrix(self, ca_only: bool, chain1: str, chain2: str, auth_chain_id_supplied: bool=False):
1052
+ """
1053
+ Generates distance matrix between two chains in the structure attribute.
1054
+
935
1055
  Parameters
936
1056
  ----------
1057
+ ca_only : bool
1058
+ If True, only atoms that have the name "CA" are selected in the chains the distance matrix is calculated between.
937
1059
  chain1 : str
938
- Name of the chain id present referring to the second column of residues.
1060
+ Chain id corresponding to the first column of residues in the structure.
939
1061
  chain2 : str
940
- Name of the chain id present referring to the second column of residues.
1062
+ Chain id corresponding to the first column of residues in the structure.
941
1063
  auth_chain_id_supplied : bool
942
1064
  If True, the chain_id supplied is the auth chain id found on the RCSB website.
943
1065
 
944
1066
  Returns
945
1067
  -------
946
- (shift1, shift2) : tuple of int, int
947
- Tuple containing both shift values, the difference between the auth_res_id and res_id.
1068
+ tuple of biotite.structure.AtomArray, biotite.structure.AtomArray, numpy.ndarray
1069
+ Tuple containing the chain 1 structure, the chain 2 structure, and the distance matrix of chain 1 and chain 2's pairwise distances.
948
1070
  """
949
- if auth_chain_id_supplied:
950
- chain1 = self.auth_chain_dict[chain1]
951
- chain2 = self.auth_chain_dict[chain2]
952
-
953
- shift1 = 0
954
- shift2 = 0
955
- if self.atom_site_category:
956
- shift1 = self.res_auth_dict[chain1][1] - self.res_auth_dict[chain1][0]
957
- shift2 = self.res_auth_dict[chain2][1] - self.res_auth_dict[chain2][0]
958
- return shift1, shift2
959
- else:
960
- return shift1, shift2
1071
+ chain1_structure = self.get_chain_specific_structure(ca_only=ca_only, chain_id=chain1, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1072
+ chain2_structure = self.get_chain_specific_structure(ca_only=ca_only, chain_id=chain2, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1073
+ dist_matrix = cdist(chain1_structure.coord, chain2_structure.coord)
1074
+ return (chain1_structure, chain2_structure, dist_matrix)
961
1075
 
962
1076
  def get_min_dist_atom_info(self, pairs: npt.NDArray, chain1: str, chain2: str, auth_chain_id_supplied: bool=False) -> npt.NDArray:
963
1077
  """
@@ -979,8 +1093,8 @@ class MMCIFInformation(StructureInformation):
979
1093
  min_dist_pairs_atoms_arr : numpy.ndarray
980
1094
  Structured ndarray that has residue indices, auth residue indices (corresponding to the protein numbering), and atomic names in the format {'names': ['residue1','residue2','auth_residue1','auth_residue2','atom_name1','atom_name2'], 'formats': [int,int,int,int,'<U10','<U10']}
981
1095
  """
982
- shift1, shift2 = self.get_shift_values(chain1, chain2, auth_chain_id_supplied=auth_chain_id_supplied)
983
- chain1_structure, chain2_structure = self.get_chain_specific_structure(ca_only=False, chain1=chain1, chain2=chain2, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1096
+ chain1_structure = self.get_chain_specific_structure(ca_only=False, chain_id=chain1, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
1097
+ chain2_structure = self.get_chain_specific_structure(ca_only=False, chain_id=chain2, remove_hetero=True, auth_chain_id_supplied=auth_chain_id_supplied)
984
1098
  min_dist_pairs_atoms = []
985
1099
  for row in pairs:
986
1100
  # Obtain structure information for chains 1 and 2
@@ -993,13 +1107,15 @@ class MMCIFInformation(StructureInformation):
993
1107
  ind = np.unravel_index(np.argmin(dist_matrix), dist_matrix.shape)
994
1108
  # Use the indices to access the atom in the atom array and get the correct atom name.
995
1109
  # Generate the auth ids of the residues in the pairs ndarray
996
- auth_res_id1 = row['residue1'] + shift1
997
- auth_res_id2 = row['residue2'] + shift2
1110
+ seq_mapping_chain1 = self.get_seq_id_mapping(chain_id=chain1, seq_to_auth=True, auth_chain_id_supplied=auth_chain_id_supplied)
1111
+ seq_mapping_chain2 = self.get_seq_id_mapping(chain_id=chain2, seq_to_auth=True, auth_chain_id_supplied=auth_chain_id_supplied)
1112
+ auth_res_id1 = seq_mapping_chain1[row['residue1']]
1113
+ auth_res_id2 = seq_mapping_chain2[row['residue2']]
998
1114
  min_dist_pairs_atoms.append((row['residue1'], row['residue2'], auth_res_id1, auth_res_id2, chain1_res1_structure[ind[0]].atom_name, chain2_res2_structure[ind[1]].atom_name))
999
1115
  min_dist_pairs_atoms_arr = np.array(min_dist_pairs_atoms, dtype={'names': ['residue1','residue2','auth_residue1','auth_residue2','atom_name1','atom_name2'], 'formats': [int,int,int,int,'<U10','<U10']})
1000
1116
  return min_dist_pairs_atoms_arr
1001
1117
 
1002
- def get_contacts(self, ca_only: bool, threshold: float, chain1: str, chain2: str, auth_contacts: bool=False, auth_chain_id_supplied: bool=False) -> set[tuple[int, int]]:
1118
+ def get_contacts(self, ca_only: bool, threshold: float, chain1: str, chain2: str, auth_seq_id: bool=False, auth_chain_id_supplied: bool=False) -> set[tuple[int, int]]:
1003
1119
  """
1004
1120
  Get contacts from the structure attribute where the distance between two residues is less than the threshold.
1005
1121
 
@@ -1013,8 +1129,8 @@ class MMCIFInformation(StructureInformation):
1013
1129
  Chain id corresponding to the first column of residues in the structure.
1014
1130
  chain2 : str
1015
1131
  Chain id corresponding to the second column of residues in the structure.
1016
- auth_contacts : bool
1017
- True if you want alt_ids for residues indices, False if cif residue indexing is needed.
1132
+ auth_seq_id : bool
1133
+ True if you want auth_seq_ids for residues indices, False if cif residue indexing is needed.
1018
1134
  auth_chain_id_supplied : bool
1019
1135
  If True, the chain_id supplied is the auth chain id found on the RCSB website.
1020
1136
 
@@ -1023,8 +1139,9 @@ class MMCIFInformation(StructureInformation):
1023
1139
  contacts_set : set of tuple of ints
1024
1140
  Set of contacts, tuples with "residue1" and "residue2" from the structure that are within the distance threshold.
1025
1141
  """
1026
-
1027
1142
  chain1_structure, chain2_structure, dist_matrix = self.generate_dist_matrix(ca_only, chain1, chain2, auth_chain_id_supplied=auth_chain_id_supplied)
1143
+ seq_mapping_chain1 = self.get_seq_id_mapping(chain_id=chain1, seq_to_auth=True, auth_chain_id_supplied=auth_chain_id_supplied)
1144
+ seq_mapping_chain2 = self.get_seq_id_mapping(chain_id=chain2, seq_to_auth=True, auth_chain_id_supplied=auth_chain_id_supplied)
1028
1145
  thresh_ind = np.argwhere(dist_matrix <= threshold)
1029
1146
  contacts_set = set()
1030
1147
  for indices in thresh_ind:
@@ -1033,9 +1150,8 @@ class MMCIFInformation(StructureInformation):
1033
1150
  res1 = chain1_atom.res_id
1034
1151
  res2 = chain2_atom.res_id
1035
1152
  if not(chain1==chain2 and res1 >= res2):
1036
- if auth_contacts:
1037
- shift1, shift2 = self.get_shift_values(chain1, chain2, auth_chain_id_supplied=auth_chain_id_supplied)
1038
- contacts_set.add((res1 + shift1, res2 + shift2))
1153
+ if auth_seq_id:
1154
+ contacts_set.add((seq_mapping_chain1[res1], seq_mapping_chain2[res2]))
1039
1155
  else:
1040
1156
  contacts_set.add((res1, res2))
1041
1157
  return contacts_set
@@ -1067,6 +1183,26 @@ class PDBInformation(StructureInformation):
1067
1183
  self.non_missing_sequences = {str(chain): str(sequence) for (chain, sequence) in list(zip(struc.get_chains(non_hetero_structure), struc.to_sequence(non_hetero_structure)[0]))}
1068
1184
  self.unique_chains = struc.get_chains(non_hetero_structure)
1069
1185
 
1186
+ def get_start_res_id(self, chain_id: str) -> int:
1187
+ """
1188
+ Gets starting residue id of the specified chain excluding heteroatom group entries.
1189
+
1190
+ Parameters
1191
+ ----------
1192
+ chain_id : str
1193
+ The chain id supplied and selected for from the structure.
1194
+
1195
+ Returns
1196
+ -------
1197
+ int
1198
+ The residue id of the first atom in the chain provided.
1199
+ """
1200
+ non_hetero_structure = self.structure[self.structure.hetero == False]
1201
+ if chain_id in self.unique_chains:
1202
+ return non_hetero_structure[non_hetero_structure.chain_id == chain_id][0].res_id
1203
+ else:
1204
+ raise ValueError("Chain supplied not found in structure.")
1205
+
1070
1206
  def get_non_missing_sequence(self, chain_id: str) -> str:
1071
1207
  """
1072
1208
  Get sequence, including only non-missing residues, from the specified chain.
@@ -1083,7 +1219,7 @@ class PDBInformation(StructureInformation):
1083
1219
  """
1084
1220
  return self.non_missing_sequences[chain_id]
1085
1221
 
1086
- def get_chain_specific_structure(self, ca_only: bool, chain1: str, chain2: str, remove_hetero=True) -> tuple:
1222
+ def get_chain_specific_structure(self, ca_only: bool, chain_id: str, remove_hetero=True):
1087
1223
  """
1088
1224
  Subsets structure attribute to select for chain specific portions of the structure.
1089
1225
 
@@ -1103,7 +1239,6 @@ class PDBInformation(StructureInformation):
1103
1239
  tuple of biotite.structure.AtomArray, biotite.structure.AtomArray
1104
1240
  Two AtomArrays that refer to atoms in the first chain and second chain, respectively without accounting for the presence of heteroatoms if `remove_hetero` is True.
1105
1241
  """
1106
-
1107
1242
  selected_structure = self.structure
1108
1243
  if remove_hetero:
1109
1244
  # Remove hetero atoms via hetero column of structure ndarray
@@ -1111,10 +1246,26 @@ class PDBInformation(StructureInformation):
1111
1246
  if ca_only:
1112
1247
  # Consider selection of alpha-carbon atoms only
1113
1248
  selected_structure = selected_structure[selected_structure.atom_name == "CA"]
1114
- chain1_structure = selected_structure[selected_structure.chain_id == chain1]
1115
- chain2_structure = selected_structure[selected_structure.chain_id == chain2]
1116
- return (chain1_structure, chain2_structure)
1249
+ chain_structure = selected_structure[selected_structure.chain_id == chain_id]
1250
+ return chain_structure
1117
1251
 
1252
+ def get_valid_chain_residues(self, chain_id: str) -> list[tuple[int, str]]:
1253
+ """
1254
+ Gets valid indexing for residues of a specified chain. This is directly analogous to get_non_missing_sequence, does not contain missing residues, and provides the corresponding indices as well.
1255
+
1256
+ Parameters
1257
+ ----------
1258
+ chain_id : str
1259
+ Chain id of the chain to be selected from the structure. This chain's sequence and corresponding residue indices are what are exclusively selected for.
1260
+
1261
+ Returns
1262
+ -------
1263
+ list of tuple of int, str
1264
+ A list of residue information in sequential order reflecting the structure. The list consists of tuple elements where each tuple is the residue index and its corresponding one-letter amino acid.
1265
+ """
1266
+ chain_structure = self.get_chain_specific_structure(ca_only=True, chain_id=chain_id, remove_hetero=True)
1267
+ return list(zip(chain_structure.res_id.tolist(), map(lambda symbol: ProteinSequence.convert_letter_3to1(symbol), chain_structure.res_name)))
1268
+
1118
1269
  def generate_dist_matrix(self, ca_only: bool, chain1: str, chain2: str):
1119
1270
  """
1120
1271
  Generates distance matrix between two chains in the structure attribute.
@@ -1133,38 +1284,11 @@ class PDBInformation(StructureInformation):
1133
1284
  tuple of biotite.structure.AtomArray, biotite.structure.AtomArray, numpy.ndarray
1134
1285
  Tuple containing the chain 1 structure, the chain 2 structure, and the distance matrix of chain 1 and chain 2's pairwise distances.
1135
1286
  """
1136
- chain1_structure, chain2_structure = self.get_chain_specific_structure(ca_only, chain1, chain2, remove_hetero=True)
1287
+ chain1_structure = self.get_chain_specific_structure(ca_only=ca_only, chain_id=chain1, remove_hetero=True)
1288
+ chain2_structure = self.get_chain_specific_structure(ca_only=ca_only, chain_id=chain2, remove_hetero=True)
1137
1289
  dist_matrix = cdist(chain1_structure.coord, chain2_structure.coord)
1138
1290
  return (chain1_structure, chain2_structure, dist_matrix)
1139
1291
 
1140
- def get_shift_values(self, chain1: str, chain2: str) -> tuple[int, int]:
1141
- """
1142
- Get shift values needed for production of auth residue ids.
1143
-
1144
- Parameters
1145
- ----------
1146
- chain1 : str
1147
- Name of the chain id present in the struct_ref_seq block of cif files referring to the second column of residues.
1148
- chain2 : str
1149
- Name of the chain id present in the struct_ref_seq block of cif files referring to the second column of residues.
1150
-
1151
- Returns
1152
- -------
1153
- (shift1, shift2) : tuple of int, int
1154
- Tuple containing both shift values, the difference between the auth_res_id and res_id.
1155
- """
1156
- non_hetero_structure = self.structure[self.structure.hetero == False]
1157
- shift1 = 0
1158
- shift2 = 0
1159
- if chain1 in self.unique_chains and chain2 in self.unique_chains:
1160
- shift1 = non_hetero_structure[non_hetero_structure.chain_id == chain1][0].res_id - 1
1161
- shift2 = non_hetero_structure[non_hetero_structure.chain_id == chain2][0].res_id - 1
1162
- shift1 *= -1
1163
- shift2 *= -1
1164
- return shift1, shift2
1165
- else:
1166
- return shift1, shift2
1167
-
1168
1292
  def get_min_dist_atom_info(self, pairs: npt.NDArray, chain1: str, chain2: str) -> npt.NDArray:
1169
1293
  """
1170
1294
  Generate a ndarray of residue ids and their corresponding atom names such that the distance is the minimum between the initial residues provided.
@@ -1183,8 +1307,8 @@ class PDBInformation(StructureInformation):
1183
1307
  min_dist_pairs_atoms_arr : numpy.ndarray
1184
1308
  Structured ndarray that has residue indices, auth residue indices (corresponding to the protein numbering), and atomic names in the format {'names': ['residue1','residue2','auth_residue1','auth_residue2','atom_name1','atom_name2'], 'formats': [int,int,int,int,'<U10','<U10']}
1185
1309
  """
1186
- shift1, shift2 = self.get_shift_values(chain1, chain2)
1187
- chain1_structure, chain2_structure = self.get_chain_specific_structure(ca_only=False, chain1=chain1, chain2=chain2, remove_hetero=True)
1310
+ chain1_structure = self.get_chain_specific_structure(ca_only=False, chain_id=chain1, remove_hetero=True)
1311
+ chain2_structure = self.get_chain_specific_structure(ca_only=False, chain_id=chain2, remove_hetero=True)
1188
1312
  min_dist_pairs_atoms = []
1189
1313
  for row in pairs:
1190
1314
  # Obtain structure information for chains 1 and 2
@@ -1195,15 +1319,11 @@ class PDBInformation(StructureInformation):
1195
1319
  dist_matrix = cdist(chain1_res1_structure.coord, chain2_res2_structure.coord)
1196
1320
 
1197
1321
  ind = np.unravel_index(np.argmin(dist_matrix), dist_matrix.shape)
1198
- # Use the indices to access the atom in the atom array and get the correct atom name.
1199
- # Generate the auth ids of the residues in the pairs ndarray
1200
- orig_res_id1 = row['residue1'] + shift1
1201
- orig_res_id2 = row['residue2'] + shift2
1202
- min_dist_pairs_atoms.append((orig_res_id1, orig_res_id2, row['residue1'], row['residue2'], chain1_res1_structure[ind[0]].atom_name, chain2_res2_structure[ind[1]].atom_name))
1322
+ min_dist_pairs_atoms.append((row['residue1'], row['residue2'], row['residue1'], row['residue2'], chain1_res1_structure[ind[0]].atom_name, chain2_res2_structure[ind[1]].atom_name))
1203
1323
  min_dist_pairs_atoms_arr = np.array(min_dist_pairs_atoms, dtype={'names': ['residue1','residue2','auth_residue1','auth_residue2','atom_name1','atom_name2'], 'formats': [int,int,int,int,'<U10','<U10']})
1204
1324
  return min_dist_pairs_atoms_arr
1205
1325
 
1206
- def get_contacts(self, ca_only: bool, threshold: float, chain1: str, chain2: str, auth_contacts: bool=False) -> set[tuple[int, int]]:
1326
+ def get_contacts(self, ca_only: bool, threshold: float, chain1: str, chain2: str) -> set[tuple[int, int]]:
1207
1327
  """
1208
1328
  Get contacts from the structure attribute where the distance between two residues is less than the threshold.
1209
1329
 
@@ -1217,8 +1337,6 @@ class PDBInformation(StructureInformation):
1217
1337
  Chain id corresponding to the first column of residues in the structure.
1218
1338
  chain2 : str
1219
1339
  Chain id corresponding to the second column of residues in the structure.
1220
- auth_contacts : bool
1221
- True if you want alt_ids for residues indices, False if cif residue indexing is needed.
1222
1340
 
1223
1341
  Returns
1224
1342
  -------
@@ -1235,9 +1353,5 @@ class PDBInformation(StructureInformation):
1235
1353
  res1 = chain1_atom.res_id
1236
1354
  res2 = chain2_atom.res_id
1237
1355
  if not(chain1==chain2 and res1 >= res2):
1238
- if not auth_contacts:
1239
- shift1, shift2 = self.get_shift_values(chain1, chain2)
1240
- contacts_set.add((res1 + shift1, res2 + shift2))
1241
- else:
1242
- contacts_set.add((res1, res2))
1356
+ contacts_set.add((res1, res2))
1243
1357
  return contacts_set
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: dcatoolkit
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: Collection of useful modules and representations for managing DCA output data.
5
5
  Author-email: Raheel Syed Ahmed <raheelsyedahmed@gmail.com>
6
6
  Maintainer-email: Raheel Syed Ahmed <raheelsyedahmed@gmail.com>
@@ -52,6 +52,7 @@ Requires-Dist: pdoc; extra == "docs"
52
52
  Requires-Dist: numpydoc; extra == "docs"
53
53
  Provides-Extra: lint
54
54
  Requires-Dist: ruffle; extra == "lint"
55
+ Dynamic: license-file
55
56
 
56
57
  # dcatoolkit
57
58
  Collection of useful modules and representations for managing DCA output data.
@@ -9,4 +9,6 @@ src/dcatoolkit.egg-info/SOURCES.txt
9
9
  src/dcatoolkit.egg-info/dependency_links.txt
10
10
  src/dcatoolkit.egg-info/requires.txt
11
11
  src/dcatoolkit.egg-info/top_level.txt
12
- tests/test_contacts.py
12
+ tests/test_alignments.py
13
+ tests/test_contacts.py
14
+ tests/test_sequence_numbering.py
@@ -0,0 +1,88 @@
1
+ from context import ResidueAlignment
2
+ import pandas as pd
3
+
4
+ test_cases = """
5
+ Test Case 1
6
+ First: MA.KLT
7
+ Second: MAAKLT
8
+
9
+ Test Case 2
10
+ First: Q..EWLP
11
+ Second: QATEWLP
12
+
13
+ Test Case 3
14
+ First: D.G.H.V
15
+ Second: DAGAHAV
16
+
17
+ Test Case 4
18
+ First: TAKAPF
19
+ Second: TAMAPF
20
+
21
+ Test Case 5
22
+ First: FACARAA
23
+ Second: FACAR--
24
+
25
+ Test Case 6
26
+ First: ALMAY
27
+ Second: ALMAY
28
+
29
+ Test Case 7
30
+ First: VAIDTSK
31
+ Second: VAID--K
32
+
33
+ Test Case 8
34
+ First: NG.TA
35
+ Second: NG-TA
36
+
37
+ Test Case 9
38
+ First: STWLPL
39
+ Second: SAWLPL
40
+
41
+ Test Case 10
42
+ First: HCSTCRAAC
43
+ Second: H--TAR--C
44
+ """
45
+
46
+
47
+ test_cases = [
48
+ (47, 15, 'MA.KLT', 'MAAKLT'),
49
+ (39, 3, 'Q..EWLP', 'QATEWLP'),
50
+ (8, 45, 'D.G.H.V', 'DAGAHAV'),
51
+ (26, 19, 'TAKAPF', 'TAMAPF'),
52
+ (33, 9, 'FACARAA', 'FACAR--')
53
+ ]
54
+ test_cases_validation = [
55
+ (42, 28, 'ALMAY', 'ALMAY'),
56
+ (14, 11, 'VAIDTSK', 'VAID--K'),
57
+ (10, 5, 'NG.TA', 'NG-TA'),
58
+ (2, 44, 'STWLPL', 'SAWLPL'),
59
+ (17, 34, 'HCSTCRAAC', 'H--TAR--C')
60
+ ]
61
+
62
+ test_answers = [
63
+ [(47, 48, pd.NA, 49, 50, 51), tuple('MA.KLT'), tuple('MAAKLT'), (15,16,17,18,19,20)],
64
+ [(39, pd.NA, pd.NA, 40, 41, 42, 43), tuple('Q..EWLP'), tuple('QATEWLP'), (3,4,5,6,7,8,9)],
65
+ [(8, pd.NA, 9, pd.NA, 10, pd.NA, 11), tuple('D.G.H.V'), tuple('DAGAHAV'), (45,46,47,48,49,50,51)],
66
+ [(26, 27, 28, 29, 30, 31), tuple('TAKAPF'), tuple('TAMAPF'), (19,20,21,22,23,24)],
67
+ [(33, 34, 35, 36, 37, 38, 39), tuple('FACARAA'), tuple('FACAR--'), (9,10,11,12,13,pd.NA,pd.NA)],
68
+ [(42, 43, 44, 45, 46), tuple('ALMAY'), tuple('ALMAY'), (28,29,30,31,32)],
69
+ [(14, 15, 16, 17, 18, 19, 20), tuple('VAIDTSK'), tuple('VAID--K'), (11, 12, 13, 14, pd.NA, pd.NA, 15)],
70
+ [(10,11,pd.NA,12,13), tuple('NG.TA'), tuple('NG-TA'), (5,6,pd.NA,7,8)],
71
+ [(2,3,4,5,6,7), tuple('STWLPL'), tuple('SAWLPL'), (44,45,46,47,48,49)],
72
+ [(17,18,19,20,21,22,23,24,25), tuple('HCSTCRAAC'), tuple('H--TAR--C'), (34, pd.NA, pd.NA, 35,36,37, pd.NA, pd.NA, 38)]
73
+ ]
74
+
75
+ def test_residue_alignments():
76
+ for test_num, test_case in enumerate(test_cases):
77
+ domain_start, protein_start, first_seq, second_seq = test_case
78
+ module_result = list(ResidueAlignment(f"Test_{test_num}", f"Test {test_num}", domain_start, protein_start, first_seq, second_seq).reference_mapping.itertuples(index=False, name=None))
79
+ answer = list(zip(*test_answers[test_num]))
80
+ assert module_result == answer
81
+ for test_num, test_case in enumerate(test_cases_validation, start=test_num+1):
82
+ domain_start, protein_start, first_seq, second_seq = test_case
83
+ module_result = list(ResidueAlignment(f"Test_{test_num}", f"Test {test_num}", domain_start, protein_start, first_seq, second_seq).reference_mapping.itertuples(index=False, name=None))
84
+ answer = list(zip(*test_answers[test_num]))
85
+ assert module_result == answer
86
+
87
+ # Can handle excess residues, but not missing any ones that are supposed to be there.
88
+ print(ResidueAlignment('name1', 'name2', 1, 1, 'MAAFT', 'MAAFT', valid_residues=[(5, 'M'), (6, 'A'), (7, 'A'), (8, 'R'), (12, 'F')]))
@@ -59,12 +59,12 @@ def check_contacts(test_CA: bool, threshold: float):
59
59
 
60
60
  cif_file_contacts = read_contacts(corresponding_file)
61
61
  chain1, auth_chain1, chain2, auth_chain2 = pdb_id_chain_map[pdb_id]
62
- fetch_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.fetch_pdb(pdb_id, 'mmcif').get_contacts(test_CA, threshold, chain1, chain2, auth_contacts=True)}
63
- read_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.read_mmCIF_file(str(cif_file)).get_contacts(test_CA, threshold, chain1, chain2, auth_contacts=True)}
64
- fetch_authchain_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.fetch_pdb(pdb_id, 'mmcif').get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_contacts=True, auth_chain_id_supplied=True)}
65
- read_authchain_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.read_mmCIF_file(str(cif_file)).get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_contacts=True, auth_chain_id_supplied=True)}
66
- fetch_pdb_contacts = {(int(x[0]), int(x[1])) for x in PDBInformation.fetch_pdb(pdb_id, struc_format="pdb").get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_contacts=True)}
67
- read_pdb_contacts = {(int(x[0]), int(x[1])) for x in PDBInformation.read_pdb_file(f"tests/pdb_info/{pdb_id.lower()}.pdb").get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_contacts=True)}
62
+ fetch_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.fetch_pdb(pdb_id, 'mmcif').get_contacts(test_CA, threshold, chain1, chain2, auth_seq_id=True)}
63
+ read_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.read_mmCIF_file(str(cif_file)).get_contacts(test_CA, threshold, chain1, chain2, auth_seq_id=True)}
64
+ fetch_authchain_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.fetch_pdb(pdb_id, 'mmcif').get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_seq_id=True, auth_chain_id_supplied=True)}
65
+ read_authchain_cif_contacts = {(int(x[0]), int(x[1])) for x in MMCIFInformation.read_mmCIF_file(str(cif_file)).get_contacts(test_CA, threshold, auth_chain1, auth_chain2, auth_seq_id=True, auth_chain_id_supplied=True)}
66
+ fetch_pdb_contacts = {(int(x[0]), int(x[1])) for x in PDBInformation.fetch_pdb(pdb_id, struc_format="pdb").get_contacts(test_CA, threshold, auth_chain1, auth_chain2)}
67
+ read_pdb_contacts = {(int(x[0]), int(x[1])) for x in PDBInformation.read_pdb_file(f"tests/pdb_info/{pdb_id.lower()}.pdb").get_contacts(test_CA, threshold, auth_chain1, auth_chain2)}
68
68
 
69
69
 
70
70
  fetch_cif_contacts = drop_inord_res(fetch_cif_contacts)
@@ -86,7 +86,7 @@ def check_contacts(test_CA: bool, threshold: float):
86
86
  else:
87
87
  compare(fetch_cif_contacts, fetch_pdb_contacts, read_cif_contacts, read_pdb_contacts)
88
88
  print(f"{pdb_id} has no difference between cif and pdb reading.")
89
-
89
+
90
90
  compare(fetch_cif_contacts, fetch_authchain_cif_contacts, read_cif_contacts, read_authchain_cif_contacts)
91
91
  print(f"{pdb_id} has no issue reading with auth chains and asym chains.")
92
92
 
@@ -0,0 +1,14 @@
1
+ from context import MMCIFInformation, PDBInformation
2
+ from pathlib import Path
3
+ import biotite.structure.io.pdbx as pdbx
4
+ import biotite.database.rcsb as rcsb
5
+
6
+
7
+ pdb_ids = ['1pzs', '3ddv', '6avj', '3d7i', '4OO8']
8
+
9
+ for index, AA in MMCIFInformation.fetch_pdb("4OO8", "mmcif").get_valid_chain_residues("A"):
10
+ if index > 30:
11
+ break
12
+ else:
13
+ pass
14
+ print(index, AA)
File without changes
File without changes
File without changes