Download model/datasets/process_mols.py from OneScience-Group/SurfDock: direct link, hf CLI and curl.
- Browser
- Download file 30 kB
-
https://huggingface.co/OneScience-Group/SurfDock/resolve/main/model/datasets/process_mols.py
- Command line
-
hf download hf://OneScience-Group/SurfDock/model/datasets/process_mols.py
-
curl -L -o process_mols.py https://huggingface.co/OneScience-Group/SurfDock/resolve/main/model/datasets/process_mols.py
30 kB
| import copy | |
| import os | |
| import warnings | |
| import numpy as np | |
| import scipy.spatial as spa | |
| import torch | |
| from Bio.PDB import PDBParser | |
| from Bio.PDB.PDBExceptions import PDBConstructionWarning | |
| from rdkit import Chem | |
| from rdkit.Chem.rdchem import BondType as BT | |
| from rdkit.Chem import AllChem, GetPeriodicTable, RemoveHs | |
| from rdkit.Geometry import Point3D | |
| from scipy import spatial | |
| from scipy.special import softmax | |
| from torch_cluster import radius_graph | |
| from itertools import permutations | |
| import MDAnalysis as mda | |
| from MDAnalysis.analysis import distances | |
| from scipy.spatial import distance_matrix | |
| import torch.nn.functional as F | |
| from datasets.conformer_matching import get_torsion_angles, optimize_rotatable_bonds | |
| from utils.torsion import get_transformation_mask | |
| from loguru import logger | |
| def remove_all_hs(mol,sanitize=None): | |
| params = Chem.RemoveHsParameters() | |
| params.removeAndTrackIsotopes = True | |
| params.removeDefiningBondStereo = True | |
| params.removeDegreeZero = True | |
| params.removeDummyNeighbors = True | |
| params.removeHigherDegrees = True | |
| params.removeHydrides = True | |
| params.removeInSGroups = True | |
| params.removeIsotopes = True | |
| params.removeMapped = True | |
| params.removeNonimplicit = True | |
| params.removeOnlyHNeighbors = True | |
| params.removeWithQuery = True | |
| params.removeWithWedgedBond = True | |
| if sanitize is not None: | |
| params.sanitize = sanitize | |
| return RemoveHs(mol, params) | |
| METAL = ["LI","NA","K","RB","CS","MG","TL","CU","AG","BE","NI","PT","ZN","CO","PD","AG","CR","FE","V","MN","HG",'GA', | |
| "CD","YB","CA","SN","PB","EU","SR","SM","BA","RA","AL","IN","TL","Y","LA","CE","PR","ND","GD","TB","DY","ER", | |
| "TM","LU","HF","ZR","CE","U","PU","TH"] | |
| def obtain_self_dist(res): | |
| try: | |
| #xx = res.atoms.select_atoms("not name H*") | |
| xx = res.atoms | |
| dists = distances.self_distance_array(xx.positions) | |
| ca = xx.select_atoms("name CA") | |
| c = xx.select_atoms("name C") | |
| n = xx.select_atoms("name N") | |
| o = xx.select_atoms("name O") | |
| return [dists.max()*0.1, dists.min()*0.1, distances.dist(ca,o)[-1][0]*0.1, distances.dist(o,n)[-1][0]*0.1, distances.dist(n,c)[-1][0]*0.1] | |
| except: | |
| return [0, 0, 0, 0, 0] | |
| def obtain_dihediral_angles(res): | |
| try: | |
| if res.phi_selection() is not None: | |
| phi = res.phi_selection().dihedral.value() | |
| else: | |
| phi = 0 | |
| if res.psi_selection() is not None: | |
| psi = res.psi_selection().dihedral.value() | |
| else: | |
| psi = 0 | |
| if res.omega_selection() is not None: | |
| omega = res.omega_selection().dihedral.value() | |
| else: | |
| omega = 0 | |
| if res.chi1_selection() is not None: | |
| chi1 = res.chi1_selection().dihedral.value() | |
| else: | |
| chi1 = 0 | |
| return [phi*0.01, psi*0.01, omega*0.01, chi1*0.01] | |
| except: | |
| return [0, 0, 0, 0] | |
| ##'FE', 'SR', 'GA', 'IN', 'ZN', 'CU', 'MN', 'SR', 'K' ,'NI', 'NA', 'CD' 'MG','CO','HG', 'CS', 'CA', | |
| def obatin_edge(u, cutoff=10.0): | |
| edgeids = [] | |
| dismin = [] | |
| dismax = [] | |
| for res1, res2 in permutations(u.residues, 2): | |
| dist = calc_dist(res1, res2) | |
| if dist.min() <= cutoff: | |
| edgeids.append([res1.ix, res2.ix]) | |
| dismin.append(dist.min()*0.1) | |
| dismax.append(dist.max()*0.1) | |
| return edgeids, np.array([dismin, dismax]).T | |
| def check_connect(u, i, j): | |
| try: | |
| if abs(i-j) != 1: | |
| return 0 | |
| else: | |
| if i > j: | |
| i = j | |
| nb1 = len(u.residues[i].get_connections("bonds")) | |
| nb2 = len(u.residues[i+1].get_connections("bonds")) | |
| nb3 = len(u.residues[i:i+2].get_connections("bonds")) | |
| if nb1 + nb2 == nb3 + 1: | |
| return 1 | |
| else: | |
| return 0 | |
| except Exception as e: | |
| if 'not contain bonds information' in str(e): | |
| return 0 | |
| else: | |
| raise e | |
| def calc_dist(res1, res2): | |
| #xx1 = res1.atoms.select_atoms('not name H*') | |
| #xx2 = res2.atoms.select_atoms('not name H*') | |
| #dist_array = distances.distance_array(xx1.positions,xx2.positions) | |
| dist_array = distances.distance_array(res1.atoms.positions,res2.atoms.positions) | |
| return dist_array | |
| #return dist_array.max()*0.1, dist_array.min()*0.1 | |
| def obtain_resname(res): | |
| if res.resname[:2] == "CA": | |
| resname = "CA" | |
| elif res.resname[:2] == "FE": | |
| resname = "FE" | |
| elif res.resname[:2] == "CU": | |
| resname = "CU" | |
| else: | |
| resname = res.resname.strip() | |
| if resname in METAL: | |
| return "M" | |
| else: | |
| return resname | |
| biopython_parser = PDBParser() | |
| periodic_table = GetPeriodicTable() | |
| # CHI_SQUAREPLANAR | |
| allowable_features = { | |
| 'possible_atomic_num_list': list(range(1, 119)) + ['misc'], | |
| 'possible_chirality_list': [ | |
| 'CHI_UNSPECIFIED', | |
| 'CHI_TETRAHEDRAL_CW', | |
| 'CHI_TETRAHEDRAL_CCW', | |
| 'CHI_OTHER', | |
| 'CHI_TETRAHEDRAL', | |
| 'CHI_ALLENE', | |
| 'CHI_SQUAREPLANAR', | |
| 'CHI_TRIGONALBIPYRAMIDAL', | |
| 'CHI_OCTAHEDRAL' | |
| ], | |
| 'possible_degree_list': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'misc'], | |
| 'possible_numring_list': [0, 1, 2, 3, 4, 5, 6, 'misc'], | |
| 'possible_implicit_valence_list': [0, 1, 2, 3, 4, 5, 6, 'misc'], | |
| 'possible_formal_charge_list': [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 'misc'], | |
| 'possible_numH_list': [0, 1, 2, 3, 4, 5, 6, 7, 8, 'misc'], | |
| 'possible_number_radical_e_list': [0, 1, 2, 3, 4, 'misc'], | |
| 'possible_hybridization_list': [ | |
| 'SP', 'SP2', 'SP3', 'SP3D', 'SP3D2', 'misc' | |
| ], | |
| 'possible_is_aromatic_list': [False, True], | |
| 'possible_is_in_ring3_list': [False, True], | |
| 'possible_is_in_ring4_list': [False, True], | |
| 'possible_is_in_ring5_list': [False, True], | |
| 'possible_is_in_ring6_list': [False, True], | |
| 'possible_is_in_ring7_list': [False, True], | |
| 'possible_is_in_ring8_list': [False, True], | |
| 'possible_amino_acids': ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', | |
| 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL', 'HIP', 'HIE', 'TPO', 'HID', 'LEV', 'MEU', | |
| 'PTR', 'GLV', 'CYT', 'SEP', 'HIZ', 'CYM', 'GLM', 'ASQ', 'TYS', 'CYX', 'GLZ','M', 'misc'], | |
| 'possible_atom_type_2': ['C*', 'CA', 'CB', 'CD', 'CE', 'CG', 'CH', 'CZ', 'N*', 'ND', 'NE', 'NH', 'NZ', 'O*', 'OD', | |
| 'OE', 'OG', 'OH', 'OX', 'S*', 'SD', 'SG', 'misc'], | |
| 'possible_atom_type_3': ['C', 'CA', 'CB', 'CD', 'CD1', 'CD2', 'CE', 'CE1', 'CE2', 'CE3', 'CG', 'CG1', 'CG2', 'CH2', | |
| 'CZ', 'CZ2', 'CZ3', 'N', 'ND1', 'ND2', 'NE', 'NE1', 'NE2', 'NH1', 'NH2', 'NZ', 'O', 'OD1', | |
| 'OD2', 'OE1', 'OE2', 'OG', 'OG1', 'OH', 'OXT', 'SD', 'SG', 'misc'], | |
| } | |
| bonds = {BT.SINGLE: 0, BT.DOUBLE: 1, BT.TRIPLE: 2, BT.AROMATIC: 3} | |
| lig_feature_dims = (list(map(len, [ | |
| allowable_features['possible_atomic_num_list'], | |
| allowable_features['possible_chirality_list'], | |
| allowable_features['possible_degree_list'], | |
| allowable_features['possible_formal_charge_list'], | |
| allowable_features['possible_implicit_valence_list'], | |
| allowable_features['possible_numH_list'], | |
| allowable_features['possible_number_radical_e_list'], | |
| allowable_features['possible_hybridization_list'], | |
| allowable_features['possible_is_aromatic_list'], | |
| allowable_features['possible_numring_list'], | |
| allowable_features['possible_is_in_ring3_list'], | |
| allowable_features['possible_is_in_ring4_list'], | |
| allowable_features['possible_is_in_ring5_list'], | |
| allowable_features['possible_is_in_ring6_list'], | |
| allowable_features['possible_is_in_ring7_list'], | |
| allowable_features['possible_is_in_ring8_list'], | |
| ])), 0) # number of scalar features | |
| rec_atom_feature_dims = (list(map(len, [ | |
| allowable_features['possible_amino_acids'], | |
| allowable_features['possible_atomic_num_list'], | |
| allowable_features['possible_atom_type_2'], | |
| allowable_features['possible_atom_type_3'], | |
| ])), 0) | |
| rec_residue_feature_dims = (list(map(len, [ | |
| allowable_features['possible_amino_acids'] | |
| ])), 5+4) | |
| def lig_atom_featurizer(mol): | |
| ringinfo = mol.GetRingInfo() | |
| atom_features_list = [] | |
| for idx, atom in enumerate(mol.GetAtoms()): | |
| atom_features_list.append([ | |
| safe_index(allowable_features['possible_atomic_num_list'], atom.GetAtomicNum()), | |
| allowable_features['possible_chirality_list'].index(str(atom.GetChiralTag())) if str(atom.GetChiralTag()) in allowable_features['possible_chirality_list'] else 0, | |
| safe_index(allowable_features['possible_degree_list'], atom.GetTotalDegree()), | |
| safe_index(allowable_features['possible_formal_charge_list'], atom.GetFormalCharge()), | |
| safe_index(allowable_features['possible_implicit_valence_list'], atom.GetImplicitValence()), | |
| safe_index(allowable_features['possible_numH_list'], atom.GetTotalNumHs()), | |
| safe_index(allowable_features['possible_number_radical_e_list'], atom.GetNumRadicalElectrons()), | |
| safe_index(allowable_features['possible_hybridization_list'], str(atom.GetHybridization())), | |
| allowable_features['possible_is_aromatic_list'].index(atom.GetIsAromatic()), | |
| safe_index(allowable_features['possible_numring_list'], ringinfo.NumAtomRings(idx)), | |
| allowable_features['possible_is_in_ring3_list'].index(ringinfo.IsAtomInRingOfSize(idx, 3)), | |
| allowable_features['possible_is_in_ring4_list'].index(ringinfo.IsAtomInRingOfSize(idx, 4)), | |
| allowable_features['possible_is_in_ring5_list'].index(ringinfo.IsAtomInRingOfSize(idx, 5)), | |
| allowable_features['possible_is_in_ring6_list'].index(ringinfo.IsAtomInRingOfSize(idx, 6)), | |
| allowable_features['possible_is_in_ring7_list'].index(ringinfo.IsAtomInRingOfSize(idx, 7)), | |
| allowable_features['possible_is_in_ring8_list'].index(ringinfo.IsAtomInRingOfSize(idx, 8)), | |
| ]) | |
| return torch.tensor(atom_features_list) | |
| def rec_residue_featurizer(mdn_rec): | |
| feature_list = [] | |
| # self_dist_dihediral_angles=[] | |
| for residue_mdn in mdn_rec.residues: | |
| feature_list.append([safe_index(allowable_features['possible_amino_acids'], obtain_resname(residue_mdn))] + \ | |
| obtain_self_dist(residue_mdn) + obtain_dihediral_angles(residue_mdn)) | |
| # obtain_self_dist(residue_mdn) + obtain_dihediral_angles(residue_mdn) | |
| return torch.tensor(feature_list, dtype=torch.float32) # (N_res, 1 + 5 + 4) | |
| # add rtmscore feature to res feature | |
| def safe_index(l, e): | |
| """ Return index of element e in list l. If e is not present, return the last index """ | |
| try: | |
| return l.index(e) | |
| except: | |
| return len(l) - 1 | |
| def parse_receptor(pdbid, pdbbind_dir): | |
| rec = parsePDB(pdbid, pdbbind_dir) | |
| return rec | |
| def parsePDB(pdbid, pdbbind_dir): | |
| rec_path = os.path.join(pdbbind_dir, pdbid, f'{pdbid}_pocket.pdb') | |
| return parse_pdb_from_path(rec_path) | |
| def parse_pdb_from_path(path): | |
| with warnings.catch_warnings(): | |
| warnings.filterwarnings("ignore", category=PDBConstructionWarning) | |
| structure = biopython_parser.get_structure('random_id', path) | |
| rec = structure[0] | |
| return rec | |
| from Bio.PDB import PDBIO | |
| def extract_receptor_structure(rec, lig, save_file,lm_embedding_chains=None): | |
| if os.path.exists(save_file): | |
| return None, None, None, None, None, lm_embedding_chains | |
| conf = lig.GetConformer() | |
| lig_coords = conf.GetPositions() | |
| min_distances = [] | |
| coords = [] | |
| c_alpha_coords = [] | |
| n_coords = [] | |
| c_coords = [] | |
| valid_chain_ids = [] | |
| lengths = [] | |
| for i, chain in enumerate(rec): | |
| chain_coords = [] # num_residues, num_atoms, 3 | |
| chain_c_alpha_coords = [] | |
| chain_n_coords = [] | |
| chain_c_coords = [] | |
| count = 0 | |
| invalid_res_ids = [] | |
| for res_idx, residue in enumerate(chain): | |
| if residue.get_resname() == 'HOH': | |
| invalid_res_ids.append(residue.get_id()) | |
| continue | |
| residue_coords = [] | |
| c_alpha, n, c = None, None, None | |
| for atom in residue: | |
| if atom.name == 'CA': | |
| c_alpha = list(atom.get_vector()) | |
| if atom.name == 'N': | |
| n = list(atom.get_vector()) | |
| if atom.name == 'C': | |
| c = list(atom.get_vector()) | |
| residue_coords.append(list(atom.get_vector())) | |
| if c_alpha != None and n != None and c != None: | |
| # only append residue if it is an amino acid and not some weird molecule that is part of the complex | |
| chain_c_alpha_coords.append(c_alpha) | |
| chain_n_coords.append(n) | |
| chain_c_coords.append(c) | |
| chain_coords.append(np.array(residue_coords)) | |
| count += 1 | |
| else: | |
| invalid_res_ids.append(residue.get_id()) | |
| for res_id in invalid_res_ids: | |
| chain.detach_child(res_id) | |
| if len(chain_coords) > 0: | |
| all_chain_coords = np.concatenate(chain_coords, axis=0) | |
| distances = spatial.distance.cdist(lig_coords, all_chain_coords) | |
| min_distance = distances.min() | |
| else: | |
| min_distance = np.inf | |
| min_distances.append(min_distance) | |
| lengths.append(count) | |
| coords.append(chain_coords) | |
| c_alpha_coords.append(np.array(chain_c_alpha_coords)) | |
| n_coords.append(np.array(chain_n_coords)) | |
| c_coords.append(np.array(chain_c_coords)) | |
| if not count == 0: valid_chain_ids.append(chain.get_id()) | |
| min_distances = np.array(min_distances) | |
| if len(valid_chain_ids) == 0: | |
| valid_chain_ids.append(np.argmin(min_distances)) | |
| valid_coords = [] | |
| valid_c_alpha_coords = [] | |
| valid_n_coords = [] | |
| valid_c_coords = [] | |
| valid_lengths = [] | |
| invalid_chain_ids = [] | |
| # valid_lm_embeddings = [] | |
| for i, chain in enumerate(rec): | |
| if chain.get_id() in valid_chain_ids: | |
| valid_coords.append(coords[i]) | |
| valid_c_alpha_coords.append(c_alpha_coords[i]) | |
| valid_n_coords.append(n_coords[i]) | |
| valid_c_coords.append(c_coords[i]) | |
| valid_lengths.append(lengths[i]) | |
| else: | |
| invalid_chain_ids.append(chain.get_id()) | |
| coords = [item for sublist in valid_coords for item in sublist] # list with n_residues arrays: [n_atoms, 3] | |
| c_alpha_coords = np.concatenate(valid_c_alpha_coords, axis=0) # [n_residues, 3] | |
| n_coords = np.concatenate(valid_n_coords, axis=0) # [n_residues, 3] | |
| c_coords = np.concatenate(valid_c_coords, axis=0) # [n_residues, 3] | |
| # if lm_embedding_chains is not None: | |
| # lm_embeddings = lm_embedding_chains if lm_embedding_chains is not None else None | |
| for invalid_id in invalid_chain_ids: | |
| rec.detach_child(invalid_id) | |
| assert len(c_alpha_coords) == len(n_coords) | |
| assert len(c_alpha_coords) == len(c_coords) | |
| assert sum(valid_lengths) == len(c_alpha_coords) | |
| if lm_embedding_chains is not None: | |
| logger.info(f'Found {len(lm_embedding_chains)} LM embeddings for {len(c_alpha_coords)} residues') | |
| assert len(lm_embedding_chains) == len(n_coords) | |
| io = PDBIO() | |
| io.set_structure(rec) | |
| io.save(save_file) | |
| mol = Chem.MolFromPDBFile(save_file) | |
| Chem.MolToPDBFile(mol, save_file) | |
| return rec, coords, c_alpha_coords, n_coords, c_coords, lm_embedding_chains | |
| def one_of_k_encoding_unk(x, allowable_set): | |
| """Maps inputs not in the allowable set to the last element.""" | |
| if x not in allowable_set: | |
| x = allowable_set[-1] | |
| return [x == s for s in allowable_set] | |
| def get_lig_graph(mol, complex_graph,use_chirality = True): | |
| lig_coords = torch.from_numpy(mol.GetConformer().GetPositions()).float() | |
| atom_feats = lig_atom_featurizer(mol) | |
| row, col, edge_type = [], [], [] | |
| for bond in mol.GetBonds(): | |
| start, end = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx() | |
| row += [start, end] | |
| col += [end, start] | |
| bt = bond.GetBondType() | |
| bond_feats = [ | |
| bt == BT.SINGLE or bt == BT.UNSPECIFIED, bt ==BT.DOUBLE, | |
| bt == BT.TRIPLE, bt == BT.AROMATIC, | |
| bond.GetIsConjugated(), | |
| bond.IsInRing() | |
| ] | |
| if use_chirality: | |
| bond_feats = bond_feats + one_of_k_encoding_unk( | |
| str(bond.GetStereo()), | |
| ["STEREONONE", "STEREOANY", "STEREOZ", "STEREOE"]) | |
| # return np.array(bond_feats).astype(int) | |
| edge_type.append(bond_feats) | |
| edge_type.append(bond_feats) | |
| edge_index = torch.tensor([row, col], dtype=torch.long) | |
| edge_type = torch.tensor(edge_type, dtype=torch.float) | |
| # edge_attr = F.one_hot(edge_type, num_classes=len(bonds)).to(torch.float) | |
| complex_graph['ligand'].x = atom_feats | |
| complex_graph['ligand'].pos = lig_coords | |
| complex_graph['ligand', 'lig_bond', 'ligand'].edge_index = edge_index | |
| complex_graph['ligand', 'lig_bond', 'ligand'].edge_attr = edge_type | |
| return | |
| def generate_conformer(mol,useRandomCoords=True): | |
| prop_dict = mol.GetPropsAsDict() | |
| ps = AllChem.ETKDGv2() | |
| failures, id = 0, -1 | |
| while failures < 5 and id == -1: | |
| # if failures > 0: | |
| id = AllChem.EmbedMolecule(mol, ps) | |
| failures += 1 | |
| # logger.info(f'rdkit coords could not be generated. tried repeats={failures}.') | |
| if id == -1 and useRandomCoords: | |
| logger.info('rdkit coords could not be generated without using random coords. using random coords now.') | |
| ps.useRandomCoords = True | |
| ps.maxAttempts=1000 | |
| AllChem.EmbedMolecule(mol, ps) | |
| AllChem.MMFFOptimizeMolecule(mol, confId=0) | |
| for prop_name, prop_value in prop_dict.items(): | |
| mol.SetProp(prop_name, str(prop_value)) | |
| def initConformer(lig_mol,inference_mode='Screen'): | |
| # use the max frag to init conformer | |
| frags = Chem.GetMolFrags(lig_mol, asMols=True) | |
| lig_mol = max(frags, key=lambda x: x.GetNumAtoms()) | |
| lig_mol.RemoveAllConformers() | |
| lig_mol = Chem.AddHs(lig_mol) | |
| generate_conformer(lig_mol,useRandomCoords=False if inference_mode == 'Screen' else True) | |
| lig_mol = remove_all_hs(lig_mol, sanitize=True) | |
| return lig_mol | |
| def get_lig_graph_with_matching(mol_, complex_graph, popsize, maxiter, matching, keep_original, num_conformers, remove_hs): | |
| mol_maybe_noh = copy.deepcopy(mol_) | |
| if remove_hs: | |
| # try: | |
| mol_maybe_noh = remove_all_hs(mol_maybe_noh, sanitize=True) | |
| if keep_original: | |
| complex_graph['ligand'].orig_pos = mol_maybe_noh.GetConformer().GetPositions() | |
| if matching: | |
| rotable_bonds = get_torsion_angles(mol_maybe_noh) | |
| if not rotable_bonds: logger.info("no_rotable_bonds but still using it") | |
| for i in range(num_conformers): | |
| mol_rdkit = copy.deepcopy(mol_) | |
| mol_rdkit.RemoveAllConformers() | |
| mol_rdkit = AllChem.AddHs(mol_rdkit) | |
| generate_conformer(mol_rdkit) | |
| if remove_hs: | |
| mol_rdkit = remove_all_hs(mol_rdkit, sanitize=True) | |
| mol = copy.deepcopy(mol_maybe_noh) | |
| if rotable_bonds: | |
| optimize_rotatable_bonds(mol_rdkit, mol, rotable_bonds, popsize=popsize, maxiter=maxiter) | |
| mol.AddConformer(mol_rdkit.GetConformer()) | |
| rms_list = [] | |
| AllChem.AlignMolConformers(mol, RMSlist=rms_list) | |
| mol_rdkit.RemoveAllConformers() | |
| mol_rdkit.AddConformer(mol.GetConformers()[1]) | |
| if i == 0: | |
| complex_graph.rmsd_matching = rms_list[0] | |
| get_lig_graph(mol_rdkit, complex_graph) | |
| else: | |
| if torch.is_tensor(complex_graph['ligand'].pos): | |
| complex_graph['ligand'].pos = [complex_graph['ligand'].pos] | |
| complex_graph['ligand'].pos.append(torch.from_numpy(mol_rdkit.GetConformer().GetPositions()).float()) | |
| else: # no matching | |
| complex_graph.rmsd_matching = 0 | |
| if remove_hs: mol_ = remove_all_hs(mol_) | |
| get_lig_graph(mol_, complex_graph) | |
| edge_mask, mask_rotate = get_transformation_mask(complex_graph) | |
| complex_graph['ligand'].edge_mask = torch.tensor(edge_mask) | |
| complex_graph['ligand'].mask_rotate = mask_rotate | |
| return | |
| def obtain_ca_pos(res): | |
| if obtain_resname(res) == "M": | |
| return res.atoms.positions[0] | |
| else: | |
| try: | |
| pos = res.atoms.select_atoms("name CA").positions[0] | |
| return pos | |
| except: ##some residues loss the CA atoms | |
| return res.atoms.positions.mean(axis=0) | |
| def get_calpha_graph(mdn_rec,c_alpha_coords, n_coords, c_coords, complex_graph, cutoff=20, max_neighbor=None, lm_embeddings=None): | |
| # n_rel_pos = n_coords - c_alpha_coords | |
| # c_rel_pos = c_coords - c_alpha_coords | |
| # num_residues = len(c_alpha_coords) | |
| # if num_residues <= 1: | |
| # raise ValueError(f"rec contains only 1 residue!") | |
| edgeids, distm = obatin_edge(mdn_rec, cutoff) | |
| src_list, dst_list = zip(*edgeids) | |
| complex_graph['receptor', 'rec_contact', 'receptor'].edge_index = torch.from_numpy(np.asarray([src_list, dst_list])) | |
| ca_pos = torch.tensor(np.array([obtain_ca_pos(res) for res in mdn_rec.residues])) | |
| # ca_pos = torch.tensor(c_alpha_coords) | |
| center_pos = torch.tensor(mdn_rec.atoms.center_of_mass(compound='residues')) | |
| dis_matx_ca = distance_matrix(ca_pos, center_pos) | |
| cadist = torch.tensor([dis_matx_ca[i,j] for i,j in edgeids]) * 0.1 | |
| dis_matx_center = distance_matrix(center_pos, center_pos) | |
| cedist = torch.tensor([dis_matx_center[i,j] for i,j in edgeids]) * 0.1 | |
| edge_connect = torch.tensor(np.array([check_connect(mdn_rec, x, y) for x,y in zip(src_list, dst_list)])) | |
| complex_graph['receptor', 'rec_contact', 'receptor'].edge_attr = torch.cat([edge_connect.view(-1,1), cadist.view(-1,1), cedist.view(-1,1), torch.tensor(distm)], dim=1) | |
| RES_MAX_NATOMS=24 | |
| complex_graph['receptor'].pos = ca_pos | |
| complex_graph['receptor'].center_pos = center_pos | |
| complex_graph['receptor'].atoms_pos = torch.tensor(np.array([np.concatenate([res.atoms.positions, np.full((np.max([RES_MAX_NATOMS-len(res.atoms),0]), 3), np.nan)],axis=0)[:RES_MAX_NATOMS] for res in mdn_rec.residues])) | |
| node_feat = rec_residue_featurizer(mdn_rec) | |
| complex_graph['receptor'].x = torch.cat([node_feat, lm_embeddings], axis=1) if lm_embeddings is not None else node_feat | |
| return | |
| def rec_atom_featurizer(rec): | |
| atom_feats = [] | |
| for i, atom in enumerate(rec.get_atoms()): | |
| atom_name, element = atom.name, atom.element | |
| if element == 'CD': | |
| element = 'C' | |
| assert not element == '' | |
| try: | |
| atomic_num = periodic_table.GetAtomicNumber(element) | |
| except: | |
| atomic_num = -1 | |
| atom_feat = [safe_index(allowable_features['possible_amino_acids'], atom.get_parent().get_resname()), | |
| safe_index(allowable_features['possible_atomic_num_list'], atomic_num), | |
| safe_index(allowable_features['possible_atom_type_2'], (atom_name + '*')[:2]), | |
| safe_index(allowable_features['possible_atom_type_3'], atom_name)] | |
| atom_feats.append(atom_feat) | |
| return atom_feats | |
| def get_rec_graph(mda_rec_model, rec_coords, c_alpha_coords, n_coords, c_coords, complex_graph, rec_radius, c_alpha_max_neighbors=None, all_atoms=False, | |
| atom_radius=5, atom_max_neighbors=None, remove_hs=False, lm_embeddings=None): | |
| if all_atoms: | |
| return get_fullrec_graph(mda_rec_model, rec_coords, c_alpha_coords, n_coords, c_coords, complex_graph, | |
| c_alpha_cutoff=rec_radius, c_alpha_max_neighbors=c_alpha_max_neighbors, | |
| atom_cutoff=atom_radius, atom_max_neighbors=atom_max_neighbors, remove_hs=remove_hs,lm_embeddings=lm_embeddings) | |
| else: | |
| return get_calpha_graph(mda_rec_model, c_alpha_coords, n_coords, c_coords, complex_graph, rec_radius, c_alpha_max_neighbors,lm_embeddings=lm_embeddings) | |
| def get_fullrec_graph(rec, rec_coords, c_alpha_coords, n_coords, c_coords, complex_graph, c_alpha_cutoff=20, | |
| c_alpha_max_neighbors=None, atom_cutoff=5, atom_max_neighbors=None, remove_hs=False, lm_embeddings=None): | |
| # builds the receptor graph with both residues and atoms | |
| n_rel_pos = n_coords - c_alpha_coords | |
| c_rel_pos = c_coords - c_alpha_coords | |
| num_residues = len(c_alpha_coords) | |
| if num_residues <= 1: | |
| raise ValueError(f"rec contains only 1 residue!") | |
| # Build the k-NN graph of residues | |
| distances = spa.distance.cdist(c_alpha_coords, c_alpha_coords) | |
| src_list = [] | |
| dst_list = [] | |
| mean_norm_list = [] | |
| for i in range(num_residues): | |
| dst = list(np.where(distances[i, :] < c_alpha_cutoff)[0]) | |
| dst.remove(i) | |
| if c_alpha_max_neighbors != None and len(dst) > c_alpha_max_neighbors: | |
| dst = list(np.argsort(distances[i, :]))[1: c_alpha_max_neighbors + 1] | |
| if len(dst) == 0: | |
| dst = list(np.argsort(distances[i, :]))[1:2] # choose second because first is i itself | |
| logger.info(f'The c_alpha_cutoff {c_alpha_cutoff} was too small for one c_alpha such that it had no neighbors. ' | |
| f'So we connected it to the closest other c_alpha') | |
| assert i not in dst | |
| src = [i] * len(dst) | |
| src_list.extend(src) | |
| dst_list.extend(dst) | |
| valid_dist = list(distances[i, dst]) | |
| valid_dist_np = distances[i, dst] | |
| sigma = np.array([1., 2., 5., 10., 30.]).reshape((-1, 1)) | |
| weights = softmax(- valid_dist_np.reshape((1, -1)) ** 2 / sigma, axis=1) # (sigma_num, neigh_num) | |
| assert 1 - 1e-2 < weights[0].sum() < 1.01 | |
| diff_vecs = c_alpha_coords[src, :] - c_alpha_coords[dst, :] # (neigh_num, 3) | |
| mean_vec = weights.dot(diff_vecs) # (sigma_num, 3) | |
| denominator = weights.dot(np.linalg.norm(diff_vecs, axis=1)) # (sigma_num,) | |
| mean_vec_ratio_norm = np.linalg.norm(mean_vec, axis=1) / denominator # (sigma_num,) | |
| mean_norm_list.append(mean_vec_ratio_norm) | |
| assert len(src_list) == len(dst_list) | |
| node_feat = rec_residue_featurizer(rec) | |
| mu_r_norm = torch.from_numpy(np.array(mean_norm_list).astype(np.float32)) | |
| side_chain_vecs = torch.from_numpy( | |
| np.concatenate([np.expand_dims(n_rel_pos, axis=1), np.expand_dims(c_rel_pos, axis=1)], axis=1)) | |
| complex_graph['receptor'].x = torch.cat([node_feat, torch.tensor(lm_embeddings)], axis=1) if lm_embeddings is not None else node_feat | |
| complex_graph['receptor'].pos = torch.from_numpy(c_alpha_coords).float() | |
| complex_graph['receptor'].mu_r_norm = mu_r_norm | |
| complex_graph['receptor'].side_chain_vecs = side_chain_vecs.float() | |
| complex_graph['receptor', 'rec_contact', 'receptor'].edge_index = torch.from_numpy(np.asarray([src_list, dst_list])) | |
| src_c_alpha_idx = np.concatenate([np.asarray([i]*len(l)) for i, l in enumerate(rec_coords)]) | |
| atom_feat = torch.from_numpy(np.asarray(rec_atom_featurizer(rec))) | |
| atom_coords = torch.from_numpy(np.concatenate(rec_coords, axis=0)).float() | |
| if remove_hs: | |
| not_hs = (atom_feat[:, 1] != 0) | |
| src_c_alpha_idx = src_c_alpha_idx[not_hs] | |
| atom_feat = atom_feat[not_hs] | |
| atom_coords = atom_coords[not_hs] | |
| atoms_edge_index = radius_graph(atom_coords, atom_cutoff, max_num_neighbors=atom_max_neighbors if atom_max_neighbors else 1000) | |
| atom_res_edge_index = torch.from_numpy(np.asarray([np.arange(len(atom_feat)), src_c_alpha_idx])).long() | |
| complex_graph['atom'].x = atom_feat | |
| complex_graph['atom'].pos = atom_coords | |
| complex_graph['atom', 'atom_contact', 'atom'].edge_index = atoms_edge_index | |
| complex_graph['atom', 'atom_rec_contact', 'receptor'].edge_index = atom_res_edge_index | |
| return | |
| def write_mol_with_coords(mol, new_coords, path): | |
| w = Chem.SDWriter(path) | |
| conf = mol.GetConformer() | |
| for i in range(mol.GetNumAtoms()): | |
| x,y,z = new_coords.astype(np.double)[i] | |
| conf.SetAtomPosition(i,Point3D(x,y,z)) | |
| w.write(mol) | |
| w.close() | |
| def read_molecule(molecule_file, sanitize=False, calc_charges=False, remove_hs=False): | |
| if molecule_file.endswith('.mol2'): | |
| mol = Chem.MolFromMol2File(molecule_file, sanitize=False, removeHs=False) | |
| elif molecule_file.endswith('.sdf'): | |
| supplier = Chem.SDMolSupplier(molecule_file, sanitize=False, removeHs=False) | |
| mol = supplier[0] | |
| elif molecule_file.endswith('.pdbqt'): | |
| with open(molecule_file) as file: | |
| pdbqt_data = file.readlines() | |
| pdb_block = '' | |
| for line in pdbqt_data: | |
| pdb_block += '{}\n'.format(line[:66]) | |
| mol = Chem.MolFromPDBBlock(pdb_block, sanitize=False, removeHs=False) | |
| elif molecule_file.endswith('.pdb'): | |
| mol = Chem.MolFromPDBFile(molecule_file, sanitize=False, removeHs=False) | |
| else: | |
| raise ValueError('Expect the format of the molecule_file to be ' | |
| 'one of .mol2, .sdf, .pdbqt and .pdb, got {}'.format(molecule_file)) | |
| try: | |
| if sanitize or calc_charges: | |
| Chem.SanitizeMol(mol) | |
| if calc_charges: | |
| # Compute Gasteiger charges on the molecule. | |
| try: | |
| AllChem.ComputeGasteigerCharges(mol) | |
| except: | |
| logger.warning('Unable to compute charges for the molecule.') | |
| if remove_hs: | |
| mol = remove_all_hs(mol, sanitize=sanitize) | |
| except Exception as e: | |
| logger.info(e) | |
| logger.info("RDKit was unable to read the molecule.") | |
| return None | |
| return mol | |
| def read_sdf_or_mol2(sdf_fileName, mol2_fileName): | |
| mol = Chem.MolFromMolFile(sdf_fileName, sanitize=False) | |
| problem = False | |
| try: | |
| Chem.SanitizeMol(mol) | |
| mol = remove_all_hs(mol) | |
| except Exception as e: | |
| problem = True | |
| if problem: | |
| mol = Chem.MolFromMol2File(mol2_fileName, sanitize=False) | |
| try: | |
| Chem.SanitizeMol(mol) | |
| mol = remove_all_hs(mol) | |
| problem = False | |
| except Exception as e: | |
| problem = True | |
| return mol, problem | |