|
|
|
|
| """Prepare PDB structure for training or inference.""" |
|
|
| from typing import Any |
|
|
| from absl import logging |
| from flax_model.alphafold3 import structure |
| from flax_model.alphafold3.constants import chemical_component_sets |
| from flax_model.alphafold3.constants import chemical_components |
| from flax_model.alphafold3.constants import mmcif_names |
| from flax_model.alphafold3.model.atom_layout import atom_layout |
| from flax_model.alphafold3.model.pipeline import inter_chain_bonds |
| from flax_model.alphafold3.model.scoring import covalent_bond_cleaning |
| from flax_model.alphafold3.structure import sterics |
| import numpy as np |
|
|
|
|
| def _get_leaving_atom_mask( |
| struc: structure.Structure, |
| polymer_ligand_bonds: atom_layout.AtomLayout | None, |
| ligand_ligand_bonds: atom_layout.AtomLayout | None, |
| chain_id: str, |
| chain_type: str, |
| res_id: int, |
| res_name: str, |
| ) -> np.ndarray: |
| """Updates a drop_leaving_atoms mask with new leaving atom locations.""" |
| bonded_atoms = atom_layout.get_bonded_atoms( |
| polymer_ligand_bonds, |
| ligand_ligand_bonds, |
| res_id, |
| chain_id, |
| ) |
| |
| drop_atoms = atom_layout.get_link_drop_atoms( |
| res_name=res_name, |
| chain_type=chain_type, |
| is_start_terminus=False, |
| is_end_terminus=False, |
| bonded_atoms=bonded_atoms, |
| drop_ligand_leaving_atoms=True, |
| ) |
| |
| drop_atom_filter_atoms = struc.chain_id != struc.chain_id |
| for drop_atom in drop_atoms: |
| drop_atom_filter_atom = np.logical_and( |
| np.logical_and( |
| struc.atom_name == drop_atom, |
| struc.chain_id == chain_id, |
| ), |
| struc.res_id == res_id, |
| ) |
| drop_atom_filter_atoms = np.logical_or( |
| drop_atom_filter_atoms, drop_atom_filter_atom |
| ) |
| return drop_atom_filter_atoms |
|
|
|
|
| def clean_structure( |
| struc: structure.Structure, |
| ccd: chemical_components.Ccd, |
| *, |
| drop_missing_sequence: bool, |
| filter_clashes: bool, |
| drop_non_standard_atoms: bool, |
| filter_crystal_aids: bool, |
| filter_waters: bool, |
| filter_hydrogens: bool, |
| filter_leaving_atoms: bool, |
| only_glycan_ligands_for_leaving_atoms: bool, |
| covalent_bonds_only: bool, |
| remove_polymer_polymer_bonds: bool, |
| remove_bad_bonds: bool, |
| remove_nonsymmetric_bonds: bool, |
| ) -> tuple[structure.Structure, dict[str, Any]]: |
| """Cleans structure. |
| |
| Args: |
| struc: Structure to clean. |
| ccd: The chemical components dictionary. |
| drop_missing_sequence: Whether to drop chains without specified sequences. |
| filter_clashes: Whether to drop clashing chains. |
| drop_non_standard_atoms: Whether to drop non CCD standard atoms. |
| filter_crystal_aids: Whether to drop ligands in the crystal aid set. |
| filter_waters: Whether to drop water chains. |
| filter_hydrogens: Whether to drop hyrdogen atoms. |
| filter_leaving_atoms: Whether to drop leaving atoms based on heuristics. |
| only_glycan_ligands_for_leaving_atoms: Whether to only include glycan |
| ligands when filtering leaving atoms. |
| covalent_bonds_only: Only include covalent bonds. |
| remove_polymer_polymer_bonds: Remove polymer-polymer bonds. |
| remove_bad_bonds: Whether to remove badly bonded ligands. |
| remove_nonsymmetric_bonds: Whether to remove nonsymmetric polymer-ligand |
| bonds from symmetric polymer chains. |
| |
| Returns: |
| Tuple of structure and metadata dict. The metadata dict has |
| information about what was cleaned from the original. |
| """ |
|
|
| metadata = {} |
| |
| if ( |
| filter_crystal_aids |
| and struc.structure_method in mmcif_names.CRYSTALLIZATION_METHODS |
| ): |
| struc = struc.filter_out( |
| res_name=chemical_component_sets.COMMON_CRYSTALLIZATION_AIDS |
| ) |
|
|
| |
| if drop_missing_sequence: |
| chains_with_unk_sequence = struc.find_chains_with_unknown_sequence() |
| num_with_unk_sequence = len(chains_with_unk_sequence) |
| if chains_with_unk_sequence: |
| struc = struc.filter_out(chain_id=chains_with_unk_sequence) |
| else: |
| num_with_unk_sequence = 0 |
| metadata['num_with_unk_sequence'] = num_with_unk_sequence |
|
|
| |
| if filter_clashes and struc.num_chains > 1: |
| clashing_chains = sterics.find_clashing_chains(struc) |
| if clashing_chains: |
| struc = struc.filter_out(chain_id=clashing_chains) |
| else: |
| clashing_chains = [] |
| metadata['num_clashing_chains_removed'] = len(clashing_chains) |
| metadata['chains_removed'] = clashing_chains |
|
|
| |
| if drop_non_standard_atoms: |
| struc = struc.drop_non_standard_atoms( |
| ccd=ccd, drop_unk=False, drop_non_ccd=False |
| ) |
|
|
| |
| struc = struc.with_sorted_chains |
|
|
| if filter_hydrogens: |
| struc = struc.without_hydrogen() |
|
|
| if filter_waters: |
| struc = struc.filter_out(chain_type=mmcif_names.WATER) |
|
|
| if filter_leaving_atoms: |
| drop_leaving_atoms_all = struc.chain_id != struc.chain_id |
| polymer_ligand_bonds = inter_chain_bonds.get_polymer_ligand_bonds( |
| struc, |
| only_glycan_ligands=only_glycan_ligands_for_leaving_atoms, |
| ) |
| ligand_ligand_bonds = inter_chain_bonds.get_ligand_ligand_bonds( |
| struc, |
| only_glycan_ligands=only_glycan_ligands_for_leaving_atoms, |
| ) |
| all_glycans = { |
| *chemical_component_sets.GLYCAN_OTHER_LIGANDS, |
| *chemical_component_sets.GLYCAN_LINKING_LIGANDS, |
| } |
| |
| if ( |
| only_glycan_ligands_for_leaving_atoms |
| and (not (ligand_ligand_bonds.atom_name == 'O1').any()) |
| and (not (polymer_ligand_bonds.atom_name == 'O1').any()) |
| ): |
| drop_leaving_atoms_all = np.logical_and( |
| np.isin(struc.atom_name, 'O1'), |
| np.isin(struc.res_name, list(all_glycans)), |
| ) |
| else: |
| substruct = struc.group_by_residue |
| glycan_mask = np.isin(substruct.res_name, list(all_glycans)) |
| substruct = substruct.filter(glycan_mask) |
| |
| for res in substruct.iter_residues(): |
| |
| if (res_name := res['res_name']) in all_glycans: |
| drop_atom_filter = _get_leaving_atom_mask( |
| struc=struc, |
| polymer_ligand_bonds=polymer_ligand_bonds, |
| ligand_ligand_bonds=ligand_ligand_bonds, |
| chain_id=res['chain_id'], |
| chain_type=res['chain_type'], |
| res_id=res['res_id'], |
| res_name=res_name, |
| ) |
| drop_leaving_atoms_all = np.logical_or( |
| drop_leaving_atoms_all, drop_atom_filter |
| ) |
|
|
| num_atoms_before = struc.num_atoms |
| struc = struc.filter_out(drop_leaving_atoms_all) |
| num_atoms_after = struc.num_atoms |
|
|
| if num_atoms_before > num_atoms_after: |
| logging.error( |
| 'Dropped %s atoms from GT struc: chain_id %s res_id %s res_name %s', |
| num_atoms_before - num_atoms_after, |
| struc.chain_id, |
| struc.res_id, |
| struc.res_name, |
| ) |
|
|
| |
| if struc.bonds and covalent_bonds_only: |
| is_covalent = np.isin(struc.bonds.type, ['covale']) |
| if sum(is_covalent) > 0: |
| new_bonds = struc.bonds[is_covalent] |
| else: |
| new_bonds = structure.Bonds.make_empty() |
| struc = struc.copy_and_update(bonds=new_bonds) |
|
|
| |
| if struc.bonds and (remove_bad_bonds or remove_polymer_polymer_bonds): |
| include_bond = [] |
| num_pp_bonds = 0 |
| num_bad_bonds = 0 |
| for bond in struc.iter_bonds(): |
| dest_atom = bond.dest_atom |
| from_atom = bond.from_atom |
| if remove_polymer_polymer_bonds: |
| if ( |
| from_atom['chain_type'] in mmcif_names.POLYMER_CHAIN_TYPES |
| and dest_atom['chain_type'] in mmcif_names.POLYMER_CHAIN_TYPES |
| ): |
| num_pp_bonds += 1 |
| include_bond.append(False) |
| continue |
| if remove_bad_bonds: |
| dest_coords = np.array( |
| [dest_atom['atom_x'], dest_atom['atom_y'], dest_atom['atom_z']] |
| ) |
| from_coords = np.array( |
| [from_atom['atom_x'], from_atom['atom_y'], from_atom['atom_z']] |
| ) |
| squared_dist = np.sum(np.square(dest_coords - from_coords)) |
| squared_threshold = 2.4 * 2.4 |
| if squared_dist > squared_threshold: |
| num_bad_bonds += 1 |
| include_bond.append(False) |
| continue |
| include_bond.append(True) |
| if sum(include_bond) < len(struc.bonds): |
| logging.info( |
| 'Reducing number of bonds for %s from %s to %s, of which %s are' |
| ' polymer-polymer bonds and %s are bad bonds.', |
| struc.name, |
| len(struc.bonds), |
| sum(include_bond), |
| num_pp_bonds, |
| num_bad_bonds, |
| ) |
| if sum(include_bond) > 0: |
| |
| |
| |
| new_bonds = struc.bonds[np.array(include_bond, dtype=bool)] |
| else: |
| new_bonds = structure.Bonds.make_empty() |
| struc = struc.copy_and_update(bonds=new_bonds) |
|
|
| if struc.bonds and remove_nonsymmetric_bonds: |
| |
| polymer_ligand_bonds = inter_chain_bonds.get_polymer_ligand_bonds( |
| struc, |
| only_glycan_ligands=False, |
| ) |
| if polymer_ligand_bonds: |
| if covalent_bond_cleaning.has_nonsymmetric_bonds_on_symmetric_polymer_chains( |
| struc, polymer_ligand_bonds |
| ): |
| from_atom_idxs, dest_atom_idxs = struc.bonds.get_atom_indices( |
| struc.atom_key |
| ) |
| poly_chain_types = list(mmcif_names.POLYMER_CHAIN_TYPES) |
| is_polymer_bond = np.logical_or( |
| np.isin(struc.chain_type[from_atom_idxs], poly_chain_types), |
| np.isin(struc.chain_type[dest_atom_idxs], poly_chain_types), |
| ) |
| struc = struc.copy_and_update(bonds=struc.bonds[~is_polymer_bond]) |
|
|
| return struc, metadata |
|
|
|
|
| def create_empty_output_struc_and_layout( |
| struc: structure.Structure, |
| ccd: chemical_components.Ccd, |
| *, |
| with_hydrogens: bool = False, |
| skip_unk: bool = False, |
| polymer_ligand_bonds: atom_layout.AtomLayout | None = None, |
| ligand_ligand_bonds: atom_layout.AtomLayout | None = None, |
| drop_ligand_leaving_atoms: bool = False, |
| ) -> tuple[structure.Structure, atom_layout.AtomLayout]: |
| """Make zero-coordinate structure from all physical residues. |
| |
| Args: |
| struc: Structure object. |
| ccd: The chemical components dictionary. |
| with_hydrogens: Whether to keep hydrogen atoms in structure. |
| skip_unk: Whether to remove unknown residues from structure. |
| polymer_ligand_bonds: Bond information for polymer-ligand pairs. |
| ligand_ligand_bonds: Bond information for ligand-ligand pairs. |
| drop_ligand_leaving_atoms: Flag for handling leaving atoms for ligands. |
| |
| Returns: |
| Tuple of structure with all bonds, physical residues and coordinates set to |
| 0 and a flat atom layout of empty structure. |
| """ |
| bonded_atom_pairs = [] |
| if polymer_ligand_bonds: |
| for chain_ids, res_ids, atom_names in zip( |
| polymer_ligand_bonds.chain_id, |
| polymer_ligand_bonds.res_id, |
| polymer_ligand_bonds.atom_name, |
| strict=True, |
| ): |
| bonded_atom_pairs.append(( |
| (chain_ids[0], res_ids[0], atom_names[0]), |
| (chain_ids[1], res_ids[1], atom_names[1]), |
| )) |
| if ligand_ligand_bonds: |
| for chain_ids, res_ids, atom_names in zip( |
| ligand_ligand_bonds.chain_id, |
| ligand_ligand_bonds.res_id, |
| ligand_ligand_bonds.atom_name, |
| strict=True, |
| ): |
| bonded_atom_pairs.append(( |
| (chain_ids[0], res_ids[0], atom_names[0]), |
| (chain_ids[1], res_ids[1], atom_names[1]), |
| )) |
| residues = atom_layout.residues_from_structure( |
| struc, include_missing_residues=True |
| ) |
|
|
| flat_output_layout = atom_layout.make_flat_atom_layout( |
| residues, |
| ccd=ccd, |
| with_hydrogens=with_hydrogens, |
| skip_unk_residues=skip_unk, |
| polymer_ligand_bonds=polymer_ligand_bonds, |
| ligand_ligand_bonds=ligand_ligand_bonds, |
| drop_ligand_leaving_atoms=drop_ligand_leaving_atoms, |
| ) |
|
|
| empty_output_struc = atom_layout.make_structure( |
| flat_layout=flat_output_layout, |
| atom_coords=np.zeros((flat_output_layout.shape[0], 3)), |
| name=struc.name, |
| atom_b_factors=None, |
| all_physical_residues=residues, |
| ) |
| if bonded_atom_pairs: |
| empty_output_struc = empty_output_struc.add_bonds( |
| bonded_atom_pairs, bond_type=mmcif_names.COVALENT_BOND |
| ) |
|
|
| return empty_output_struc, flat_output_layout |
|
|