def hydrogen_abstraction(xgr1, xgr2): """ find an addition transformation """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tra = None xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) ret = formula.reac.argsort_hydrogen_abstraction( list(map(automol.convert.graph.formula, xgrs1)), list(map(automol.convert.graph.formula, xgrs2))) if ret is not None: idxs1, idxs2 = ret q1h_xgr, q2_xgr = list(map(xgrs1.__getitem__, idxs1)) q1_xgr, q2h_xgr = list(map(xgrs2.__getitem__, idxs2)) q1_tra = _partial_hydrogen_abstraction(q1h_xgr, q1_xgr) q2_rev_tra = _partial_hydrogen_abstraction(q2h_xgr, q2_xgr) if q1_tra and q2_rev_tra: xgr1_ = _union(apply(q1_tra, q1h_xgr), q2_xgr) xgr2_ = _union(q1_xgr, q2h_xgr) q2_tra = _reverse(q2_rev_tra, xgr2_, xgr1_) tra = from_data(frm_bnd_keys=formed_bond_keys(q2_tra), brk_bnd_keys=broken_bond_keys(q1_tra)) return tra
def insertion(xgr1, xgr2): """ find an insertion transformation """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tra = None idxs = None xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) if len(xgrs1) == 2 and len(xgrs2) == 1: xgrA, xgrB = xgrs1 atmsA = automol.graph.atoms(xgrA) atmsB = automol.graph.atoms(xgrB) neighsA = automol.graph.atom_neighbor_keys(xgrA) neighsB = automol.graph.atom_neighbor_keys(xgrB) bndsA = automol.graph.bond_keys(xgrA) bndsB = automol.graph.bond_keys(xgrB) tra = _insertion(atmsA, neighsA, bndsA, atmsB, neighsB, xgr1, xgr2) idxs = [0, 1] if not tra: tra = _insertion(atmsB, neighsB, bndsB, atmsA, neighsA, xgr1, xgr2) if tra: idxs = [1, 0] else: idxs = None elif len(xgrs1) == 1 and len(xgrs2) == 1: xgrA = xgr1 idxs = [0] atmsA = automol.graph.atoms(xgrA) neighsA = automol.graph.atom_neighbor_keys(xgrA) bndsA = automol.graph.bond_keys(xgrA) tra = _insertion(atmsA, neighsA, bndsA, atmsA, neighsA, xgr1, xgr2) return tra, idxs
def addition(xgr1, xgr2): """ find an addition transformation """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tra = None xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) if len(xgrs1) == 2 and len(xgrs2) == 1: x_xgr, y_xgr = xgrs1 xgr2, = xgrs2 x_atm_keys = _unsaturated_atom_keys(x_xgr) y_atm_keys = _unsaturated_atom_keys(y_xgr) xgeo = automol.graph.geometry(xgr2) for x_atm_key, y_atm_key in itertools.product(x_atm_keys, y_atm_keys): xy_xgr = _add_bonds(_union(x_xgr, y_xgr), [{x_atm_key, y_atm_key}]) xgeo = automol.graph.geometry(xy_xgr) atm_key_dct = _full_isomorphism(xy_xgr, xgr2) if atm_key_dct: tra = from_data(frm_bnd_keys=[{x_atm_key, y_atm_key}], brk_bnd_keys=[]) return tra
def proton_migration(xgr1, xgr2): """ find a proton migration transformation """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tras = [] xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) h_atm_key1 = max(_atom_keys(xgr1)) + 1 h_atm_key2 = max(_atom_keys(xgr2)) + 1 if len(xgrs1) == 1 and len(xgrs2) == 1: xgr1, = xgrs1 xgr2, = xgrs2 atm_keys1 = _unsaturated_atom_keys(xgr1) atm_keys2 = _unsaturated_atom_keys(xgr2) for atm_key1, atm_key2 in itertools.product(atm_keys1, atm_keys2): xgr1_h = _add_atom_explicit_hydrogen_keys(xgr1, {atm_key1: [h_atm_key1]}) xgr2_h = _add_atom_explicit_hydrogen_keys(xgr2, {atm_key2: [h_atm_key2]}) inv_atm_key_dct = _full_isomorphism(xgr2_h, xgr1_h) if inv_atm_key_dct: tras.append( from_data( frm_bnd_keys=[{atm_key1, inv_atm_key_dct[h_atm_key2]}], brk_bnd_keys=[{ inv_atm_key_dct[atm_key2], inv_atm_key_dct[h_atm_key2] }])) if len(tras) < 1: tras = None return tras
def substitution(xgr1, xgr2): """identifies substitution reactions """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tra = None idxs = None xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) if len(xgrs1) == 2 and len(xgrs2) == 2: xgrA, xgrB = xgrs1 xgrC, xgrD = xgrs2 atmsA = automol.graph.atoms(xgrA) neighsA = automol.graph.atom_neighbor_keys(xgrA) bndsA = automol.graph.bond_keys(xgrA) atmsB = automol.graph.atoms(xgrB) neighsB = automol.graph.atom_neighbor_keys(xgrB) bndsB = automol.graph.bond_keys(xgrB) atmsC = automol.graph.atoms(xgrC) neighsC = automol.graph.atom_neighbor_keys(xgrC) bndsC = automol.graph.bond_keys(xgrC) atmsD = automol.graph.atoms(xgrD) neighsD = automol.graph.atom_neighbor_keys(xgrD) bndsD = automol.graph.bond_keys(xgrD) tra = _substitution(atmsA, neighsA, bndsA, atmsB, neighsB, xgr1, xgr2) idxs = [[0, 1], [0, 1]] if not tra: tra = _substitution(atmsB, neighsB, bndsB, atmsA, neighsA, xgr1, xgr2) idxs = [[0, 1], [1, 0]] if not tra: tra = _substitution(atmsC, neighsC, bndsC, atmsD, neighsD, xgr2, xgr1) idxs = [[1, 0], [0, 1]] if not tra: tra = _substitution(atmsD, neighsD, bndsD, atmsC, neighsC, xgr2, xgr1) idxs = [[1, 0], [1, 0]] if not tra: idxs = None # return not substitution for radical + unsaturated reactions unsat_atm_keys = automol.graph.unsaturated_atom_keys(xgrB) for key in unsat_atm_keys: if key in tra[0]: tra = None return tra, idxs
def atom_stereo_coordinates(sgr): """ stereo-specific coordinates for this molecular graph """ assert sgr == _explicit(sgr) last_xgr = None xgr = _without_stereo_parities(sgr) # first, get a set of non-stereo-specific coordinates for the graph atm_xyz_dct = _atom_coordinates(xgr) 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() while last_xgr != xgr: last_xgr = xgr atm_keys.update(stereogenic_atom_keys(xgr)) bnd_keys.update(stereogenic_bond_keys(xgr)) 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 } xgr, atm_xyz_dct = _correct_atom_stereo_coordinates( xgr, atm_ste_par_dct, atm_xyz_dct) xgr, atm_xyz_dct = _correct_bond_stereo_coordinates( xgr, bnd_ste_par_dct, atm_xyz_dct) return atm_xyz_dct
def heuristic_geometry(xgr): """ heuristic geometry for this molecular graph """ xgr = _explicit(xgr) atm_keys = sorted(_atom_keys(xgr)) syms = dict_.values_by_key(_atom_symbols(xgr), atm_keys) xyzs = dict_.values_by_key(atom_stereo_coordinates(xgr), atm_keys) geo = automol.create.geom.from_data(syms, xyzs) return geo
def _set_bond_stereo_from_coordinates(xgr, bnd_keys, atm_xyz_dct): assert xgr == _explicit(xgr) bnd_pars = [ _bond_stereo_parity_from_coordinates(xgr, bnd_key, atm_xyz_dct) for bnd_key in bnd_keys ] xgr = _set_bond_stereo_parities(xgr, dict(zip(bnd_keys, bnd_pars))) return xgr
def _set_atom_stereo_from_coordinates(xgr, atm_keys, atm_xyz_dct): assert xgr == _explicit(xgr) atm_pars = [ _atom_stereo_parity_from_coordinates(xgr, atm_key, atm_xyz_dct) for atm_key in atm_keys ] xgr = _set_atom_stereo_parities(xgr, dict(zip(atm_keys, atm_pars))) return xgr
def stereogenic_bond_keys(xgr): """ (unassigned) stereogenic bonds in this graph """ xgr = _without_bond_orders(xgr) xgr = _explicit(xgr) # for simplicity, add the explicit hydrogens back in bnd_keys = dict_.keys_by_value(_resonance_dominant_bond_orders(xgr), lambda x: 2 in x) # make sure both ends are sp^2 (excludes cumulenes) atm_hyb_dct = _resonance_dominant_atom_hybridizations(xgr) sp2_atm_keys = dict_.keys_by_value(atm_hyb_dct, lambda x: x == 2) bnd_keys = frozenset( {bnd_key for bnd_key in bnd_keys if bnd_key <= sp2_atm_keys}) bnd_keys -= bond_stereo_keys(xgr) bnd_keys -= functools.reduce( # remove double bonds in small rings frozenset.union, filter(lambda x: len(x) < 8, _rings_bond_keys(xgr)), frozenset()) atm_ngb_keys_dct = _atom_neighbor_keys(xgr) def _is_stereogenic(bnd_key): atm1_key, atm2_key = bnd_key def _is_symmetric_on_bond(atm_key, atm_ngb_key): atm_ngb_keys = list(atm_ngb_keys_dct[atm_key] - {atm_ngb_key}) if not atm_ngb_keys: # C=:O: ret = True elif len(atm_ngb_keys) == 1: # C=N:-X ret = False else: assert len(atm_ngb_keys) == 2 # C=C(-X)-Y ret = (stereo_priority_vector( xgr, atm_key, atm_ngb_keys[0]) == stereo_priority_vector( xgr, atm_key, atm_ngb_keys[1])) return ret return not (_is_symmetric_on_bond(atm1_key, atm2_key) or _is_symmetric_on_bond(atm2_key, atm1_key)) ste_gen_bnd_keys = frozenset(filter(_is_stereogenic, bnd_keys)) return ste_gen_bnd_keys
def stereogenic_atom_keys(xgr): """ (unassigned) stereogenic atoms in this graph """ xgr = _without_bond_orders(xgr) xgr = _explicit(xgr) # for simplicity, add the explicit hydrogens back in atm_keys = dict_.keys_by_value(_atom_bond_valences(xgr), lambda x: x == 4) atm_keys -= atom_stereo_keys(xgr) atm_ngb_keys_dct = _atom_neighbor_keys(xgr) def _is_stereogenic(atm_key): atm_ngb_keys = list(atm_ngb_keys_dct[atm_key]) pri_vecs = [ stereo_priority_vector(xgr, atm_key, atm_ngb_key) for atm_ngb_key in atm_ngb_keys ] return not any(pv1 == pv2 for pv1, pv2 in itertools.combinations(pri_vecs, r=2)) ste_gen_atm_keys = frozenset(filter(_is_stereogenic, atm_keys)) return ste_gen_atm_keys
def rotational_bond_keys(xgr, with_h_rotors=True): """ determine rotational bonds in this molecular graph """ xgr = _explicit(xgr) atm_bnd_vlc_dct = _atom_bond_valences(xgr, bond_order=False) atm_exp_hyd_vlc_dct = _atom_explicit_hydrogen_valences(xgr) res_dom_bnd_ords_dct = resonance_dominant_bond_orders(xgr) bnd_keys = [] for bnd_key, bnd_ords in res_dom_bnd_ords_dct.items(): if all(bnd_ord <= 1 for bnd_ord in bnd_ords): atm_keys = list(bnd_key) bnd_ord = min(bnd_ords) rot_vlcs = numpy.array( list(map(atm_bnd_vlc_dct.__getitem__, atm_keys))) rot_vlcs -= bnd_ord if not with_h_rotors: atm_exp_hyd_vlcs = numpy.array(list( map(atm_exp_hyd_vlc_dct.__getitem__, atm_keys))) rot_vlcs -= atm_exp_hyd_vlcs if all(rot_vlcs): bnd_keys.append(bnd_key) return frozenset(bnd_keys)
def _connected_graph_atom_coordinates(sgr): """ non-stereo-specific coordinates for a connected molecular graph (currently assumes a with at most one ring -- fix that) """ assert sgr == _explicit(sgr) atm_keys = _atom_keys(sgr) rng_atm_keys_lst = _rings_sorted_atom_keys(sgr) if len(atm_keys) == 1: atm1_key, = atm_keys atm_xyz_dct = {} atm_xyz_dct[atm1_key] = (0., 0., 0.) elif len(atm_keys) == 2: atm1_key, atm2_key = atm_keys atm1_xyz = (0., 0., 0.) dist = _bond_distance(sgr, atm2_key, atm1_key) atm2_xyz = cart.vec.from_internals(dist=dist, xyz1=atm1_xyz) atm_xyz_dct = {} atm_xyz_dct[atm1_key] = tuple(atm1_xyz) atm_xyz_dct[atm2_key] = tuple(atm2_xyz) elif not rng_atm_keys_lst: atm_ngb_keys_dct = _atom_neighbor_keys(sgr) # start assigning coordinates along the longest chain max_chain = longest_chain(sgr) atm2_key, atm3_key = max_chain[:2] # add a dummy atom to start the chain atm1_key = max(atm_keys) + 1 sgr = _add_atoms(sgr, {atm1_key: 'X'}) atm1_xyz = (0., 0., -5.) atm2_xyz = (0., 0., 0.) dist = _bond_distance(sgr, atm3_key, atm2_key) ang = numpy.pi / 2. atm3_xyz = cart.vec.from_internals(dist=dist, xyz1=atm2_xyz, ang=ang, xyz2=atm1_xyz) atm_xyz_dct = {} atm_xyz_dct[atm1_key] = tuple(atm1_xyz) atm_xyz_dct[atm2_key] = tuple(atm2_xyz) atm_xyz_dct[atm3_key] = tuple(atm3_xyz) atm_xyz_dct = _extend_atom_coordinates(sgr, atm1_key, atm2_key, atm3_key, atm_xyz_dct) atm_ngb_keys_dct = _atom_neighbor_keys(sgr) atm4_keys = sorted(atm_ngb_keys_dct[atm3_key] - {atm2_key}) for atm4_key in atm4_keys: atm_xyz_dct = _extend_atom_coordinates(sgr, atm4_key, atm3_key, atm2_key, atm_xyz_dct) # we don't actually need to remove it from sgr, but fwiw sgr = _remove_atoms(sgr, {atm1_key}) atm_xyz_dct.pop(atm1_key) elif len(rng_atm_keys_lst) == 1: # for now, we'll assume only one ring rng_atm_keys, = rng_atm_keys_lst rng_atm_xyzs = _polygon_coordinates(num=len(rng_atm_keys)) atm_xyz_dct = dict(zip(rng_atm_keys, rng_atm_xyzs)) assert len(rng_atm_keys) >= 3 trip_iter = mit.windowed(rng_atm_keys[-2:] + rng_atm_keys, 3) for atm1_key, atm2_key, atm3_key in trip_iter: atm_xyz_dct = _extend_atom_coordinates(sgr, atm1_key, atm2_key, atm3_key, atm_xyz_dct) else: raise NotImplementedError("This algorithm is currently not implemented" "for more than one ring.") return atm_xyz_dct
def elimination(xgr1, xgr2): """identifies elimination reactions """ assert xgr1 == _explicit(xgr1) and xgr2 == _explicit(xgr2) tra = None xgrs1 = _connected_components(xgr1) xgrs2 = _connected_components(xgr2) tras = [] if len(xgrs1) == 1 and len(xgrs2) == 2: atms = automol.graph.atoms(xgr1) neighs = automol.graph.atom_neighbor_keys(xgr1) bnds = automol.graph.bond_keys(xgr1) lonepairs = automol.graph.atom_lone_pair_counts(xgr1) for atmi in atms: i_neighs = neighs[atmi] for atmj in i_neighs: bnd_break_key_ij = _get_bnd_key(atmi, atmj, bnds) new_xgr = automol.graph.remove_bonds(xgr1, [bnd_break_key_ij]) new_xgrs = _connected_components(new_xgr) if len(new_xgrs) == 2: xgrA, xgrB = new_xgrs atmsA = automol.graph.atoms(xgrA) if atmi not in atmsA.keys(): xgrB, xgrA = xgrA, xgrB atmsA = automol.graph.atoms(xgrA) neighsA = automol.graph.atom_neighbor_keys(xgrA) atmsB = automol.graph.atoms(xgrB) neighs_i = neighsA[atmi] for atmk in atmsB: if lonepairs[atmk] > 0: for atml in neighs_i: neighs_l = neighsA[atml] if atml != atmj: bnd_break_key_il = _get_bnd_key( atmi, atml, bnds) bnd_form_key_kl = frozenset({atmk, atml}) newnew_xgr = automol.graph.remove_bonds( new_xgr, [bnd_break_key_il]) newnew_xgr = automol.graph.add_bonds( newnew_xgr, [bnd_form_key_kl]) atm_key_dct = _full_isomorphism( newnew_xgr, xgr2) if atm_key_dct: tra = [[bnd_form_key_kl], [ bnd_break_key_ij, bnd_break_key_il ]] return tra for atmm in neighs_l: if atmm != atmi: bnd_break_key_lm = _get_bnd_key( atml, atmm, bnds) bnd_form_key_km = frozenset( {atmk, atmm}) newnew_xgr = automol.graph.remove_bonds( new_xgr, [bnd_break_key_lm]) newnew_xgr = automol.graph.add_bonds( newnew_xgr, [bnd_form_key_km]) atm_key_dct = _full_isomorphism( newnew_xgr, xgr2) if atm_key_dct: tras.append([[bnd_form_key_km], [ bnd_break_key_ij, bnd_break_key_lm ]]) if len(tras) < 1: tras = None return tras