|
|
|
|
| """Realign sequences found in PDB seqres to the actual CIF sequences.""" |
|
|
| from collections.abc import Mapping |
|
|
|
|
| class AlignmentError(Exception): |
| """Failed alignment between the hit sequence and the actual mmCIF sequence.""" |
|
|
|
|
| def realign_hit_to_structure( |
| *, |
| hit_sequence: str, |
| hit_start_index: int, |
| hit_end_index: int, |
| full_length: int, |
| structure_sequence: str, |
| query_to_hit_mapping: Mapping[int, int], |
| ) -> Mapping[int, int]: |
| """Realigns the hit sequence to the Structure sequence. |
| |
| For example, for the given input: |
| query_sequence : ABCDEFGHIJKL |
| hit_sequence : ---DEFGHIJK- |
| struc_sequence : XDEFGHKL |
| the mapping is {3: 0, 4: 1, 5: 2, 6: 3, 7: 4, 8: 5, 9: 6, 10: 7}. However, the |
| actual Structure sequence has an extra X at the start as well as no IJ. So the |
| alignment from the query to the Structure sequence will be: |
| hit_sequence : ---DEFGHIJK- |
| struc_aligned : --XDEFGH--KL |
| and the new mapping will therefore be: {3: 1, 4: 2, 5: 3, 6: 4, 7: 5, 10: 6}. |
| |
| Args: |
| hit_sequence: The PDB seqres hit sequence obtained from Hmmsearch, but |
| without any gaps. This is not the full PDB seqres template sequence but |
| rather just its subsequence from hit_start_index to hit_end_index. |
| hit_start_index: The start index of the hit sequence in the full PDB seqres |
| template sequence (inclusive). |
| hit_end_index: The end index of the hit sequence in the full PDB seqres |
| template sequence (exclusive). |
| full_length: The length of the full PDB seqres template sequence. |
| structure_sequence: The actual sequence extracted from the Structure |
| corresponding to this template. In vast majority of cases this is the same |
| as the PDB seqres sequence, but this function handles the cases when not. |
| query_to_hit_mapping: The mapping from the query sequence to the |
| hit_sequence. |
| |
| Raises: |
| AlignmentError: if the alignment between the sequence returned by Hmmsearch |
| differs from the actual sequence found in the mmCIF and can't be aligned |
| using the simple alignment algorithm. |
| |
| Returns: |
| A mapping from the query sequence to the actual Structure sequence. |
| """ |
| max_num_gaps = full_length - len(structure_sequence) |
| if max_num_gaps < 0: |
| raise AlignmentError( |
| f'The Structure sequence ({len(structure_sequence)}) ' |
| f'must be shorter than the PDB seqres sequence ({full_length}):\n' |
| f'Structure sequence : {structure_sequence}\n' |
| f'PDB seqres sequence: {hit_sequence}' |
| ) |
|
|
| if len(hit_sequence) != hit_end_index - hit_start_index: |
| raise AlignmentError( |
| f'The difference of {hit_end_index=} and {hit_start_index=} does not ' |
| f'equal to the length of the {hit_sequence}: {len(hit_sequence)}' |
| ) |
|
|
| best_score = -1 |
| best_start = 0 |
| best_query_to_hit_mapping = query_to_hit_mapping |
| max_num_gaps_before_subseq = min(hit_start_index, max_num_gaps) |
| |
| |
| |
| for num_gaps_before_subseq in range(0, max_num_gaps_before_subseq + 1): |
| start = hit_start_index - num_gaps_before_subseq |
| end = hit_end_index - num_gaps_before_subseq |
| structure_subseq = structure_sequence[start:end] |
|
|
| new_query_to_hit_mapping, score = _remap_to_struc_seq( |
| hit_seq=hit_sequence, |
| struc_seq=structure_subseq, |
| max_num_gaps=max_num_gaps - num_gaps_before_subseq, |
| mapping=query_to_hit_mapping, |
| ) |
| if score >= best_score: |
| |
| best_score = score |
| best_start = start |
| best_query_to_hit_mapping = new_query_to_hit_mapping |
|
|
| return {q: h + best_start for q, h in best_query_to_hit_mapping.items()} |
|
|
|
|
| def _remap_to_struc_seq( |
| *, |
| hit_seq: str, |
| struc_seq: str, |
| max_num_gaps: int, |
| mapping: Mapping[int, int], |
| ) -> tuple[Mapping[int, int], int]: |
| """Remaps the query -> hit mapping to match the actual Structure sequence. |
| |
| Args: |
| hit_seq: The hit sequence - a subsequence of the PDB seqres sequence without |
| any Hmmsearch modifications like inserted gaps or lowercased residues. |
| struc_seq: The actual sequence obtained from the corresponding Structure. |
| max_num_gaps: The maximum number of gaps that can be inserted in the |
| Structure sequence. In practice, this is the length difference between the |
| PDB seqres sequence and the actual Structure sequence. |
| mapping: The mapping from the query residues to the hit residues. This will |
| be remapped to point to the actual Structure sequence using a simple |
| realignment algorithm. |
| |
| Returns: |
| A tuple of (mapping, score): |
| * Mapping from the query to the actual Structure sequence. |
| * Score which is the number of matching aligned residues. |
| |
| Raises: |
| ValueError if the structure sequence isn't shorter than the seqres sequence. |
| ValueError if the alignment fails. |
| """ |
| hit_seq_idx = 0 |
| struc_seq_idx = 0 |
| hit_to_struc_seq_mapping = {} |
| score = 0 |
|
|
| |
| |
| remaining_num_gaps = max_num_gaps |
| while hit_seq_idx < len(hit_seq) and struc_seq_idx < len(struc_seq): |
| if hit_seq[hit_seq_idx] != struc_seq[struc_seq_idx]: |
| |
| best_shift = 0 |
| for shift in range(0, remaining_num_gaps + 1): |
| next_hit_res = hit_seq[hit_seq_idx + shift : hit_seq_idx + shift + 1] |
| next_struc_res = struc_seq[struc_seq_idx : struc_seq_idx + 1] |
| if next_hit_res == next_struc_res: |
| best_shift = shift |
| break |
| hit_seq_idx += best_shift |
| remaining_num_gaps -= best_shift |
|
|
| hit_to_struc_seq_mapping[hit_seq_idx] = struc_seq_idx |
| score += hit_seq[hit_seq_idx] == struc_seq[struc_seq_idx] |
| hit_seq_idx += 1 |
| struc_seq_idx += 1 |
|
|
| fixed_mapping = {} |
| for query_idx, original_hit_idx in mapping.items(): |
| fixed_hit_idx = hit_to_struc_seq_mapping.get(original_hit_idx) |
| if fixed_hit_idx is not None: |
| fixed_mapping[query_idx] = fixed_hit_idx |
|
|
| return fixed_mapping, score |
|
|