def phar_from_mol(ligand): """Create Pharmacophore from given RDKit.Chem.Mol object.""" if not isinstance(ligand, Chem.Mol): raise TypeError("Invalid ligand! Expected RDKit.Chem.Mol object, got " "%s instead" % type(ligand).__name__) matches = {} for (phar, pattern) in PATTERNS.items(): atoms = list(zip(*ligand.GetSubstructMatches(pattern))) if len(atoms) > 0: matches[phar] = list(atoms[0]) else: matches[phar] = [] points = {} # graph ids of matched atoms nodes = [] idx = 0 for (phar, atoms) in matches.items(): for atom in atoms: if atom in points: nodes[points[atom]]["type"][phar] = 1.0 else: nodes.append({"label": idx, "type": {phar: 1.0}, "freq": 1.0}) points[atom] = idx idx += 1 edges = np.zeros((idx, idx)) keys = sorted(points.keys()) for i in range(len(keys)): for j in range(i): dist = float( __count_bonds( ligand, keys[i], keys[j], [keys[k] for k in range(len(keys)) if k not in [i, j]])) if dist > -1: edges[points[keys[i]], points[keys[j]]] = dist edges[points[keys[j]], points[keys[i]]] = dist if not ligand.HasProp("_Name"): return Pharmacophore(nodes, edges, molecules=1.0) else: return Pharmacophore(nodes, edges, molecules=1.0, title=ligand.GetProp("_Name"))
def setUp(self): from decaf import Pharmacophore nodes = [{ "label": 0, "freq": 2.0, "type": { "HH": 2.0, "AR": 2.0, "R": 2.0 } }, { "label": 1, "freq": 2.0, "type": { "HH": 2.0, "AR": 2.0, "R": 2.0 } }, { "label": 2, "freq": 2.0, "type": { "HH": 2.0, "AR": 2.0, "R": 2.0 } }, { "label": 3, "freq": 2.0, "type": { "HH": 2.0, "AR": 2.0, "R": 2.0 } }, { "label": 4, "freq": 2.0, "type": { "HH": 2.0, "AR": 2.0, "R": 2.0 } }, { "label": 5, "freq": 2.0, "type": { "AR": 2.0, "R": 2.0 } }, { "label": 6, "freq": 2.0, "type": { "HA": 2.0 } }, { "label": 7, "freq": 2.0, "type": { "HH": 2.0 } }, { "label": 8, "freq": 1.0, "type": { "HA": 1.0, "HD": 1.0 } }, { "label": 9, "freq": 1.0, "type": { "HA": 1.0, "HD": 1.0 } }] edges = np.array([[0., 1., 0., 0., 0., 1., 0., 0., 0., 0.], [1., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 1., 0., 0., 0., 0.], [1., 0., 0., 0., 1., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 2., 0., 0.], [0., 0., 0., 0., 0., 0., 2., 0., 1., 1.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 1.], [0., 0., 0., 0., 0., 0., 0., 1., 1., 0.]]) self.phar = Pharmacophore(nodes, edges, molecules=2, title="test")
def combine_pharmacophores(p1, p2, dist_tol=0.0, freq_cutoff=0.0, add_neighbours=False): """Create new model from Pharmacophores p1 and p2 Find common part of two Pharmacophores, add unique elements and calculate new frequencies and distances. Args: p1, p2 (Pharmacophore): models to combine dist_tol (float, optional): accept distance differences below this threshold freq_cutoff (float, optional): skip unique nodes with frequencies below this threshold add_neighbours (bool, optional): if True, try to extend alignment by adding neighbours of already aligned nodes. Returns: Pharmacophore: combination of p1 and p2 """ if not isinstance(p1, Pharmacophore): raise TypeError("Expected Pharmacophore, got %s instead" % type(p1).__name__) if not isinstance(p2, Pharmacophore): raise TypeError("Expected Pharmacophore, got %s instead" % type(p2).__name__) if not isinstance(dist_tol, (int, float)): raise TypeError("dist_tol must be float or int!") if dist_tol < 0: raise ValueError("dist_tol must be greater than or equal 0") if not isinstance(freq_cutoff, (int, float)): raise TypeError("freq_cutoff must be float or int!") if freq_cutoff < 0 or freq_cutoff > 1: raise ValueError("Invalid freq_cutoff! Use value in the range [0,1]") if not isinstance(add_neighbours, bool): raise TypeError("add_neighbours must be bool!") # find common pharmacophore _, _, mapped_nodes = map_pharmacophores(p1, p2, dist_tol, coarse_grained=False, add_neighbours=add_neighbours) dist1 = distances(p1) dist1[p1.edges > 0] = p1.edges[p1.edges > 0] dist2 = distances(p2) dist2[p2.edges > 0] = p2.edges[p2.edges > 0] # we will need it later added = {0: {}, 1: {}} # create new graph from common part molecules = p1.molecules + p2.molecules title = "(" + p1.title + ")+(" + p2.title + ")" nodes = [] idx = 0 for i in range(len(mapped_nodes[0])): u = p1.nodes[mapped_nodes[0][i]] v = p2.nodes[mapped_nodes[1][i]] _, types = compare_nodes(u, v) nodes.append({ "label": idx, "type": types, "freq": u["freq"] + v["freq"] }) for j in [0, 1]: added[j][idx] = mapped_nodes[j][i] idx += 1 # add edges edges = np.zeros((idx, idx)) for i in range(idx): no1 = (added[0][i], added[1][i]) for j in range(i): dist = 0.0 no2 = (added[0][j], added[1][j]) freq1 = p1.nodes[no1[0]]["freq"] + p1.nodes[no2[0]]["freq"] freq2 = p2.nodes[no1[1]]["freq"] + p2.nodes[no2[1]]["freq"] if p1.edges[no1[0], no2[0]] or p2.edges[no1[1], no2[1]]: d1 = dist1[no1[0], no2[0]] d2 = dist2[no1[1], no2[1]] dist = (d1 * freq1 + d2 * freq2) / (freq1 + freq2) edges[i, j] = edges[j, i] = dist # do not warn about empty pharmacophore (nodes might be added latter) # warn about empty common part instead warnings.simplefilter("ignore", UserWarning) new_p = Pharmacophore(nodes=nodes, edges=edges, molecules=molecules, title=title) warnings.simplefilter("always", UserWarning) if new_p.numnodes == 0: warnings.warn("Empty common part!") # add unique elements freq_cutoff = molecules * freq_cutoff to_add = [ [ i for i in range(p1.numnodes) if i not in mapped_nodes[0] and p1.nodes[i]["freq"] >= freq_cutoff ], [ i for i in range(p2.numnodes) if i not in mapped_nodes[1] and p2.nodes[i]["freq"] >= freq_cutoff ] ] for (nr, phar) in {0: p1, 1: p2}.items(): for n in to_add[nr]: added[nr][idx] = n new_p.add_node(phar.nodes[n].copy()) new_p.nodes[idx]["label"] = idx for (k, v) in added[nr].items(): if phar.edges[n, v] > 0: new_p.add_edge(k, idx, phar.edges[n, v]) idx += 1 # check if new pharmacophore is connected components = split_components(new_p) comp_nr = len(components) if comp_nr > 1: # shortest distances between components comp_dist = np.zeros((comp_nr, comp_nr)) + float("inf") # nearest_node[i, j] == id of node from component j, that is nearest to # component i nearest_node = np.zeros((comp_nr, comp_nr), dtype=int) for i in range(comp_nr): for j in range(i): shortest_dist = float("inf") nearest_nodes = [None, None] for n1 in components[i]: for n2 in components[j]: if n1 in added[0] and n2 in added[0]: d1 = dist1[added[0][n1], added[0][n2]] freq1 = p1.nodes[added[0][n1]]["freq"] + \ p1.nodes[added[0][n2]]["freq"] else: d1 = 0 freq1 = 0 if n1 in added[1] and n2 in added[1]: d2 = dist2[added[1][n1], added[1][n2]] freq2 = p2.nodes[added[1][n1]]["freq"] + \ p2.nodes[added[1][n2]]["freq"] else: d2 = 0 freq2 = 0 if (freq1 + freq2) == 0: dist = float("inf") else: dist = (d1 * freq1 + d2 * freq2) / (freq1 + freq2) if dist < shortest_dist: shortest_dist = dist nearest_nodes = [n1, n2] comp_dist[i, j] = comp_dist[j, i] = shortest_dist if shortest_dist < float("inf"): nearest_node[i, j] = nearest_nodes[1] nearest_node[j, i] = nearest_nodes[0] sorted_connections = np.unravel_index(comp_dist.argsort(axis=None), comp_dist.shape) # connect components for i, j in zip(*sorted_connections): n1 = int(nearest_node[i, j]) n2 = int(nearest_node[j, i]) new_p.add_edge(n1, n2, comp_dist[i, j]) # check if graph is already connected if len(split_components(new_p)) == 1: break return new_p