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 _is_compatible(sgr): atm_ste_par_dct = _assigned(atom_stereo_parities(sgr)) bnd_ste_par_dct = _assigned(bond_stereo_parities(sgr)) _compat_atm_assgns = (set(known_atm_ste_par_dct.items()) <= set( atm_ste_par_dct.items())) _compat_bnd_assgns = (set(known_bnd_ste_par_dct.items()) <= set( bnd_ste_par_dct.items())) return _compat_atm_assgns and _compat_bnd_assgns
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 to_index_based_stereo(sgr): """ Convert a graph to index-based stereo assignments, where parities are defined relative to the ordering of indices rather than the absolute stereo priority. :param sgr: a graph with absolute stereo assignments :returns: a graph with index-based stereo assignments """ assert sgr == explicit(sgr), ("Not an explicit graph:\n{}".format( string(sgr, one_indexed=False))) abs_srt_keys_dct = atoms_stereo_sorted_neighbor_atom_keys(sgr) atm_ste_keys = atom_stereo_keys(sgr) bnd_ste_keys = bond_stereo_keys(sgr) abs_atm_ste_par_dct = atom_stereo_parities(sgr) abs_bnd_ste_par_dct = bond_stereo_parities(sgr) idx_atm_ste_par_dct = {} idx_bnd_ste_par_dct = {} # Determine index-based stereo assignments for atoms for atm_key in atm_ste_keys: abs_srt_keys = abs_srt_keys_dct[atm_key] idx_srt_keys = sorted(abs_srt_keys) if automol.util.is_even_permutation(idx_srt_keys, abs_srt_keys): idx_atm_ste_par_dct[atm_key] = abs_atm_ste_par_dct[atm_key] else: idx_atm_ste_par_dct[atm_key] = not abs_atm_ste_par_dct[atm_key] # Determine index-based stereo assignments for bonds for bnd_key in bnd_ste_keys: atm1_key, atm2_key = sorted(bnd_key) atm1_abs_srt_keys = abs_srt_keys_dct[atm1_key] atm2_abs_srt_keys = abs_srt_keys_dct[atm2_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])): idx_bnd_ste_par_dct[bnd_key] = abs_bnd_ste_par_dct[bnd_key] else: idx_bnd_ste_par_dct[bnd_key] = not abs_bnd_ste_par_dct[bnd_key] sgr = set_atom_stereo_parities(sgr, idx_atm_ste_par_dct) sgr = set_bond_stereo_parities(sgr, idx_bnd_ste_par_dct) return sgr
def from_graph(gra): """ networkx graph object from a molecular graph """ nxg = networkx.Graph() nxg.add_nodes_from(atom_keys(gra)) nxg.add_edges_from(bond_keys(gra)) networkx.set_node_attributes(nxg, atom_symbols(gra), 'symbol') networkx.set_node_attributes(nxg, atom_implicit_hydrogen_valences(gra), 'implicit_hydrogen_valence') networkx.set_node_attributes(nxg, atom_stereo_parities(gra), 'stereo_parity') networkx.set_edge_attributes(nxg, bond_orders(gra), 'order') networkx.set_edge_attributes(nxg, bond_stereo_parities(gra), 'stereo_parity') return nxg
def chirality_constraint_bounds(gra, keys): """ bounds for enforcing chirality restrictions """ ste_keys = set(atom_stereo_keys(gra)) & set(keys) par_dct = atom_stereo_parities(gra) ngb_key_dct = atoms_neighbor_atom_keys(gra) def _chirality_constraint(key): ngb_keys = ngb_key_dct[key] ngb_keys = atom_stereo_sorted_neighbor_atom_keys(gra, key, ngb_keys) idxs = tuple(map(keys.index, ngb_keys)) vol_range = (-999., -7.) if par_dct[key] else (+7., +999.) return idxs, vol_range chi_dct = dict(map(_chirality_constraint, ste_keys)) return chi_dct
def substereomers(gra): """ all stereomers compatible with this graph's assignments """ _assigned = functools.partial(dict_.filter_by_value, func=lambda x: x is not None) known_atm_ste_par_dct = _assigned(atom_stereo_parities(gra)) known_bnd_ste_par_dct = _assigned(bond_stereo_parities(gra)) def _is_compatible(sgr): atm_ste_par_dct = _assigned(atom_stereo_parities(sgr)) bnd_ste_par_dct = _assigned(bond_stereo_parities(sgr)) _compat_atm_assgns = (set(known_atm_ste_par_dct.items()) <= set( atm_ste_par_dct.items())) _compat_bnd_assgns = (set(known_bnd_ste_par_dct.items()) <= set( bnd_ste_par_dct.items())) return _compat_atm_assgns and _compat_bnd_assgns sgrs = tuple(filter(_is_compatible, stereomers(gra))) return sgrs
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 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
def qualitative_convergence_checker_(gra, keys, rqq_bond_max=1.8, rqh_bond_max=1.3, rhh_bond_max=1.1, bond_nobond_diff=0.3): """ a convergence checker for error minimization, checking that the geometry is qualitatively correct (correct connectivity and stereo) """ symb_dct = atom_symbols(gra) pairs = set(map(frozenset, itertools.combinations(keys, 2))) bnd_keys = pairs & bond_keys(gra) nob_keys = pairs - bond_keys(gra) nob_symbs = tuple(tuple(map(symb_dct.__getitem__, nob_key)) for nob_key in nob_keys) bnd_symbs = tuple(tuple(map(symb_dct.__getitem__, bnd_key)) for bnd_key in bnd_keys) nob_idxs = tuple(tuple(map(keys.index, nob_key)) for nob_key in nob_keys) bnd_idxs = tuple(tuple(map(keys.index, bnd_key)) for bnd_key in bnd_keys) bnd_udists = tuple((rqq_bond_max if 'H' not in symb else rhh_bond_max if set(symb) == {'H'} else rqh_bond_max) for symb in bnd_symbs) diff = bond_nobond_diff nob_ldists = tuple((rqq_bond_max+diff if 'H' not in symb else rhh_bond_max+diff if set(symb) == {'H'} else rqh_bond_max+diff) for symb in nob_symbs) bnd_idxs += tuple(map(tuple, map(reversed, bnd_idxs))) bnd_idx_vecs = tuple(map(list, zip(*bnd_idxs))) bnd_udists *= 2 nob_idxs += tuple(map(tuple, map(reversed, nob_idxs))) nob_idx_vecs = tuple(map(list, zip(*nob_idxs))) nob_ldists *= 2 symbs = tuple(map(symb_dct.__getitem__, keys)) geo_idx_dct = dict(map(reversed, enumerate(keys))) atm_ste_keys = atom_stereo_keys(gra) & set(keys) bnd_ste_keys = bond_stereo_keys(gra) & bnd_keys atm_ste_par_dct = atom_stereo_parities(gra) bnd_ste_par_dct = bond_stereo_parities(gra) def _is_converged(xmat, err, grad): assert err and numpy.any(grad) xyzs = xmat[:, :3] dmat = embed.distance_matrix_from_coordinates(xyzs) # check for correct connectivity connectivity_check = ( (numpy.all(dmat[bnd_idx_vecs] < bnd_udists) if bnd_udists else True) and (numpy.all(dmat[nob_idx_vecs] > nob_ldists) if nob_ldists else True)) # check for correct stereo parities geo = _create.geom.from_data(symbs, xyzs, angstrom=True) atom_stereo_check = all( (_atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct) == atm_ste_par_dct[atm_key]) for atm_key in atm_ste_keys) bond_stereo_check = all( (_bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct) == bnd_ste_par_dct[bnd_key]) for bnd_key in bnd_ste_keys) return connectivity_check and atom_stereo_check and bond_stereo_check return _is_converged