| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import copy |
| import logging |
|
|
| import numpy as np |
| from protenix.data.json_parser import ( |
| DNA_1to3, |
| PROTEIN_1to3, |
| RNA_1to3, |
| _build_polymer_atom_array, |
| add_reference_features, |
| build_ligand, |
| lig_file_to_atom_info, |
| rdkit_mol_to_atom_info, |
| ) |
| from protenix.data.parser import MMCIFParser |
| from protenix.utils.file_io import load_gzip_pickle |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def remove_unresolved_residue_in_atom_array(atom_array): |
| coord_mask = atom_array.is_resolved.astype(bool) |
| res_ids = atom_array.res_id |
| chain_ids = atom_array.chain_id |
| res_chain_ids_to_mask = set(zip(res_ids[coord_mask], chain_ids[coord_mask])) |
| new_mask = np.array( |
| [ |
| not (res_id, chain_id) in res_chain_ids_to_mask |
| for res_id, chain_id in zip(res_ids, chain_ids) |
| ] |
| ) |
| atom_array = atom_array[~new_mask] |
| return atom_array |
|
|
|
|
| def build_polymer_from_sequence(entity_info: dict): |
| """ |
| build a polymer from a polymer info dict |
| example: { |
| "name": "polymer", |
| "sequence": "GPDSMEEVVVPEEPPKLVSALATYVQQERLCTMFLSIANKLLPLKP", |
| "count": 1 |
| } |
| |
| Args: |
| item (dict): polymer info dict |
| |
| Returns: |
| dict: {"atom_array": biotite_AtomArray_object} |
| """ |
| poly_type, info = list(entity_info.items())[0] |
| if poly_type == "proteinChain": |
| ccd_seqs = [PROTEIN_1to3[x] for x in info["sequence"]] |
| if modifications := info.get("modifications"): |
| for m in modifications: |
| index = m["ptmPosition"] - 1 |
| mtype = m["ptmType"] |
| if mtype.startswith("CCD_"): |
| ccd_seqs[index] = mtype[4:] |
| else: |
| raise ValueError(f"unknown modification type: {mtype}") |
| if glycans := info.get("glycans"): |
| logging.warning(f"glycans not supported: {glycans}") |
| chain_array = _build_polymer_atom_array(ccd_seqs) |
|
|
| elif poly_type in ("dnaSequence", "rnaSequence"): |
| map_1to3 = DNA_1to3 if poly_type == "dnaSequence" else RNA_1to3 |
| ccd_seqs = [map_1to3[x] for x in info["sequence"]] |
| if modifications := info.get("modifications"): |
| for m in modifications: |
| index = m["basePosition"] - 1 |
| mtype = m["modificationType"] |
| if mtype.startswith("CCD_"): |
| ccd_seqs[index] = mtype[4:] |
| else: |
| raise ValueError(f"unknown modification type: {mtype}") |
| chain_array = _build_polymer_atom_array(ccd_seqs) |
|
|
| else: |
| raise ValueError( |
| "polymer type must be proteinChain, dnaSequence or rnaSequence" |
| ) |
| chain_array = add_reference_features(chain_array) |
| return {"atom_array": chain_array} |
|
|
|
|
| def build_polymer_from_bioassombly_dict(entity_info, remove_unresolved_residue): |
| poly_type, info = list(entity_info.items())[0] |
| path_file = entity_info[poly_type]["path"] |
| if path_file.endswith(".pkl.gz"): |
| bioassembly_dict = load_gzip_pickle(path_file) |
| else: |
| raise ValueError(f"Unsupported structure file {path_file}!") |
|
|
| mask_chain_id = entity_info[poly_type]["json_chain_id"] |
| atom_array = bioassembly_dict["atom_array"] |
|
|
| if remove_unresolved_residue: |
| atom_array = remove_unresolved_residue_in_atom_array(atom_array) |
|
|
| chain_array_mask = atom_array.chain_id == mask_chain_id |
| chain_array = atom_array[chain_array_mask] |
|
|
| if "hotspot" in entity_info[poly_type]: |
| hotspot = entity_info[poly_type]["hotspot"] |
| else: |
| hotspot = [] |
| hotspot = np.isin(chain_array.res_id, np.array(hotspot)) |
|
|
| if "noise_level" in entity_info[poly_type]: |
| noise = np.full(len(chain_array), float(entity_info[poly_type]["noise_level"])) |
| else: |
| noise = np.full(len(chain_array), 0.00) |
|
|
| conditional_label = np.full(len(chain_array), 1).astype(bool) |
|
|
| chain_array.set_annotation("noise_level", noise) |
| chain_array.set_annotation("conditional_label", conditional_label) |
| chain_array.set_annotation("hotspot", hotspot) |
| chain_array.set_annotation("coord_from_cif", chain_array.coord) |
| chain_array.set_annotation( |
| "coord_from_cif_is_resolved", chain_array.is_resolved.astype(bool) |
| ) |
| chain_array = add_reference_features(chain_array) |
|
|
| if "crop" in entity_info[poly_type] and entity_info[poly_type]["crop"] is not None: |
| crop = entity_info[poly_type]["crop"] |
| crop.replace(" ", "") |
| crop = crop.split(",") |
| save_list = [] |
| for pid in crop: |
| if "-" in pid: |
| s, e = pid.split("-") |
| length = int(e) - int(s) + 1 |
| save_num = [i + int(s) for i in range(0, length)] |
| save_list += save_num |
| else: |
| save_list.append(int(pid)) |
| crop_mask = np.isin(chain_array.res_id, np.array(save_list)) |
| chain_array = chain_array[crop_mask] |
|
|
| |
|
|
| return {"atom_array": chain_array} |
|
|
|
|
| def build_polymer(entity_info: dict, remove_unresolved_residue: bool = True): |
|
|
| poly_type, info = list(entity_info.items())[0] |
| if ( |
| entity_info[poly_type]["sequence_type"] == "condition" |
| and info.get("path", None) is not None |
| ): |
| return build_polymer_from_bioassombly_dict( |
| entity_info, remove_unresolved_residue |
| ) |
|
|
| assert entity_info[poly_type]["sequence_type"] in ["design", "condition"] |
| assert "sequence" in info |
| chain_array = build_polymer_from_sequence(entity_info=entity_info)["atom_array"] |
| |
| if "hotspot" in entity_info[poly_type]: |
| hotspot = entity_info[poly_type]["hotspot"] |
| else: |
| hotspot = [] |
| hotspot = np.isin(chain_array.res_id, np.array(hotspot)) |
| chain_array.set_annotation("hotspot", hotspot) |
|
|
| |
| noise = np.full(len(chain_array), 0.00) |
| chain_array.set_annotation("noise_level", noise) |
|
|
| |
| if entity_info[poly_type]["sequence_type"] == "design": |
| conditional_label = np.full(len(chain_array), 0).astype(bool) |
| else: |
| assert entity_info[poly_type]["sequence_type"] == "condition" |
| conditional_label = np.full(len(chain_array), 1).astype(bool) |
|
|
| chain_array.set_annotation("conditional_label", conditional_label.copy()) |
| res_name = chain_array.res_name.copy() |
| res_name[~conditional_label] = "xpb" |
| chain_array.set_annotation("res_name", res_name) |
|
|
| |
| chain_array.set_annotation("coord_from_cif", chain_array.coord * 0.0) |
| chain_array.set_annotation( |
| "coord_from_cif_is_resolved", np.full(len(chain_array), 0).astype(bool) |
| ) |
|
|
| if "is_resolved" not in chain_array._annot: |
| chain_array.set_annotation( |
| "is_resolved", np.ones((len(chain_array),)).astype(bool) |
| ) |
|
|
| if "crop" in entity_info[poly_type] and entity_info[poly_type]["crop"] is not None: |
| crop = entity_info[poly_type]["crop"] |
| crop.replace(" ", "") |
| crop = crop.split(",") |
| save_list = [] |
| for pid in crop: |
| if "-" in pid: |
| s, e = pid.split("-") |
| length = int(e) - int(s) + 1 |
| save_num = [i + int(s) for i in range(0, length)] |
| save_list += save_num |
| else: |
| save_list.append(int(pid)) |
| crop_mask = np.isin(chain_array.res_id, np.array(save_list)) |
| chain_array = chain_array[crop_mask] |
| return {"atom_array": chain_array} |
|
|
|
|
| def add_entity_atom_array(single_job_dict: dict) -> dict: |
| """ |
| add atom_array to each entity in single_job_dict |
| args: |
| single_job_dict (dict): input job dict |
| returns: |
| dict: deepcopy and updated job dict with atom_array |
| """ |
| single_job_dict = copy.deepcopy(single_job_dict) |
| sequences = single_job_dict["sequences"] |
| smiles_ligand_count = 0 |
| for entity_info in sequences: |
| if info := entity_info.get("proteinChain"): |
| atom_info = build_polymer(entity_info) |
| elif info := entity_info.get("dnaSequence"): |
| atom_info = build_polymer(entity_info) |
| elif info := entity_info.get("rnaSequence"): |
| atom_info = build_polymer(entity_info) |
| elif info := entity_info.get("condition_ligand"): |
| atom_info = build_polymer(entity_info) |
| elif info := entity_info.get("ligand"): |
| atom_info = build_ligand(entity_info) |
| if not info["ligand"].startswith("CCD_"): |
| smiles_ligand_count += 1 |
| assert smiles_ligand_count <= 99, "too many smiles ligands" |
| |
| atom_info["atom_array"].res_name[:] = f"l{smiles_ligand_count:02d}" |
| elif info := entity_info.get("ion"): |
| atom_info = build_ligand(entity_info) |
| else: |
| raise ValueError( |
| "entity type must be proteinChain, dnaSequence, rnaSequence, ligand or ion" |
| ) |
| info.update(atom_info) |
| return single_job_dict |
|
|