def hydrogen_abstraction(rct_gras, prd_gras): """ find a hydrogen abstraction transformation Hydrogen abstractions are identified first by checking whether the molecular formulas are consistent with a reaction of the form R1H + R2 => R2H + R1. If they do, we identify the abstraction sites by adding hydrogens to unsaturated sites of the R1 product to see if we get the R1H reactant. We then do the same for the R2 reactant and the R2H product. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 2 and len(prd_gras) == 2 and not is_triv: rct_fmls = list(map(automol.convert.graph.formula, rct_gras)) prd_fmls = list(map(automol.convert.graph.formula, prd_gras)) ret = automol.formula.reac.argsort_hydrogen_abstraction( rct_fmls, prd_fmls) if ret: rct_idxs_, prd_idxs_ = ret q1h_gra, q2_gra = list(map(rct_gras.__getitem__, rct_idxs_)) q2h_gra, q1_gra = list(map(prd_gras.__getitem__, prd_idxs_)) rets1 = _partial_hydrogen_abstraction(q1h_gra, q1_gra) rets2 = _partial_hydrogen_abstraction(q2h_gra, q2_gra) for ret1, ret2 in itertools.product(rets1, rets2): q1h_q_atm_key, q1h_h_atm_key, _ = ret1 _, _, q2_q_atm_key = ret2 frm_bnd_key = frozenset({q2_q_atm_key, q1h_h_atm_key}) brk_bnd_key = frozenset({q1h_q_atm_key, q1h_h_atm_key}) tra = trans.from_data( rxn_class=par.REACTION_CLASS.HYDROGEN_ABSTRACTION, frm_bnd_keys=[frm_bnd_key], brk_bnd_keys=[brk_bnd_key]) tras.append(tra) rct_idxs = rct_idxs_ prd_idxs = prd_idxs_ tras = tuple(tras) return tras, rct_idxs, prd_idxs
def ring_forming_scission(rct_gras, prd_gras): """ find a ring forming reaction that eliminates a radical group """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 1 and len(prd_gras) == 2 and not is_triv: rgra, = rct_gras pgra1, pgra2 = prd_gras pgra = automol.graph.union(pgra1, pgra2) rad_atm_keys = unsaturated_atom_keys(rgra) atms, bnds = rgra ngb_atms = automol.graph.atom_neighbor_keys(rgra) for rad_atm in rad_atm_keys: for xatm in atms: if (xatm != rad_atm and atms[xatm][1] != 'H' and xatm not in ngb_atms[rad_atm] and not tras): for natm in ngb_atms[xatm]: if natm != rad_atm: xgra = atms.copy(), bnds.copy() xgra = add_bonds(xgra, [frozenset({rad_atm, xatm})]) xgra = remove_bonds(xgra, [frozenset({xatm, natm})]) atm_key_dct = full_isomorphism(xgra, pgra) if atm_key_dct: tra = trans.from_data( rxn_class=( par.REACTION_CLASS.RING_FORM_SCISSION), frm_bnd_keys=[{rad_atm, xatm}], brk_bnd_keys=[ {xatm, natm}, ]) tras.append(tra) break # sort the reactants so that the largest species is first rct_idxs = (0, ) prd_idxs = _argsort_reactants(prd_gras) tras = tuple(tras) return tras, rct_idxs, prd_idxs
def hydrogen_migration(rct_gras, prd_gras): """ find a hydrogen migration transformation Hydrogen migrations are identified by adding a hydrogen to an unsaturated site of the reactant and adding a hydrogen to an unsaturated site of the product and seeing if they match up. If so, we have a hydrogen migration between these two sites. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 1 and len(prd_gras) == 1 and not is_triv: gra1, = rct_gras gra2, = prd_gras h_atm_key1 = max(atom_keys(gra1)) + 1 h_atm_key2 = max(atom_keys(gra2)) + 1 atm_keys1 = unsaturated_atom_keys(gra1) atm_keys2 = unsaturated_atom_keys(gra2) for atm_key1, atm_key2 in itertools.product(atm_keys1, atm_keys2): gra1_h = add_atom_explicit_hydrogen_keys(gra1, {atm_key1: [h_atm_key1]}) gra2_h = add_atom_explicit_hydrogen_keys(gra2, {atm_key2: [h_atm_key2]}) inv_atm_key_dct = full_isomorphism(gra2_h, gra1_h) if inv_atm_key_dct: tra = trans.from_data( rxn_class=par.REACTION_CLASS.HYDROGEN_MIGRATION, 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] }]) tras.append(tra) rct_idxs = (0, ) prd_idxs = (0, ) tras = tuple(tras) return tras, rct_idxs, prd_idxs
def trivial_reaction(rct_gras, prd_gras): """ is this a trivial reaction, with the same reactants and products? """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None if len(rct_gras) == len(prd_gras): prd_gras = list(prd_gras) num = len(rct_gras) rct_idxs = tuple(range(num)) prd_idxs = [0] * num # cycle through reactants and check for matching products for rct_idx, rct_gra in enumerate(rct_gras): prd_idx = next((idx for idx, prd_gra in enumerate(prd_gras) if full_isomorphism(rct_gra, prd_gra)), None) # if the reactant has a matching product, remove it from the # products list if prd_idx is not None: prd_idxs[rct_idx] = prd_idx prd_gras.pop(prd_idx) # if the reactant has no matching product, this is not a # trivial reaction else: tras = [] rct_idxs = prd_idxs = None break if rct_idxs is not None: tra = trans.from_data(rxn_class=par.REACTION_CLASS.TRIVIAL, frm_bnd_keys=[], brk_bnd_keys=[]) tras = (tra, ) rct_idxs = tuple(rct_idxs) prd_idxs = tuple(prd_idxs) tras = tuple(tras) return tras, rct_idxs, prd_idxs
def substitution(rct_gras, prd_gras): """ find an substitution transformation Substitutions are identified by breaking one bond in the reactants and one bond from the products and checking for isomorphism. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 2 and len(prd_gras) == 2 and not is_triv: rct_gra = union_from_sequence(rct_gras) prd_gra = union_from_sequence(prd_gras) rct_bnd_keys = bond_keys(rct_gra) prd_bnd_keys = bond_keys(prd_gra) for rct_bnd_key, prd_bnd_key in itertools.product( rct_bnd_keys, prd_bnd_keys): rct_gra_ = remove_bonds(rct_gra, [rct_bnd_key]) prd_gra_ = remove_bonds(prd_gra, [prd_bnd_key]) inv_atm_key_dct = full_isomorphism(prd_gra_, rct_gra_) if inv_atm_key_dct: brk_bnd_key = rct_bnd_key frm_bnd_key = frozenset( map(inv_atm_key_dct.__getitem__, prd_bnd_key)) tra = trans.from_data( rxn_class=par.REACTION_CLASS.SUBSTITUTION, frm_bnd_keys=[frm_bnd_key], brk_bnd_keys=[brk_bnd_key]) tras.append(tra) rct_idxs = _argsort_reactants(rct_gras) prd_idxs = _argsort_reactants(prd_gras) tras = tuple(set(tras)) return tras, rct_idxs, prd_idxs
def addition(rct_gras, prd_gras): """ find an addition transformation Additions are identified by joining an unsaturated site on one reactant to an unsaturated site on the other. If the result matches the products, this is an addition reaction. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 2 and len(prd_gras) == 1 and not is_triv: x_gra, y_gra = rct_gras prd_gra, = prd_gras x_atm_keys = unsaturated_atom_keys(x_gra) y_atm_keys = unsaturated_atom_keys(y_gra) for x_atm_key, y_atm_key in itertools.product(x_atm_keys, y_atm_keys): xy_gra = add_bonds(union(x_gra, y_gra), [{x_atm_key, y_atm_key}]) atm_key_dct = full_isomorphism(xy_gra, prd_gra) if atm_key_dct: tra = trans.from_data(rxn_class=par.REACTION_CLASS.ADDITION, frm_bnd_keys=[{x_atm_key, y_atm_key}], brk_bnd_keys=[]) tras.append(tra) # sort the reactants so that the largest species is first rct_idxs = _argsort_reactants(rct_gras) prd_idxs = (0, ) tras = tuple(tras) return tras, rct_idxs, prd_idxs
def elimination(rct_gras, prd_gras): """ find an elimination transformation Eliminations are identified by breaking two bonds from the reactant, forming three fragments. This will form one "central fragment" with two break sites and two "end fragments" with one break site each. If the central fragment plus the two end fragments, joined at their break sites, matches the products, this is an elimination reaction. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) tras = [] rct_idxs = None prd_idxs = None is_triv = is_trivial_reaction(rct_gras, prd_gras) if len(rct_gras) == 1 and len(prd_gras) == 2 and not is_triv: rct_gra, = rct_gras rct_bnd_keys = bond_keys(rct_gra) # Loop over pairs of bonds and break them. Then, if this forms three # fragments, join the two end fragments and compare the result to the # products. for brk_bnd_key1, brk_bnd_key2 in itertools.combinations(rct_bnd_keys, r=2): rct_gra_ = remove_bonds(rct_gra, [brk_bnd_key1, brk_bnd_key2]) # Find the central fragment, which is the one connected to both # break sites. If there's a loop there may not be a central # fragment, in which case this function will return None. cent_frag_atm_keys = _central_fragment_atom_keys( rct_gra_, brk_bnd_key1, brk_bnd_key2) if cent_frag_atm_keys is not None: atm1_key, = brk_bnd_key1 - cent_frag_atm_keys atm2_key, = brk_bnd_key2 - cent_frag_atm_keys frm_bnd_key = frozenset({atm1_key, atm2_key}) rct_gra_ = add_bonds(rct_gra_, [frm_bnd_key]) prd_gra = union_from_sequence(prd_gras) atm_key_dct = full_isomorphism(rct_gra_, prd_gra) if atm_key_dct: tra = trans.from_data( rxn_class=par.REACTION_CLASS.ELIMINATION, frm_bnd_keys=[frm_bnd_key], brk_bnd_keys=[brk_bnd_key1, brk_bnd_key2]) tras.append(tra) rct_idxs = (0, ) cent_prd_atm_keys = frozenset( map(atm_key_dct.__getitem__, cent_frag_atm_keys)) if cent_prd_atm_keys <= atom_keys(prd_gras[0]): prd_idxs = (0, 1) else: assert cent_prd_atm_keys <= atom_keys(prd_gras[1]) prd_idxs = (1, 0) tras = tuple(tras) return tras, rct_idxs, prd_idxs