def isomorphism(gra1, gra2, backbone_only=False, stereo=True, dummy=True): """ Obtain an isomorphism between two graphs This should eventually replace the other isomorphism functions. :param backbone_only: Compare backbone atoms only? :type backbone_only: bool :param stereo: Consider stereo? :type stereo: bool :param dummy: Consider dummy atoms? :type dummy: bool :returns: The isomorphism mapping `gra1` onto `gra2` :rtype: dict """ if backbone_only: gra1 = implicit(gra1) gra2 = implicit(gra2) if not stereo: gra1 = without_stereo_parities(gra1) gra2 = without_stereo_parities(gra2) if not dummy: gra1 = without_dummy_atoms(gra1) gra2 = without_dummy_atoms(gra2) return _isomorphism(gra1, gra2)
def is_stereo_compatible(tra, sgr1, sgr2): """ is this transformation compatible with the reactant/product stereo assignments? """ cgr1 = without_stereo_parities(sgr1) cgr2 = without_stereo_parities(sgr2) atm_key_dct = _full_isomorphism(apply(tra, cgr1), cgr2) # determine the stereo centers which are preserved in the transformation sgr1 = _relabel(sgr1, atm_key_dct) atm_keys = sorted(atom_stereo_keys(sgr1) & atom_stereo_keys(sgr2)) bnd_keys = sorted(bond_stereo_keys(sgr1) & bond_stereo_keys(sgr2)) atm_pars1 = dict_.values_by_key(atom_stereo_parities(sgr1), atm_keys) atm_pars2 = dict_.values_by_key(atom_stereo_parities(sgr2), atm_keys) bnd_pars1 = dict_.values_by_key(bond_stereo_parities(sgr1), bnd_keys) bnd_pars2 = dict_.values_by_key(bond_stereo_parities(sgr2), bnd_keys) atm_ngb_keys_dct1 = atom_neighbor_keys(sgr1) atm_ngb_keys_dct2 = atom_neighbor_keys(sgr2) ret = True for atm_key, par1, par2 in zip(atm_keys, atm_pars1, atm_pars2): atm_ngb_keys1 = stereo_sorted_atom_neighbor_keys( sgr1, atm_key, atm_ngb_keys_dct1[atm_key]) atm_ngb_keys2 = stereo_sorted_atom_neighbor_keys( sgr2, atm_key, atm_ngb_keys_dct2[atm_key]) if _permutation_parity(atm_ngb_keys1, atm_ngb_keys2): ret &= (par1 == par2) else: ret &= (par1 != par2) for bnd_key, par1, par2 in zip(bnd_keys, bnd_pars1, bnd_pars2): atm1_key, atm2_key = bnd_key atm1_ngb_key1 = stereo_sorted_atom_neighbor_keys( sgr1, atm1_key, atm_ngb_keys_dct1[atm1_key] - {atm2_key})[0] atm2_ngb_key1 = stereo_sorted_atom_neighbor_keys( sgr1, atm2_key, atm_ngb_keys_dct1[atm2_key] - {atm1_key})[0] atm1_ngb_key2 = stereo_sorted_atom_neighbor_keys( sgr2, atm1_key, atm_ngb_keys_dct2[atm1_key] - {atm2_key})[0] atm2_ngb_key2 = stereo_sorted_atom_neighbor_keys( sgr2, atm2_key, atm_ngb_keys_dct2[atm2_key] - {atm1_key})[0] if not ((atm1_ngb_key1 != atm1_ngb_key2) ^ (atm2_ngb_key1 != atm2_ngb_key2)): ret &= (par1 == par2) else: ret &= (par1 != par2) return ret
def set_stereo_from_geometry(gra, geo, geo_idx_dct=None): """ set graph stereo from a geometry (coordinate distances need not match connectivity -- what matters is the relative positions at stereo sites) """ gra = without_stereo_parities(gra) last_gra = None atm_keys = sorted(atom_keys(gra)) geo_idx_dct = (geo_idx_dct if geo_idx_dct is not None else {atm_key: idx for idx, atm_key in enumerate(atm_keys)}) # set atom and bond stereo, iterating to self-consistency atm_keys = set() bnd_keys = set() while last_gra != gra: last_gra = gra atm_keys.update(stereogenic_atom_keys(gra)) bnd_keys.update(stereogenic_bond_keys(gra)) gra = _set_atom_stereo_from_geometry(gra, atm_keys, geo, geo_idx_dct) gra = _set_bond_stereo_from_geometry(gra, bnd_keys, geo, geo_idx_dct) return gra
def atom_groups(gra, atm, stereo=False): """ return a list of groups off of one atom """ if not stereo: gra = without_stereo_parities(gra) adj_atms = atoms_neighbor_atom_keys(gra) keys = [] for atmi in adj_atms[atm]: key = [atm, atmi] key.sort() key = frozenset(key) keys.append(key) gras = remove_bonds(gra, keys) return connected_components(gras)
def stereomers(tsg): """ Expand all possible stereo assignments for the reactants in this TS graph. (Ignores stereo assignments already present, if any.) :param tsg: The TS graph, without stereo assignments. :returns: All possible TS graphs with stereo assignments for the reactants. """ rcts_gra = reactants_graph(tsg) frm_bnd_keys = forming_bond_keys(tsg) brk_bnd_keys = breaking_bond_keys(tsg) rcts_gra = without_stereo_parities(rcts_gra) rcts_sgrs = _stereomers(rcts_gra) ste_tsgs = tuple( graph(rcts_sgr, frm_bnd_keys, brk_bnd_keys) for rcts_sgr in rcts_sgrs) return ste_tsgs
def compatible_reverse_stereomers(ste_tsg): """ Given a TS graph with stereo assignments, expand all possible reverse graphs compatble with the forward graph. :param ste_tsg: The TS graph, with stereo assignments. :returns: All possible reverse TS graphs. """ frm_bnd_keys = forming_bond_keys(ste_tsg) brk_bnd_keys = breaking_bond_keys(ste_tsg) _, des_ste_atm_keys = nonconserved_atom_stereo_keys(ste_tsg) _, des_ste_bnd_keys = nonconserved_bond_stereo_keys(ste_tsg) cons_atm_keys = sorted(atom_stereo_keys(ste_tsg) - des_ste_atm_keys) cons_bnd_keys = sorted(bond_stereo_keys(ste_tsg) - des_ste_bnd_keys) # 1. Determine index-based stereo assignments for conserved stereo centers idx_tsg = to_index_based_stereo(ste_tsg) cons_idx_atm_pars = dict_.values_by_key( atom_stereo_parities(idx_tsg), cons_atm_keys) cons_idx_bnd_pars = dict_.values_by_key( bond_stereo_parities(idx_tsg), cons_bnd_keys) # 2. Determine all possible index-based stereo assignments for the reverse # reaction. prds_gra = without_stereo_parities(products_graph(ste_tsg)) prds_sgrs = _stereomers(prds_gra) prds_idx_sgrs = list(map(_to_index_based_stereo, prds_sgrs)) rev_idx_tsgs_pool = [ graph(p, brk_bnd_keys, frm_bnd_keys) for p in prds_idx_sgrs] # 3. Find possibilities which match the assignments for the conserved # stereo centers. rev_idx_tsgs = [] for rev_idx_tsg in rev_idx_tsgs_pool: rev_cons_idx_atm_pars = dict_.values_by_key( atom_stereo_parities(rev_idx_tsg), cons_atm_keys) rev_cons_idx_bnd_pars = dict_.values_by_key( bond_stereo_parities(rev_idx_tsg), cons_bnd_keys) if (rev_cons_idx_atm_pars == cons_idx_atm_pars and rev_cons_idx_bnd_pars == cons_idx_bnd_pars): rev_idx_tsgs.append(rev_idx_tsg) # 4. Convert the matching reverse graphs back from index-based stereo # assignments to absolute stereo assignments. rev_ste_tsgs = list(map(from_index_based_stereo, rev_idx_tsgs)) return rev_ste_tsgs
def _stereo_corrected_geometry(sgr, geo, geo_idx_dct): """ correct the stereo parities of a geometry (works iterately to handle cases of higher-order stereo) """ assert sgr == explicit(sgr) gra = without_stereo_parities(sgr) if has_stereo(sgr): full_atm_ste_par_dct = atom_stereo_parities(sgr) full_bnd_ste_par_dct = bond_stereo_parities(sgr) atm_keys = set() bnd_keys = set() last_gra = None while last_gra != gra: last_gra = gra atm_keys.update(stereogenic_atom_keys(gra)) bnd_keys.update(stereogenic_bond_keys(gra)) atm_ste_par_dct = { atm_key: full_atm_ste_par_dct[atm_key] for atm_key in atm_keys } bnd_ste_par_dct = { bnd_key: full_bnd_ste_par_dct[bnd_key] for bnd_key in bnd_keys } geo, gra = _atom_stereo_corrected_geometry(gra, atm_ste_par_dct, geo, geo_idx_dct) geo, gra = _bond_stereo_corrected_geometry(gra, bnd_ste_par_dct, geo, geo_idx_dct) return geo
def stereomers(gra): """ all stereomers, ignoring this graph's assignments """ bool_vals = (False, True) def _expand_atom_stereo(sgr): atm_ste_keys = stereogenic_atom_keys(sgr) nste_atms = len(atm_ste_keys) sgrs = [ set_atom_stereo_parities(sgr, dict(zip(atm_ste_keys, atm_ste_par_vals))) for atm_ste_par_vals in itertools.product(bool_vals, repeat=nste_atms) ] return sgrs def _expand_bond_stereo(sgr): bnd_ste_keys = stereogenic_bond_keys(sgr) nste_bnds = len(bnd_ste_keys) sgrs = [ set_bond_stereo_parities(sgr, dict(zip(bnd_ste_keys, bnd_ste_par_vals))) for bnd_ste_par_vals in itertools.product(bool_vals, repeat=nste_bnds) ] return sgrs last_sgrs = [] sgrs = [without_stereo_parities(gra)] while sgrs != last_sgrs: last_sgrs = sgrs sgrs = list(itertools.chain(*map(_expand_atom_stereo, sgrs))) sgrs = list(itertools.chain(*map(_expand_bond_stereo, sgrs))) return tuple(sorted(sgrs, key=frozen))
def from_index_based_stereo(sgr): """ Convert a graph from index-based stereo assignments back to absolute stereo assignments, where parities are independent of atom ordering. :param sgr: a graph with index-based stereo assignments :returns: a graph with absolute stereo assignments """ assert sgr == explicit(sgr), ("Not an explicit graph:\n{}".format( string(sgr, one_indexed=False))) gra = without_stereo_parities(sgr) if has_stereo(sgr): atm_keys_pool = atom_stereo_keys(sgr) bnd_keys_pool = bond_stereo_keys(sgr) idx_atm_ste_par_dct = atom_stereo_parities(sgr) idx_bnd_ste_par_dct = bond_stereo_parities(sgr) atm_ngb_keys_dct = atoms_neighbor_atom_keys(sgr) atm_keys = set() bnd_keys = set() last_gra = None # Do the assignments iteratively to handle higher-order stereo while last_gra != gra: last_gra = gra abs_atm_ste_par_dct = {} abs_bnd_ste_par_dct = {} atm_keys.update(stereogenic_atom_keys(gra) & atm_keys_pool) bnd_keys.update(stereogenic_bond_keys(gra) & bnd_keys_pool) # Determine absolute stereo assignments for atoms for atm_key in atm_keys: abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys( gra, atm_key, atm_ngb_keys_dct[atm_key]) idx_srt_keys = sorted(abs_srt_keys) if automol.util.is_even_permutation(idx_srt_keys, abs_srt_keys): abs_atm_ste_par_dct[atm_key] = ( idx_atm_ste_par_dct[atm_key]) else: abs_atm_ste_par_dct[atm_key] = ( not idx_atm_ste_par_dct[atm_key]) # Determine absolute stereo assignments for bonds for bnd_key in bnd_keys: atm1_key, atm2_key = sorted(bnd_key) atm1_abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys( gra, atm1_key, atm_ngb_keys_dct[atm1_key] - bnd_key) atm2_abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys( gra, atm2_key, atm_ngb_keys_dct[atm2_key] - bnd_key) atm1_idx_srt_keys = sorted(atm1_abs_srt_keys) atm2_idx_srt_keys = sorted(atm2_abs_srt_keys) if not ((atm1_idx_srt_keys[0] != atm1_abs_srt_keys[0]) ^ (atm2_idx_srt_keys[0] != atm2_abs_srt_keys[0])): abs_bnd_ste_par_dct[bnd_key] = ( idx_bnd_ste_par_dct[bnd_key]) else: abs_bnd_ste_par_dct[bnd_key] = ( not idx_bnd_ste_par_dct[bnd_key]) gra = set_atom_stereo_parities(gra, abs_atm_ste_par_dct) gra = set_bond_stereo_parities(gra, abs_bnd_ste_par_dct) atm_ste_keys = atom_stereo_keys(gra) bnd_ste_keys = bond_stereo_keys(gra) assert atm_ste_keys == atm_keys_pool, ( "Index-based to absolute stereo conversion failed:\n" "{} != {}".format(str(atm_ste_keys), str(atm_keys_pool))) assert bnd_ste_keys == bnd_keys_pool, ( "Index-based to absolute stereo conversion failed:\n" "{} != {}".format(str(bnd_ste_keys), str(bnd_keys_pool))) return gra