| import re |
| import string |
| from typing import Iterator |
|
|
| from boltzgen.data import const |
| from boltzgen.data.data import Structure |
|
|
|
|
| def generate_tags() -> Iterator[str]: |
| """Generate chain tags. |
| |
| Yields |
| ------ |
| str |
| The next chain tag |
| |
| """ |
| for i in range(1, 4): |
| for j in range(len(string.ascii_uppercase) ** i): |
| tag = "" |
| for k in range(i): |
| tag += string.ascii_uppercase[ |
| j |
| // (len(string.ascii_uppercase) ** k) |
| % len(string.ascii_uppercase) |
| ] |
| yield tag |
|
|
|
|
| def to_pdb(structure: Structure) -> str: |
| """Write a structure into a PDB file. |
| |
| Parameters |
| ---------- |
| structure : Structure |
| The input structure |
| |
| Returns |
| ------- |
| str |
| the output PDB file |
| |
| """ |
| pdb_lines = [] |
|
|
| atom_index = 1 |
| atom_reindex_ter = [] |
| chain_tags = generate_tags() |
|
|
| |
| for chain in structure.chains: |
| |
| chain_idx = chain["asym_id"] |
| chain_tag = next(chain_tags) |
|
|
| res_start = chain["res_idx"] |
| res_end = chain["res_idx"] + chain["res_num"] |
|
|
| residues = structure.residues[res_start:res_end] |
| for residue in residues: |
| atom_start = residue["atom_idx"] |
| atom_end = residue["atom_idx"] + residue["atom_num"] |
| atoms = structure.atoms[atom_start:atom_end] |
| atom_coords = atoms["coords"] |
| res_name = residue["name"] |
| for i, atom in enumerate(atoms): |
| atom_reindex_ter.append(atom_index) |
|
|
| |
| if not atom["is_present"]: |
| continue |
|
|
| record_type = ( |
| "ATOM" |
| if chain["mol_type"] != const.chain_type_ids["NONPOLYMER"] |
| else "HETATM" |
| ) |
| atom_name = atom["name"] |
| alt_loc = "" |
| insertion_code = "" |
| occupancy = 1.00 |
| atom_key = re.sub(r"\d", "", atom_name) |
| if atom_key in const.ambiguous_atoms: |
| if isinstance(const.ambiguous_atoms[atom_key], str): |
| element = const.ambiguous_atoms[atom_key] |
| elif res_name in const.ambiguous_atoms[atom_key]: |
| element = const.ambiguous_atoms[atom_key][res_name] |
| else: |
| element = const.ambiguous_atoms[atom_key]["*"] |
| else: |
| element = atom_key[0] |
|
|
| charge = "" |
| residue_index = residue["res_idx"] + 1 |
| pos = atom_coords[i] |
| res_name_3 = "LIG" if record_type == "HETATM" else res_name |
| b_factor = 1.00 |
|
|
| |
| atom_line = ( |
| f"{record_type:<6}{atom_index:>5} {atom_name:<4}{alt_loc:>1}" |
| f"{res_name_3:>3} {chain_tag:>1}" |
| f"{residue_index:>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 |
|
|
| should_terminate = chain_idx < (len(structure.chains) - 1) |
| if should_terminate: |
| |
| chain_end = "TER" |
| chain_termination_line = ( |
| f"{chain_end:<6}{atom_index:>5} " |
| f"{res_name_3:>3} " |
| f"{chain_tag:>1}{residue_index:>4}" |
| ) |
| pdb_lines.append(chain_termination_line) |
| atom_index += 1 |
|
|
| |
| for bond in structure.bonds: |
| atom1 = structure.atoms[bond["atom_1"]] |
| atom2 = structure.atoms[bond["atom_2"]] |
| if not atom1["is_present"] or not atom2["is_present"]: |
| continue |
| atom1_idx = atom_reindex_ter[bond["atom_1"]] |
| atom2_idx = atom_reindex_ter[bond["atom_2"]] |
| conect_line = f"CONECT{atom1_idx:>5}{atom2_idx:>5}" |
| pdb_lines.append(conect_line) |
|
|
| pdb_lines.append("END") |
| pdb_lines.append("") |
| pdb_lines = [line.ljust(80) for line in pdb_lines] |
| return "\n".join(pdb_lines) |
|
|