| import dataclasses |
| import numpy as np |
|
|
| from utils import residue_constants |
|
|
| PDB_CHAIN_IDS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' |
| PDB_MAX_CHAINS = len(PDB_CHAIN_IDS) |
|
|
|
|
| @dataclasses.dataclass(frozen=True) |
| class Protein: |
| atom_positions: np.ndarray |
| aatype: np.ndarray |
| atom_mask: np.ndarray |
| residue_index: np.ndarray |
| chain_index: np.ndarray |
| b_factors: np.ndarray |
|
|
| def __post_init__(self): |
| if len(np.unique(self.chain_index)) > PDB_MAX_CHAINS: |
| raise ValueError( |
| f'Cannot build an instance with more than {PDB_MAX_CHAINS}' |
| ) |
|
|
|
|
| def _chain_end(atom_index, end_resname, chain_name, residue_index) -> str: |
| chain_end = 'TER' |
| return (f'{chain_end:<6}{atom_index:>5} {end_resname:>3} ' |
| f'{chain_name:>1}{residue_index:>4}') |
|
|
|
|
| def to_pdb(prot: Protein, model=1, add_end=True) -> str: |
| """Converts a `Protein` instance to a PDB string. |
| |
| Args: |
| prot: The protein to convert to PDB. |
| |
| Returns: |
| PDB string. |
| """ |
| restypes = residue_constants.restypes + ['X'] |
| res_1to3 = lambda r: residue_constants.restype_1to3.get(restypes[r], 'UNK') |
| atom_types = residue_constants.atom_types |
|
|
| pdb_lines = [] |
|
|
| atom_mask = prot.atom_mask |
| aatype = prot.aatype |
| atom_positions = prot.atom_positions |
| residue_index = prot.residue_index.astype(int) |
| chain_index = prot.chain_index.astype(int) |
| b_factors = prot.b_factors |
|
|
| if np.any(aatype > residue_constants.restype_num): |
| raise ValueError('Invalid aatypes.') |
|
|
| |
| chain_ids = {} |
| for i in np.unique(chain_index): |
| if i >= PDB_MAX_CHAINS: |
| raise ValueError( |
| f'The PDB format supports at most {PDB_MAX_CHAINS} chains.') |
| chain_ids[i] = PDB_CHAIN_IDS[i] |
|
|
| pdb_lines.append(f'MODEL {model}') |
| atom_index = 1 |
| last_chain_index = chain_index[0] |
| |
| for i in range(aatype.shape[0]): |
| |
| if last_chain_index != chain_index[i]: |
| pdb_lines.append(_chain_end( |
| atom_index, res_1to3(aatype[i - 1]), chain_ids[chain_index[i - 1]], |
| residue_index[i - 1])) |
| last_chain_index = chain_index[i] |
| atom_index += 1 |
|
|
| res_name_3 = res_1to3(aatype[i]) |
| for atom_name, pos, mask, b_factor in zip( |
| atom_types, atom_positions[i], atom_mask[i], b_factors[i]): |
| if mask < 0.5: |
| continue |
|
|
| record_type = 'ATOM' |
| name = atom_name if len(atom_name) == 4 else f' {atom_name}' |
| alt_loc = '' |
| insertion_code = '' |
| occupancy = 1.00 |
| element = atom_name[0] |
| charge = '' |
| |
| atom_line = (f'{record_type:<6}{atom_index:>5} {name:<4}{alt_loc:>1}' |
| f'{res_name_3:>3} {chain_ids[chain_index[i]]:>1}' |
| f'{residue_index[i]:>4}{insertion_code:>1} ' |
| f'{pos[0]:>8.3f}{pos[1]:>8.3f}{pos[2]:>8.3f}' |
| f'{occupancy:>6.2f}{b_factor:>6.2f} ' |
| f'{element:>2}{charge:>2}') |
| pdb_lines.append(atom_line) |
| atom_index += 1 |
|
|
| |
| pdb_lines.append(_chain_end(atom_index, res_1to3(aatype[-1]), |
| chain_ids[chain_index[-1]], residue_index[-1])) |
| pdb_lines.append('ENDMDL') |
| if add_end: |
| pdb_lines.append('END') |
|
|
| |
| pdb_lines = [line.ljust(80) for line in pdb_lines] |
| return '\n'.join(pdb_lines) + '\n' |
|
|