Source code for schrodinger.application.desmond.r_group_asl

from collections import defaultdict

import networkx as nx

import schrodinger.structure as structure
import schrodinger.structutils.analyze as analyze
import schrodinger.utils.log as log
from schrodinger.application.desmond import constants

N_HATOM = 1
MAX_NUM_REST_ATOM = 25
MIN_FUSED_RING = 7
LARGE_FRAG_ON_RING = 3

logger = log.get_output_logger(name="r_group_asl")


[docs]def countHeavyAtoms(st): """ This function counts the number of heavy atoms in the structure. :type st: `structure.Structure` :param st: Structure :rtype: `int` :return: number of heavy atoms """ n = 0 for a in st.atom: if a.element != "H": n += 1 return n
[docs]def getImportantRotatableBonds(st, atom_cutoff=6): """ This function returns important rotatable bonds. Important rotatable bonds are defined when both sides of the bond have either rings or heavy atoms greater than atom_cutoff. :type st: `structure.Structure` :param st: Structure :type atom_cutoff: `int` :param atom_cutoff: # of heavy atoms :return: generator for atom pairs in important rotatable bonds """ for (i, j) in analyze.rotatable_bonds_iterator(st): disconnected_st = st.copy() disconnected_st.deleteBond(i, j) # probably macro cycle if disconnected_st.mol_total == 1: yield (i, j) mol_i = disconnected_st.atom[i].getMolecule().extractStructure() mol_j = disconnected_st.atom[j].getMolecule().extractStructure() nheavy_i = countHeavyAtoms(mol_i) nheavy_j = countHeavyAtoms(mol_j) nring_i = len(mol_i.ring) nring_j = len(mol_j.ring) if ((nheavy_i > atom_cutoff or nring_i > 0) and (nheavy_j > atom_cutoff or nring_j > 0)): yield (i, j)
[docs]def get_fep_cts(cts): """ Find reactant and product ct from the input, and return touple of reference ligand and mutated ligand structures :param cts: input structures :return: ref_ct, mut_ct """ ref_ct = [] mut_ct = [] for ct in cts: if "s_fep_fragname" in ct.property: if ct.property["s_fep_fragname"] == "none": ref_ct.append(ct) else: mut_ct.append(ct) return ref_ct, mut_ct
[docs]def count_heavy_atoms(ct, atom_index_list): n = 0 for index in atom_index_list: if ct.atom[index].element != "H": n += 1 return n
[docs]def get_heavy_atoms(ct, atom_index_list): ret = [] for index in atom_index_list: if ct.atom[index].element != "H": ret.append(index) return ret
[docs]def get_fragment(b0, b1, ct): """ :param b0: first atom index for bond :param b1: second atom index for bond, this connects to the fragment returned :return: list of atom indice for all atom connected to b1 when bond b0-b1 is removed """ fragments = ct.copy() for a in fragments.atom: a.property["i_org_index"] = a.index if b0 and b1: fragments.deleteBond(b0, b1) if fragments.mol_total == 1: return [] mol_b1 = fragments.atom[b1].getMolecule().extractStructure() return [a.property["i_org_index"] for a in mol_b1.atom]
def _get_heavy_atom_fragments(ct, atom1, atom2): """ :param ct: input structure :type ct: structure.Structure :param atom1: first atom index in a bond :type atom1: int :param atom2: second atom index in a bond :type atom2: int :return: lists of atom indices of connected groups of heavy atoms after bond, (atom1, atom2), is removed from ct. The element lists are sorted in ascending number of atoms. :rtype: List[List[Int]] """ atoms = {a for a in ct.atom if not a.element == "H"} mol_graph = analyze.create_nx_graph(ct, atoms=atoms) mol_graph.remove_edge(atom1, atom2) return sorted(nx.connected_components(mol_graph), key=len)
[docs]def get_attachment_bonds(source_ct, dest_ct): """ :param source_ct: reference structure :type source_ct: `structure.Structure` :param dest_ct: mutant structure :type dest_ct: `structure.Structure` :return: [(int, int), (int, int)] list of attachment bond pairs: ((source_anchor, source_bridge), (dest_anchor, dest_bridge)) source_anchor and dest_anchor are matched pair of core atom numbers; source_bridge and dest_bridge are atoms connect to the anchor atom numbers (these could be None). The algorithm works by looking at each matched pair of atoms (core atoms) and track chemical changes. If there is a dummy atom attached to the mutant core atom, an attachment point is generated. If the chemical change is connected to more than one core atoms, ring or linker atom mutation is detected for attachment point generation after the dummy atom mutations. If any of the fragment to which linker atom connects has less than 6 heavy atoms, treat this fragment as functional group. In an attachment bond, the second atom connects to a mutating fragment. If there is a dummy atom, it is always the second atom. For bonds attached to ring/linker atom mutations, both atoms in one structure may map to atoms in the other. In case one atom is a core atom and the other is a mutating linker atom, the ring/linker atom must be the second atom. This way the calling function can use the second atom to decide which fragment is mutating across the attachment bond. """ d2s_map = { a: source_ct.atom[a.property[constants.FEP_MAPPING]] for a in dest_ct.atom if a.property[constants.FEP_MAPPING] } source_core = set(d2s_map.values()) dest_core = set(d2s_map) source_dummy = set(source_ct.atom) - source_core dest_dummy = set(dest_ct.atom) - dest_core ring_list = [r.getAtomIndices() for r in dest_ct.ring] linker = set() d_anchor = [] d_anchor_dummy = defaultdict(list) d_anchor_mapped = defaultdict(set) linker_as_dummy = set() def atom_index(atom): return atom.index for d in sorted(d2s_map, key=atom_index): s = d2s_map[d] dest_bonded_atoms = frozenset(d.bonded_atoms) source_bonded_atoms = frozenset(s.bonded_atoms) s_linked_core = source_bonded_atoms.intersection(source_core) d_linked_core = dest_bonded_atoms.intersection(dest_core) # linker/ring atoms first, treat small linker atom as functional group # mutation if len(s_linked_core) > 1 and len(d_linked_core) > 1: if (len(dest_bonded_atoms) != len(source_bonded_atoms)) or (s.element != d.element): for a in sorted(d_linked_core, key=atom_index): if not any( {d.index, a.index}.issubset(r) for r in ring_list) and count_heavy_atoms( dest_ct, get_fragment(d.index, a.index, dest_ct) ) + count_heavy_atoms( source_ct, get_fragment(s.index, d2s_map[a].index, source_ct)) < 12: d_anchor_dummy[d].append(a) linker_as_dummy.add((d, a)) else: linker.add((s, d)) for d_bridge in sorted(dest_bonded_atoms.intersection(dest_dummy), key=atom_index): d_anchor_dummy[d].append(d_bridge) if d not in d_anchor: d_anchor.append(d) if source_bonded_atoms.intersection(source_dummy): if d not in d_anchor: d_anchor.append(d) for l_s, l_d in sorted(linker, key=lambda atom_pair: atom_pair[0].index): for d_bridge in sorted(set(l_d.bonded_atoms).intersection(dest_core), key=atom_index): if (l_d, d_bridge) not in linker_as_dummy: d_anchor_mapped[l_d].add(d_bridge) # add linker/ring atoms back so that dummy atoms get picked first if l_d not in d_anchor: d_anchor.append(l_d) ret_val = [] s_linker = set() if linker: s_linker, _ = zip(*linker) for b in d_anchor: s_bridge = 0 s_anchor = d2s_map[b] for s_b in sorted(set(s_anchor.bonded_atoms).intersection(source_dummy), key=atom_index): # pair up core-dummy, if there are more core-dummy in any of the molecules # use None for the dummy in the other molecule if s_bridge < len(d_anchor_dummy[b]): ret_val.append(((s_anchor.index, s_b.index), (b.index, d_anchor_dummy[b][s_bridge].index))) s_bridge += 1 else: ret_val.append(((s_anchor.index, s_b.index), (b.index, None))) dest_bridge_atoms = d_anchor_dummy[b] for d_bridge_atom in sorted(dest_bridge_atoms[s_bridge:], key=atom_index): # unpaired dummy dest bridge atoms don't have any corresponding # source bridge dummy but there are matched linker dest bridge atoms # which have matched source bridge atoms. Need to look up the # source bridge atoms in atom matches. s_bridge_index = None if (b, d_bridge_atom) in linker_as_dummy: s_bridge_index = d2s_map[d_bridge_atom].index ret_val.append(((s_anchor.index, s_bridge_index), (b.index, d_bridge_atom.index))) for d_mapped in sorted(d_anchor_mapped[b], key=atom_index): # need to make attachment bond a (core atom, mutating atom) # pair s_mapped = d2s_map[d_mapped] if s_mapped not in s_linker: # s_mapped is not changing, i.e. core atom # Checking d_mapped and b yields the same result att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) else: # This is the case where both atoms in the attachment bond are ring/linker atoms. # Use the number of heavy atoms in the connected fragments connected to determine the direction, # i.e. make the second atom always in the smaller fragment (in terms of the number of heavy atoms). # If the attachment bond connects the same sized fragments in both of the molecules, # print a warning. source_frags = _get_heavy_atom_fragments( source_ct, s_anchor.index, s_mapped.index) if len(source_frags) == 1: att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) if len( _get_heavy_atom_fragments(dest_ct, b.index, d_mapped.index)) > 1: logger.warning( "linker atom mapped to ring atoms, core-hopping workflow should be used" ) elif len(source_frags[0]) == len(source_frags[1]): dest_frags = _get_heavy_atom_fragments( dest_ct, b.index, d_mapped.index) if len(dest_frags) == 1: logger.warning( "linker atom mapped to ring atoms, core-hopping workflow should be used" ) att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) elif len(dest_frags[0]) == len(dest_frags[1]): logger.warning( f"linker ({s_anchor.index}, {s_mapped.index}) connects two fragments of the same number of heavy atoms" ) att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) else: if b.index in dest_frags[0]: att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) else: att_pair = ((s_anchor.index, d2s_map[d_mapped].index), (b.index, d_mapped.index)) else: if s_anchor.index in source_frags[0]: att_pair = ((s_mapped.index, s_anchor.index), (d_mapped.index, b.index)) else: att_pair = ((s_anchor.index, s_mapped.index), (b.index, d_mapped.index)) ret_val.append(att_pair) return ret_val
[docs]def get_big_fused_rings(ct): """ return heavy atoms of fused rings from SSSR set """ visited = set([]) all_rings = [] ring_list = [r.getAtomIndices() for r in ct.ring] for (i, r) in enumerate(ring_list): if i not in visited: visited.add(i) fusable = False for r0 in all_rings: if r0.intersection(r): r0.update(r) fusable = True if not fusable: all_rings.append(set(r)) remove_list = [] for r in all_rings: if count_heavy_atoms(ct, set(r)) < MIN_FUSED_RING: remove_list.append(r) for r in remove_list: all_rings.remove(r) return all_rings
[docs]def combine_rings(rings): """ combine fused rings from SSSR set :return: list of lists of atoms """ visited = set([]) all_rings = [] for (i, r) in enumerate(rings): if i not in visited: visited.add(i) fusable = False for r0 in all_rings: if r0.intersection(r): r0.update(r) fusable = True if not fusable: all_rings.append(set(r)) return all_rings
[docs]def is_ring_atom(ct, atom): for r in ct.ring: if atom in r.getAtomIndices(): return True return False
[docs]def whole_ring_test(ct, atom_list): """ test if atom list contains entire rings """ alist = set(atom_list) ret_val = False for r in ct.ring: ring_atoms = r.getAtomIndices() if alist.issuperset(set(ring_atoms)): ret_val = True return ret_val
[docs]def find_closest_rotbond(graph, anchor_index, rotable_bonds): """ given anchor atom and connection graph of a molecule, find the rotable bonds that is cloest (in terms of number of bonds to the anchor atom) :param graph: molecular graph :type graph: networkx graph :param anchor_index: anchor atom index :param rotable_bonds: list of rotable bonds :type rotable_bonds: list of touples """ distance = 100 for bond in rotable_bonds: #find closest rotable bond path0 = nx.shortest_path(graph, anchor_index, bond[0]) path1 = nx.shortest_path(graph, anchor_index, bond[1]) if len(path1) < len(path0): if distance > len(path1): distance = len(path1) rot_atom1 = bond[1] rot_atom0 = bond[0] #save the bond in original atom order save_rot_bond = bond else: if distance > len(path0): distance = len(path0) rot_atom1 = bond[0] rot_atom0 = bond[1] #save the bond in original atom order save_rot_bond = bond return (distance, (rot_atom0, rot_atom1), save_rot_bond)
[docs]def remove_extra_rotable_atoms(ct, graph, anchor_index, hot_r, rotable_bonds): """ remove the extra fragments connected to the current hot region. :param ct: structure :param graph: molecular graph :type graph: networkx graph :param anchor_index: anchor atom index :param hot_r: hot region found by finding closes rotable bonds from anchor atom :type hot_r: list of integers :param rotable_bonds: list of rotable bonds :type rotable_bonds: list of touples """ extra_rot = [] for rot_bond in rotable_bonds: if (rot_bond[0] in hot_r) and (rot_bond[1] in hot_r): path0 = nx.shortest_path(graph, anchor_index, rot_bond[0]) path1 = nx.shortest_path(graph, anchor_index, rot_bond[1]) atom0 = rot_bond[0] atom1 = rot_bond[1] if len(path1) < len(path0): atom1 = rot_bond[0] atom0 = rot_bond[1] # delete atoms for additional rotable bonds extra_frag = get_fragment(atom0, atom1, ct) if count_heavy_atoms(ct, extra_frag) > LARGE_FRAG_ON_RING: extra_rot.append((atom0, atom1)) remove = list(set(hot_r) & set(extra_frag)) for a in remove: hot_r.remove(a) return extra_rot
[docs]def find_frag_from_closest_rotbond(ct, anchor_index, rotable_bonds): """ find rotable bond that is closest to the anchor atom in terms of number of bonds return the fragment, original rotable bond, bond used to find the fragment, and list of rotable bonds from which large fragment is cut off (extra_rot). The last two items are used to find the matching fragment in the other molecule :return: (hot_r, save_rot_bond, (rot_atom0, rot_atom1), extra_rot) where: - hot_r: hot region found - save_rot_bond: original rotable bond used to find the fragment - (rot_atom0, rot_atom1): rotable bond in the same atom order passed to get_fragment - extra_rot: list of extra_rotable bond directly connected to the fragment; the fragments attached by these bonds are removed already """ fuse_rings = get_big_fused_rings(ct) graph = analyze.create_nx_graph(ct) distance = 100 rot_atom1 = 0 rot_atom0 = 0 save_rot_bond = (0, 0) if anchor_index: (distance, (rot_atom0, rot_atom1), save_rot_bond) = find_closest_rotbond(graph, anchor_index, rotable_bonds) if distance == 100: return ([], (0, 0), (0, 0), []) hot_r = get_fragment(rot_atom0, rot_atom1, ct) extra_rot = remove_extra_rotable_atoms(ct, graph, anchor_index, hot_r, rotable_bonds) for r in fuse_rings: if set(hot_r).intersection(r): hot_r = set() break return (hot_r, save_rot_bond, (rot_atom0, rot_atom1), extra_rot)
[docs]def get_fragments_from_att_points(source_ct, dest_ct, source_att, dest_att, source_rotable_bonds, dest_rotable_bonds): """ given attachment pairs, find the rotable fragments containing the attachment bond return fragments and the rotable bond associated The bigger one of the fragment pair is used to determine which rotabond to use. :param source_ct: source structure :param dest_ct: dest structure :param source_att: source attachment point :type source_att: touple of integers :param dest_att: dest attachment point :type dest_att: touple of integers :param source_rotable_bonds: source rotable bond list :param dest_rotable_bonds: dest rotable bond list :return: (source_hot_frag, dest_hot_frag, (s_rot0, s_rot1), (d_rot0, d_rot1)). The order of atoms in the rotable bonds returned is in accord to the way the fragments are generated. """ dest_hot_frag = [] source_hot_frag = [] (s_rot0, s_rot1) = (0, 0) (d_rot0, d_rot1) = (0, 0) s_frag = get_fragment(source_att[0], source_att[1], source_ct) d_frag = get_fragment(dest_att[0], dest_att[1], dest_ct) if len(s_frag) > len(d_frag) and source_rotable_bonds: (source_hot_frag, (s_rot0, s_rot1), bond, extra_rot) = find_frag_from_closest_rotbond(source_ct, source_att[0], source_rotable_bonds) if len(source_hot_frag): dest_hot_frag = get_fragment( source_ct.atom[bond[0]].property[constants.FEP_MAPPING], source_ct.atom[bond[1]].property[constants.FEP_MAPPING], dest_ct) for (atom0, atom1) in extra_rot: extra_frag = get_fragment( source_ct.atom[atom0].property[constants.FEP_MAPPING], source_ct.atom[atom1].property[constants.FEP_MAPPING], dest_ct) dest_hot_frag = list(set(dest_hot_frag) - set(extra_frag)) elif dest_rotable_bonds: (dest_hot_frag, (d_rot0, d_rot1), bond, extra_rot) = find_frag_from_closest_rotbond(dest_ct, dest_att[0], dest_rotable_bonds) if len(dest_hot_frag): source_hot_frag = get_fragment( dest_ct.atom[bond[0]].property[constants.FEP_MAPPING], dest_ct.atom[bond[1]].property[constants.FEP_MAPPING], source_ct) for (atom0, atom1) in extra_rot: extra_frag = get_fragment( dest_ct.atom[atom0].property[constants.FEP_MAPPING], dest_ct.atom[atom1].property[constants.FEP_MAPPING], source_ct) source_hot_frag = list(set(source_hot_frag) - set(extra_frag)) if (s_rot0, s_rot1) == (0, 0) and (d_rot0, d_rot1) != (0, 0): if (d_rot0 == dest_att[0] and d_rot1 == dest_att[1]) or (d_rot0 == dest_att[1] and d_rot1 == dest_att[0]): (s_rot0, s_rot1) = source_att else: s_rot0 = dest_ct.atom[d_rot0].property[constants.FEP_MAPPING] s_rot1 = dest_ct.atom[d_rot1].property[constants.FEP_MAPPING] elif (d_rot0, d_rot1) == (0, 0) and (s_rot0, s_rot1) != (0, 0): if (s_rot0 == source_att[0] and s_rot1 == source_att[1]) or (s_rot0 == source_att[1] and s_rot1 == source_att[0]): (d_rot0, d_rot1) = dest_att else: d_rot0 = source_ct.atom[s_rot0].property[constants.FEP_MAPPING] d_rot1 = source_ct.atom[s_rot1].property[constants.FEP_MAPPING] return (source_hot_frag, dest_hot_frag, (s_rot0, s_rot1), (d_rot0, d_rot1))
[docs]def is_rotabond(bond, rota_bond_list): for b in rota_bond_list: if set(b) == set(bond): return True return False
[docs]def apply_exclusion(source_hot_frag, source_excl, dest_hot_frag, dest_excl): s_excl = set(source_hot_frag) & set(source_excl) for a in s_excl: source_hot_frag.remove(a) d_excl = set(dest_hot_frag) & set(dest_excl) for a in d_excl: dest_hot_frag.remove(a)
[docs]def extend_hotregion(total_heavy, source_ct, dest_ct, att_bonds, \ hotregion_source, hotregion_dest, \ source_rot_bonds, dest_rot_bonds, \ source_frag_rot_bonds_visited, dest_frag_rot_bonds_visited, max_rest_atoms, source_excl, dest_excl): """ extend the hot region by moving to the next rotable bond :param total_heavy: total heavy atoms in the hot region so far :param source_ct: source structure :param dest_ct: dest structure :param att_bonds: all attachment bonds for mutations :param source_rot_bonds: rotable bond list of reactant :param dest_rot_bonds: rotable bond list of product :param source_frag_rot_bonds_visite: dictionary of rotable bonds already visited for attchment bonds in reactant :param dest_frag_rot_bonds_visite: dictionary of rotable bonds already visited for attchment bonds in product """ while total_heavy < max_rest_atoms: total_rotbond_left = 0 new_frag = 0 # old_total = total_heavy for (source_att, dest_att) in att_bonds: #try to increase the rest region by moving to the next rotable bond for each fragment source_rot_bond_for_frag = [ bond for bond in source_rot_bonds if bond not in source_frag_rot_bonds_visited[source_att] ] dest_rot_bond_for_frag = [ bond for bond in dest_rot_bonds if bond not in dest_frag_rot_bonds_visited[dest_att] ] total_rotbond_left += len(source_rot_bond_for_frag) + len( dest_rot_bond_for_frag) if not source_rot_bond_for_frag and not dest_rot_bond_for_frag: continue (source_hot_frag, dest_hot_frag, source_rot_bond, dest_rot_bond) = \ get_fragments_from_att_points(source_ct, dest_ct, source_att, dest_att, source_rot_bond_for_frag, dest_rot_bond_for_frag) apply_exclusion(source_hot_frag, source_excl, dest_hot_frag, dest_excl) new_frag += len(source_hot_frag) + len(dest_hot_frag) if source_rot_bond != (0, 0): source_frag_rot_bonds_visited[source_att].append( source_rot_bond) if dest_rot_bond != (0, 0): dest_frag_rot_bonds_visited[dest_att].append(dest_rot_bond) tmp_source_hot = hotregion_source.copy() tmp_source_hot.update(source_hot_frag) tmp_dest_hot = hotregion_dest.copy() tmp_dest_hot.update(dest_hot_frag) if (count_heavy_atoms(source_ct, tmp_source_hot) + count_heavy_atoms(dest_ct, tmp_dest_hot)) < max_rest_atoms: hotregion_source.update(source_hot_frag) hotregion_dest.update(dest_hot_frag) total_heavy = count_heavy_atoms( source_ct, hotregion_source) + count_heavy_atoms( dest_ct, hotregion_dest) elif whole_ring_test(source_ct, source_hot_frag) and \ whole_ring_test(dest_ct, dest_hot_frag) and \ (count_heavy_atoms(source_ct, tmp_source_hot) + \ count_heavy_atoms(dest_ct, tmp_dest_hot)) < max_rest_atoms + 6: hotregion_source.update(source_hot_frag) hotregion_dest.update(dest_hot_frag) total_heavy = count_heavy_atoms( source_ct, hotregion_source) + count_heavy_atoms( dest_ct, hotregion_dest) else: continue if total_rotbond_left == 0 or new_frag == 0: break
[docs]def get_rotable_bonds(source_ct, dest_ct, min_h_atom): """ Find rotable bonds paired for source and dest cts return paired rotable bonds in core, and all possible rotable bonds for each ct """ source_rot_bonds = [ x for x in getImportantRotatableBonds(source_ct, atom_cutoff=min_h_atom) ] dest_rot_bonds = [ x for x in getImportantRotatableBonds(dest_ct, atom_cutoff=min_h_atom) ] all_s_rot_bonds = [ x for x in getImportantRotatableBonds(source_ct, atom_cutoff=min_h_atom) ] all_d_rot_bonds = [ x for x in getImportantRotatableBonds(dest_ct, atom_cutoff=min_h_atom) ] #remove the rotable bonds consisting of dummpy atoms source_rot_remove = [] for bond in source_rot_bonds: if source_ct.atom[bond[0]].property[ constants.FEP_MAPPING] == 0 or source_ct.atom[bond[1]].property[ constants.FEP_MAPPING] == 0: source_rot_remove.append(bond) for bond in source_rot_remove: source_rot_bonds.remove(bond) dest_rot_remove = [] for bond in dest_rot_bonds: if dest_ct.atom[bond[0]].property[ constants.FEP_MAPPING] == 0 or dest_ct.atom[bond[1]].property[ constants.FEP_MAPPING] == 0: dest_rot_remove.append(bond) for bond in dest_rot_remove: dest_rot_bonds.remove(bond) if len(source_rot_bonds) < len(dest_rot_bonds): source_rot_bonds = [(dest_ct.atom[x[0]].property[constants.FEP_MAPPING], dest_ct.atom[x[1]].property[constants.FEP_MAPPING]) for x in dest_rot_bonds] elif len(source_rot_bonds) > len(dest_rot_bonds): dest_rot_bonds = [(source_ct.atom[x[0]].property[constants.FEP_MAPPING], source_ct.atom[x[1]].property[constants.FEP_MAPPING]) for x in source_rot_bonds] return (source_rot_bonds, dest_rot_bonds, all_s_rot_bonds, all_d_rot_bonds)
[docs]def set_hotregion(cts, \ max_rest_atoms=MAX_NUM_REST_ATOM, \ min_h_atom=N_HATOM, \ exclusion_lists=(set(), set()), \ get_fragment=get_fragment, \ get_attachment_bonds=get_attachment_bonds): """ :param cts: input structures, s_fep_fragname should be defined for sourec and dest cts :type cts: Lists of structure :param max_rest_atoms: maximum allowed number of heavy atoms in hot region, soft limit :param min_h_atom: minimum heavy atom moved by a rotable bond :param exclusion_lists: touple of sets of core atoms (hydrogen included) :param get_fragment: callable to compute fragment given (atom_index0, atom_index1, ct) :param get_attachment_bonds: callable to compute attathment points given two cts with i_fep_mapping defined (ct0, ct1) :return: number of heavy atoms assigned Set the hotregions in the cts according to mutated fragments and maximum allowed number of atoms. Core atoms can be exclude via exclusion_lists. Steps to set REST region: 1. Include all the changed atoms (?99, ?97, ?98). If there are too many atoms, stop. Linker atoms are ignored (linker connects two fragments with more than 6 heavy atoms in each of the fragments) 2. If a rotable bond is also an attachment point, include all atoms in the fragment. 3. For each changed fragment pair (attachment point), try to include all the atoms up to the closest rotatble bond. How far away the rotable bond is is measure by the minimum number of bonds separating the fragment and the rotable bonds. Fused rings are excluded. If the fragment is a ring, the maximum allowed atoms may be six more than the allowed number. 4. Repeat 3, if maximum number of atoms are not reached and it is still possible to add more atoms. """ ref_ct, mut_ct = get_fep_cts(cts) source_ct = ref_ct[0] dest_ct = mut_ct[0] (source_excl, dest_excl) = exclusion_lists for s_atom in source_ct.atom: s_atom.property[constants.FEP_MAPPING] = 0 for d_atom in dest_ct.atom: s_mapped = d_atom.property[constants.FEP_MAPPING] if s_mapped: source_ct.atom[s_mapped].property[ constants.FEP_MAPPING] = d_atom.index att_bonds = get_attachment_bonds(source_ct, dest_ct) hotregion_source = set() hotregion_dest = set() total_heavy = 0 (source_rot_bonds, dest_rot_bonds, all_s_rot_bonds, all_d_rot_bonds) = \ get_rotable_bonds(source_ct, dest_ct, min_h_atom) source_frag_rot_bonds_visited = {} dest_frag_rot_bonds_visited = {} for (source_att, dest_att) in att_bonds: source_frag_rot_bonds_visited[source_att] = [] dest_frag_rot_bonds_visited[dest_att] = [] old_total_robond_left = 0 #try to include all the mutated fragment pairs once for (source_att, dest_att) in att_bonds: # if the attachment bonds is also a rotable bond, add the fragment tmp_source_fragment = set() tmp_dest_fragment = set() if is_rotabond(source_att, all_s_rot_bonds): tmp_source_fragment = get_fragment(source_att[0], source_att[1], source_ct) tmp_dest_fragment = get_fragment(dest_att[0], dest_att[1], dest_ct) elif is_rotabond(dest_att, all_d_rot_bonds): tmp_source_fragment = get_fragment(source_att[0], source_att[1], source_ct) tmp_dest_fragment = get_fragment(dest_att[0], dest_att[1], dest_ct) elif is_ring_atom(source_ct, source_att[0]) or is_ring_atom(source_ct, source_att[1]) or\ is_ring_atom(dest_ct, dest_att[0]) or is_ring_atom(dest_ct, dest_att[0]): #try to include the whole ring when attachment is part of the ring or #directly attached to a ring (source_hot_frag, dest_hot_frag, source_rot_bond, dest_rot_bond) = \ get_fragments_from_att_points(source_ct, dest_ct, source_att, dest_att, source_rot_bonds, dest_rot_bonds) apply_exclusion(source_hot_frag, source_excl, dest_hot_frag, dest_excl) (tmp_source_fragment, tmp_dest_fragment) = (source_hot_frag, dest_hot_frag) if source_rot_bond != (0, 0): source_frag_rot_bonds_visited[source_att].append( source_rot_bond) if dest_rot_bond != (0, 0): dest_frag_rot_bonds_visited[dest_att].append(dest_rot_bond) if total_heavy + ( count_heavy_atoms(source_ct, tmp_source_fragment) + count_heavy_atoms(dest_ct, tmp_dest_fragment)) < max_rest_atoms: hotregion_source.update(tmp_source_fragment) hotregion_dest.update(tmp_dest_fragment) total_heavy = count_heavy_atoms( source_ct, hotregion_source) + count_heavy_atoms( dest_ct, hotregion_dest) elif whole_ring_test(source_ct, tmp_source_fragment) and \ whole_ring_test(dest_ct, tmp_dest_fragment) and \ total_heavy + (count_heavy_atoms(source_ct, tmp_source_fragment) + \ count_heavy_atoms(dest_ct, tmp_dest_fragment)) < max_rest_atoms + 6: hotregion_source.update(tmp_source_fragment) hotregion_dest.update(tmp_dest_fragment) total_heavy = count_heavy_atoms( source_ct, hotregion_source) + count_heavy_atoms( dest_ct, hotregion_dest) else: logger.debug("trying to assign more than %d atoms for purterbation %s->%s " % \ (max_rest_atoms, source_ct.property['s_m_title'], dest_ct.property['s_m_title'])) extend_hotregion(total_heavy, source_ct, dest_ct, att_bonds, \ hotregion_source, hotregion_dest, \ source_rot_bonds, dest_rot_bonds, \ source_frag_rot_bonds_visited, dest_frag_rot_bonds_visited, \ max_rest_atoms, source_excl, dest_excl) for a in source_ct.atom: a.property[constants.REST_HOTREGION] = int(int(a) in hotregion_source) for a in dest_ct.atom: a.property[constants.REST_HOTREGION] = int(int(a) in hotregion_dest) return total_heavy
if ("__main__" == __name__): import sys if (len(sys.argv) == 1): print( "Usage: $SCHRODINGER/run %s <input-mae-file> [<output-mae-file>]" % sys.argv[0]) sys.exit(0) mae_fname = sys.argv[1] cts = [] for ct in structure.StructureReader(mae_fname): if len(ct.atom) > 500: continue ct.deletePropertyFromAllAtoms(constants.REST_HOTREGION) ct.deletePropertyFromAllAtoms('i_user_hotregion') cts.append(ct) n_set = set_hotregion(cts, max_rest_atoms=MAX_NUM_REST_ATOM, min_h_atom=N_HATOM) print("heavy atoms assigned", n_set) for i, ct in enumerate(cts): for atom in ct.atom: if (atom.property.get(constants.REST_HOTREGION) or atom.property.get("i_user_hotregion")): print(i, atom.property.get(constants.REST_HOTREGION), atom.property.get("i_user_hotregion"), \ atom.property.get(constants.FEP_MAPPING), atom.element) if (len(sys.argv) > 2): out_fname = sys.argv[2] with structure.StructureWriter(out_fname) as writer: writer.extend(cts)