| import warnings |
| import string |
| from typing import Mapping, Optional |
|
|
| import gemmi |
| import numpy as np |
| from rdkit.Chem.rdchem import Mol |
|
|
| from boltzgen.data.parse.mmcif import ( |
| mmcif_from_block, |
| ParsedStructure, |
| ) |
| from boltzgen.data.data import ( |
| Chain, |
| Structure, |
| ) |
|
|
|
|
| def parse_pdb( |
| path: str, |
| mols: Mapping[str, Mol] = {}, |
| moldir: Optional[str] = None, |
| use_assembly: bool = True, |
| use_original_res_idx: bool = False, |
| ) -> ParsedStructure: |
| """ |
| WARNING: this function does not parse ligands if they are seperated with TER records. |
| """ |
|
|
| if not str(path).endswith(".pdb"): |
| warnings.warn(f"Input file {path} does not have .pdb extension") |
|
|
| st = gemmi.read_structure(str(path)) |
| st.setup_entities() |
|
|
| for entity in st.entities: |
| if "Polymer" != entity.entity_type.name: |
| continue |
| sc = entity.subchains[0] |
| sc = st[0].get_subchain(sc) |
|
|
| |
| |
| if len(entity.full_sequence) < 1: |
| full_sequence = [res.name for res in sc] |
| entity.full_sequence = full_sequence |
|
|
| |
| align_result = gemmi.align_sequence_to_polymer( |
| entity.full_sequence, |
| sc, |
| entity.polymer_type, |
| gemmi.AlignmentScoring(), |
| ).match_string |
|
|
| i = 0 |
| for j, align in enumerate(align_result): |
| if align == "|": |
| sc[i].label_seq = j + 1 |
| i += 1 |
|
|
| block = st.make_mmcif_block() |
|
|
| structure = mmcif_from_block( |
| block, mols, moldir, use_assembly, use_original_res_idx=use_original_res_idx |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| chain_names = [chain.name for chain in st[0]] |
| if any(len(chain) > 1 for chain in chain_names): |
| raise ValueError( |
| f"Chain names in the PDB file are not 1-letter: {chain_names}. This should never happen" |
| "according to PDB spec. But it is also possible to extend patch_chain_names with an" |
| "additional argument to pass the original chain names to distinguish between chains" |
| "with the same first letter, and patch chain names unambiguously." |
| ) |
| structure = patch_chain_names(structure) |
|
|
| return structure |
|
|
|
|
| def patch_chain_names(structure: ParsedStructure) -> ParsedStructure: |
| """Patch chain names to first character according to PDB spec. Ligands within same chain id |
| get renamed to avoid ambiguity. |
| """ |
| |
| chains_new = [] |
| chain_mapping = {} |
| chain_names = [chain["name"][0] for chain in structure.data.chains] |
| chain_id_pool = list(reversed(string.ascii_uppercase)) + list( |
| reversed(string.digits) |
| ) |
| used_names = [] |
| for chain in structure.data.chains: |
| chain_name = chain["name"][0] |
| if chain_name in used_names: |
| for candidate in chain_id_pool: |
| if candidate not in chain_names and candidate not in used_names: |
| chain_name = candidate |
| break |
| used_names.append(chain_name) |
| chain_mapping[chain["name"]] = chain_name |
| chains_new.append( |
| ( |
| chain_name, |
| chain["mol_type"], |
| chain["entity_id"], |
| chain["sym_id"], |
| chain["asym_id"], |
| chain["atom_idx"], |
| chain["atom_num"], |
| chain["res_idx"], |
| chain["res_num"], |
| chain["cyclic_period"], |
| chain["symmetric_group"], |
| ) |
| ) |
| chains_new = np.array(chains_new, dtype=Chain) |
|
|
| |
| sequences_new = { |
| chain_mapping[name]: seq for name, seq in structure.sequences.items() |
| } |
|
|
| data = Structure( |
| atoms=structure.data.atoms, |
| bonds=structure.data.bonds, |
| residues=structure.data.residues, |
| chains=chains_new, |
| interfaces=structure.data.interfaces, |
| mask=structure.data.mask, |
| ensemble=structure.data.ensemble, |
| coords=structure.data.coords, |
| ) |
| return ParsedStructure( |
| data=data, |
| info=structure.info, |
| sequences=sequences_new, |
| ) |
|
|