def substitutions(rct_gras, prd_gras): """ find substitutions consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) 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) rxns = [] if len(rct_gras) == 2 and len(prd_gras) == 2: rct_gra = union_from_sequence(rct_gras) prd_gra = union_from_sequence(prd_gras) for rgra1, rgra2 in itertools.permutations(rct_gras): bnd_keys = bond_keys(rgra1) rad_keys = unsaturated_atom_keys(rgra2) for bnd_key, rad_key in itertools.product(bnd_keys, rad_keys): gra = remove_bonds(rct_gra, [bnd_key]) for brk_key1 in bnd_key: gra = add_bonds(gra, [(brk_key1, rad_key)]) inv_dct = isomorphism(gra, prd_gra) if inv_dct: brk_key2, = bnd_key - {brk_key1} f_frm_bnd_key = (brk_key1, rad_key) f_brk_bnd_key = (brk_key1, brk_key2) b_frm_bnd_key = (inv_dct[brk_key1], inv_dct[brk_key2]) b_brk_bnd_key = (inv_dct[brk_key1], inv_dct[rad_key]) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key]) back_tsg = ts.graph(prd_gra, frm_bnd_keys=[b_frm_bnd_key], brk_bnd_keys=[b_brk_bnd_key]) rcts_atm_keys = [atom_keys(rgra1), atom_keys(rgra2)] prds_atm_keys = list(map(atom_keys, prd_gras)) if inv_dct[rad_key] not in prds_atm_keys[0]: prds_atm_keys = list(reversed(prds_atm_keys)) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.SUBSTITUTION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) return ts_unique(rxns)
def homolytic_scissions(rct_gras, viable_only=False): """ find all possible homolytic scission reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Homolytic scissions are enumerated by identifying all pure single bonds (single bonds with no resonances), and looping over the results of breaking each of them. If this gives rise to two distinct fragments, the reaction is added to the list. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 1: rct_gra, = rct_gras # Identify all pure single bonds involving radical site neighbor avg_bnd_ord_dct = resonance_avg_bond_orders(rct_gra) brk_bnd_keys = dict_.keys_by_value(avg_bnd_ord_dct, lambda x: x == 1) for brk_bnd_key in brk_bnd_keys: prds_gra = remove_bonds(rct_gra, [brk_bnd_key]) prd_gras = connected_components(prds_gra) if len(prd_gras) == 2: prd_gras = sort_reagents(prd_gras) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[], brk_bnd_keys=[brk_bnd_key]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[brk_bnd_key], brk_bnd_keys=[]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.HOMOLYT_SCISSION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) # Dummy line to fix linting checks assert viable_only or not viable_only # filter removes all reactions # if viable_only: # rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def ring_forming_scissions(rct_gras, prd_gras): """ find ring-forming scissions consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) Ring-forming scissions are found by breaking ring-bonds on one product and joining the ends to unsaturated sites on the other product """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) rxns = [] if len(rct_gras) == 1 and len(prd_gras) == 2: rgra, = rct_gras pgra = union_from_sequence(prd_gras) for pgra1, pgra2 in itertools.permutations(prd_gras): bnd_keys = list(itertools.chain(*rings_bond_keys(pgra1))) atm_keys = unsaturated_atom_keys(pgra2) for bnd_key, atm_key in itertools.product(bnd_keys, atm_keys): # Break a ring bond gra = remove_bonds(pgra, [bnd_key]) for end_key in bnd_key: # Add to one end of the broken ring fgra = add_bonds(gra, [(atm_key, end_key)]) inv_dct = isomorphism(fgra, rgra) if inv_dct: other_end_key, = bnd_key - {end_key} f_frm_bnd_key = (inv_dct[end_key], inv_dct[other_end_key]) f_brk_bnd_key = (inv_dct[end_key], inv_dct[atm_key]) b_frm_bnd_key = (end_key, atm_key) b_brk_bnd_key = (end_key, other_end_key) forw_tsg = ts.graph(rgra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key]) back_tsg = ts.graph(pgra, frm_bnd_keys=[b_frm_bnd_key], brk_bnd_keys=[b_brk_bnd_key]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.RING_FORM_SCISSION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=[atom_keys(rgra)], prds_keys=[atom_keys(pgra1), atom_keys(pgra2)], )) return ts_unique(rxns)
def additions(rct_gras, prd_gras): """ find additions consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) 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) rxns = [] if len(rct_gras) == 2 and len(prd_gras) == 1: 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}]) iso_dct = isomorphism(xy_gra, prd_gra) if iso_dct: rcts_gra = union_from_sequence(rct_gras) prds_gra = prd_gra f_frm_bnd_key = (x_atm_key, y_atm_key) b_brk_bnd_key = (iso_dct[x_atm_key], iso_dct[y_atm_key]) forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[], brk_bnd_keys=[b_brk_bnd_key]) # sort the reactants so that the largest species is first rct_idxs = _argsort_reactants(rct_gras) rct_gras = list(map(rct_gras.__getitem__, rct_idxs)) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.ADDITION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) return ts_unique(rxns)
def additions(rct_gras, viable_only=True): """ find all possible addition reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Additions are enumerated by joining an unsaturated site on one reactant to an unsaturated site on the other. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 2: rct_gras = sort_reagents(rct_gras) rct1_gra, rct2_gra = rct_gras rct1_atm_keys = unsaturated_atom_keys(rct1_gra) rct2_atm_keys = unsaturated_atom_keys(rct2_gra) for frm_bnd_key in itertools.product(rct1_atm_keys, rct2_atm_keys): rcts_gra = union(rct1_gra, rct2_gra) prd_gra = add_bonds(rcts_gra, [frm_bnd_key]) prd_gras = [prd_gra] forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=[frm_bnd_key], brk_bnd_keys=[]) back_tsg = ts.graph(prd_gra, frm_bnd_keys=[], brk_bnd_keys=[frm_bnd_key]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.ADDITION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def trivial(rct_gras, prd_gras): """ find 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) rxns = [] if len(rct_gras) == len(prd_gras): prd_gras = list(prd_gras) rct_idxs = [] prd_idxs = [] # One at a time, find matches for each reactant; track the positions to # get the right sort order for rct_idx, rct_gra in enumerate(rct_gras): prd_idx = next((idx for idx, prd_gra in enumerate(prd_gras) if isomorphism(rct_gra, prd_gra)), None) if prd_idx is not None: rct_idxs.append(rct_idx) prd_idxs.append(prd_idx) prd_gras.pop(prd_idx) else: break if rct_idxs and prd_idxs: # reorder the reactants and products rct_gras = list(map(rct_gras.__getitem__, rct_idxs)) prd_gras = list(map(prd_gras.__getitem__, prd_idxs)) rcts_gra = union_from_sequence(rct_gras) prds_gra = union_from_sequence(prd_gras) rxns.append( Reaction( rxn_cls=par.ReactionClass.TRIVIAL, forw_tsg=ts.graph(rcts_gra, [], []), back_tsg=ts.graph(prds_gra, [], []), rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) return tuple(rxns)
def add_stereo_from_geometries(rxn, rct_geos, prd_geos): """ Add stereo assignments to this reaction object from geometries. :param rxn: a reaction object :type rxn: Reaction :param rct_geos: the reactant geometries :param prd_geos: the product geometries :returns: a reaction object with stereo assignments :rtype: Reaction """ rct_gras = list(reactant_graphs(rxn)) prd_gras = list(product_graphs(rxn)) def _index_dict(gra): return dict( map(reversed, enumerate(sorted(automol.graph.atom_keys(gra))))) rct_gras = [ automol.graph.set_stereo_from_geometry( rct_gra, rct_geo, geo_idx_dct=_index_dict(rct_gra)) for rct_gra, rct_geo in zip(rct_gras, rct_geos) ] prd_gras = [ automol.graph.set_stereo_from_geometry( prd_gra, prd_geo, geo_idx_dct=_index_dict(prd_gra)) for prd_gra, prd_geo in zip(prd_gras, prd_geos) ] rcts_gra = automol.graph.union_from_sequence(rct_gras) prds_gra = automol.graph.union_from_sequence(prd_gras) rev_rxn = reverse(rxn) forw_tsg = automol.graph.ts.graph(rcts_gra, forming_bond_keys(rxn), breaking_bond_keys(rxn)) back_tsg = automol.graph.ts.graph(prds_gra, forming_bond_keys(rev_rxn), breaking_bond_keys(rev_rxn)) srxn = Reaction(rxn.class_, forw_tsg, back_tsg, rxn.reactants_keys, rxn.products_keys) return srxn
def expand_stereo(rxn): """ Expand all possible stereo assignments for the reactants and products of this reaction. Only includes possibilities that are mutually consistent with each other. :param rxn: a reaction object :type rxn: Reaction :returns: a sequence reaction objects with stereo assignments :rtype: Reaction """ rxn_cls = rxn.class_ forw_tsg = rxn.forward_ts_graph back_tsg = rxn.backward_ts_graph rcts_keys = rxn.reactants_keys prds_keys = rxn.products_keys key_dct = atom_mapping(rxn) forw_ste_tsgs = ts.stereomers(forw_tsg) srxns = [] for forw_ste_tsg in forw_ste_tsgs: for back_ste_tsg in ts.compatible_reverse_stereomers(forw_ste_tsg): back_ste_tsg = automol.graph.relabel(back_ste_tsg, key_dct) # But for dummy atoms, we could just do the conversion directly, # but this avoids loss of dummy atoms from the products graph. tsg_ = back_tsg tsg_ = automol.graph.set_atom_stereo_parities( tsg_, automol.graph.atom_stereo_parities(back_ste_tsg)) tsg_ = automol.graph.set_bond_stereo_parities( tsg_, automol.graph.bond_stereo_parities(back_ste_tsg)) back_ste_tsg = tsg_ srxn = Reaction(rxn_cls, forw_ste_tsg, back_ste_tsg, rcts_keys, prds_keys) srxns.append(srxn) srxns = tuple(srxns) return srxns
def expand_product_stereo(srxn): """ Expand all possible stereo assignments for the products of this reaction, given a set of stereo assignments for the reactants. Stereo assignments for the products will be ignored. :param srxn: a reaction object with stereo assignments :type srxn: Reaction :returns: a sequence reaction objects with stereo assignments :rtype: Reaction """ rxn_cls = srxn.class_ back_tsg = srxn.backward_ts_graph rcts_keys = srxn.reactants_keys prds_keys = srxn.products_keys key_dct = atom_mapping(srxn) forw_ste_tsg = srxn.forward_ts_graph srxns = [] for back_ste_tsg in ts.compatible_reverse_stereomers(forw_ste_tsg): back_ste_tsg = automol.graph.relabel(back_ste_tsg, key_dct) # But for dummy atoms, we could just do the conversion directly, but # this avoids loss of dummy atoms from the products graph. tsg_ = back_tsg tsg_ = automol.graph.set_atom_stereo_parities( tsg_, automol.graph.atom_stereo_parities(back_ste_tsg)) tsg_ = automol.graph.set_bond_stereo_parities( tsg_, automol.graph.bond_stereo_parities(back_ste_tsg)) back_ste_tsg = tsg_ srxn = Reaction(rxn_cls, forw_ste_tsg, back_ste_tsg, rcts_keys, prds_keys) srxns.append(srxn) srxns = tuple(srxns) return srxns
def eliminations(rct_gras, prd_gras): """ find eliminations consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) Eliminations are identified by forming a bond between an attacking heavy atom and another atom not initially bonded to it, forming a ring. The bond adjacent to the attacked atom is then broken, along with a second bond in the ring, downstream of the attacking heavy atom, away from the attacked atom. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) rxns = [] if len(rct_gras) == 1 and len(prd_gras) == 2: rgra, = rct_gras pgra = union_from_sequence(prd_gras) rngb_keys = atoms_sorted_neighbor_atom_keys(rgra) frm1_keys = atom_keys(rgra, excl_syms=('H', )) frm2_keys = atom_keys(rgra) bnd_keys = bond_keys(rgra) frm_bnd_keys = [ (frm1_key, frm2_key) for frm1_key, frm2_key in itertools.product(frm1_keys, frm2_keys) if frm1_key != frm2_key and not frozenset({frm1_key, frm2_key}) in bnd_keys ] for frm1_key, frm2_key in frm_bnd_keys: # Bond the radical atom to the hydrogen atom rgra_ = add_bonds(rgra, [(frm2_key, frm1_key)]) # Get keys to the ring formed by this extra bond rng_keys = next((ks for ks in rings_atom_keys(rgra_) if frm2_key in ks and frm1_key in ks), None) if rng_keys is not None: for nfrm2_key in rngb_keys[frm2_key]: # Break the bond between the attacked atom and its neighbor rgra_ = remove_bonds(rgra_, [(frm2_key, nfrm2_key)]) # Sort the ring keys so that they start with the radical # atom and end with the hydrogen atom keys = cycle_ring_atom_key_to_front(rng_keys, frm1_key, end_key=frm2_key) # Break one ring bond at a time, starting from the rind, # and see what we get for brk_key1, brk_key2 in mit.windowed(keys[:-1], 2): gra = remove_bonds(rgra_, [(brk_key1, brk_key2)]) inv_dct = full_isomorphism(gra, pgra) if inv_dct: f_frm_bnd_key = (frm2_key, frm1_key) f_brk_bnd_key1 = (frm2_key, nfrm2_key) f_brk_bnd_key2 = (brk_key1, brk_key2) b_frm_bnd_key1 = (inv_dct[frm2_key], inv_dct[nfrm2_key]) b_frm_bnd_key2 = (inv_dct[brk_key1], inv_dct[brk_key2]) b_brk_bnd_key = (inv_dct[frm2_key], inv_dct[frm1_key]) forw_tsg = ts.graph( rgra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key1, f_brk_bnd_key2]) back_tsg = ts.graph( pgra, frm_bnd_keys=[b_frm_bnd_key1, b_frm_bnd_key2], brk_bnd_keys=[b_brk_bnd_key]) rcts_atm_keys = list(map(atom_keys, rct_gras)) prds_atm_keys = list(map(atom_keys, prd_gras)) if inv_dct[frm2_key] not in prds_atm_keys[1]: prds_atm_keys = list(reversed(prds_atm_keys)) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.ELIMINATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) return tuple(rxns)
def insertions(rct_gras, viable_only=True): """ find all possible insertion reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Insertions are enumerated by looping over carbenes and multiple bonds on one reactant, which serve as a source of potential "attacking" atoms for the insertion, and looping over single bonds that could be inserted into on the other reactant. For lack of a better term, we can call these "donating atoms". The insertion then looks as follows: A1=A2 A1 . . or . . . . . D1-D2 D1-D2 where two bonds are formed between the A and D atoms and the bond between the two D atoms is broken. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 2: for rct1_gra, rct2_gra in itertools.permutations(rct_gras): rcts_gra = union(rct1_gra, rct2_gra) # Carbenes on R1 are potential attacking atoms atm_keys = radical_atom_keys(rct1_gra, min_valence=2.) atm_keys = tuple(atm_keys) # So are atoms on either side of a multiple bond bnd_keys = dict_.keys_by_value(resonance_avg_bond_orders(rct1_gra), lambda x: x > 1.) bnd_keys = bond_equivalence_class_reps(rct1_gra, bnd_keys) # Use this to form a list of attacking atom pairs for R1 att_pairs = list(map(tuple, map(sorted, bnd_keys))) att_pairs += list(zip(atm_keys, atm_keys)) # As donor pairs, consider single bonds on R2 don_bnd_keys = dict_.keys_by_value( resonance_avg_bond_orders(rct2_gra), lambda x: x == 1.) don_bnd_keys = bond_equivalence_class_reps(rct2_gra, don_bnd_keys) don_pairs = list(map(tuple, map(sorted, don_bnd_keys))) for att_pair, don_pair in itertools.product(att_pairs, don_pairs): if not (are_equivalent_atoms(rct1_gra, *att_pair) or are_equivalent_atoms(rct2_gra, *don_pair)): don_pairs_ = list(itertools.permutations(don_pair)) else: don_pairs_ = [don_pair] for don_pair_ in don_pairs_: att1_key, att2_key = att_pair don1_key, don2_key = don_pair_ prds_gra = rcts_gra prds_gra = add_bonds(prds_gra, [(att1_key, don1_key), (att2_key, don2_key)]) prds_gra = remove_bonds(prds_gra, [(don1_key, don2_key)]) prd_gras = connected_components(prds_gra) if len(prd_gras) == 1: forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=[(att1_key, don1_key), (att2_key, don2_key) ], brk_bnd_keys=[(don1_key, don2_key) ]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[(don1_key, don2_key) ], brk_bnd_keys=[(att1_key, don1_key), (att2_key, don2_key) ]) # Create the reaction object rcts_keys = list(map(atom_keys, [rct1_gra, rct2_gra])) prds_keys = list(map(atom_keys, prd_gras)) rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.INSERTION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_keys, prds_keys=prds_keys, )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def _identify(frm1_keys, frm2_keys, bnd_keys): """ Try and identify elmination from some set of keys """ _rxns = [] frm_bnd_keys = [ (frm1_key, frm2_key) for frm1_key, frm2_key in itertools.product(frm1_keys, frm2_keys) if frm1_key != frm2_key and not frozenset({frm1_key, frm2_key}) in bnd_keys ] for frm1_key, frm2_key in frm_bnd_keys: prds_gra_ = add_bonds(rct_gra, [(frm2_key, frm1_key)]) # Get keys of all bonds in the ring formed by this extra bond rng_bnd_keys = next((ks for ks in rings_bond_keys(prds_gra_) if frozenset({frm1_key, frm2_key}) in ks), None) if rng_bnd_keys is not None: # Elims break two bonds of the ring formed by the forming bond # Loop over all ring bond-pairs, break bonds, see if prods form # Ensure to preclude the forming-bond from this set brk_bnds = tuple( bond for bond in itertools.combinations(rng_bnd_keys, 2) if frozenset({frm1_key, frm2_key}) not in bond) for brk_bnd_1, brk_bnd_2 in brk_bnds: prds_gra_2_ = prds_gra_ prds_gra_2_ = remove_bonds(prds_gra_2_, [brk_bnd_1]) prds_gra_2_ = remove_bonds(prds_gra_2_, [brk_bnd_2]) inv_dct = isomorphism(prds_gra_2_, prds_gra) if inv_dct: f_frm_bnd_key = (frm1_key, frm2_key) inv_ = inv_dct.__getitem__ b_frm_bnd_key1 = tuple(map(inv_, brk_bnd_1)) b_frm_bnd_key2 = tuple(map(inv_, brk_bnd_2)) b_brk_bnd_key = tuple(map(inv_, f_frm_bnd_key)) forw_tsg = ts.graph( rct_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[brk_bnd_1, brk_bnd_2]) back_tsg = ts.graph( prds_gra, frm_bnd_keys=[b_frm_bnd_key1, b_frm_bnd_key2], brk_bnd_keys=[b_brk_bnd_key]) rcts_atm_keys = list(map(atom_keys, rct_gras)) prds_atm_keys = list(map(atom_keys, prd_gras)) if inv_dct[frm1_key] not in prds_atm_keys[1]: prds_atm_keys = list(reversed(prds_atm_keys)) assert inv_dct[frm1_key] in prds_atm_keys[1] assert inv_dct[frm2_key] in prds_atm_keys[1] # Create the reaction object _rxns.append( Reaction( rxn_cls=ReactionClass.Typ.ELIMINATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) return _rxns
def hydrogen_abstractions(rct_gras, viable_only=True): """ find hydrogen abstraction reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Hydrogen abstractions are enumerated by looping over unique unsaturated atoms on one molecule and abstracting from unique atoms on the other. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 2: for q1h_gra, q2_gra in itertools.permutations(rct_gras): hyd_keys = atom_keys(q1h_gra, sym='H') # Identify unique heavy atoms as potential donors don_keys = atom_keys(q1h_gra, excl_syms=('H', )) don_keys = atom_equivalence_class_reps(q1h_gra, don_keys) # Identify unique unsaturated atoms as potential attackers att_keys = unsaturated_atom_keys(q2_gra) att_keys = atom_equivalence_class_reps(q2_gra, att_keys) for don_key, att_key in itertools.product(don_keys, att_keys): hyd_key = atom_neighbor_atom_key(q1h_gra, don_key, symbs_first=['H'], symbs_last=[]) if hyd_key in hyd_keys: # Remove a hydrogen from the donor site q1_gra = remove_atoms(q1h_gra, {hyd_key}) # Add a hydrogen atom to the attacker site q2h_gra = add_bonded_atom(q2_gra, 'H', att_key, bnd_atm_key=hyd_key) rcts_gra = union(q1h_gra, q2_gra) prds_gra = union(q2h_gra, q1_gra) forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=[(att_key, hyd_key)], brk_bnd_keys=[(don_key, hyd_key)]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[(don_key, hyd_key)], brk_bnd_keys=[(att_key, hyd_key)]) rcts_atm_keys = list(map(atom_keys, [q1h_gra, q2_gra])) prds_atm_keys = list(map(atom_keys, [q2h_gra, q1_gra])) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.HYDROGEN_ABSTRACTION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def hydrogen_migrations(rct_gras, viable_only=True): """ find all possible hydrogen migration reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Hydrogen migrations are enumerated looping over unsaturated sites, adding hydrogens to them, and looping over non-equivalent heavy atoms and removing hydrgens from them. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 1: rct_gra, = rct_gras # Identify unsaturated sites rct_add_key = max(atom_keys(rct_gra)) + 1 rct_rad_keys = unsaturated_atom_keys(rct_gra) rct_hyd_keys = atom_keys(rct_gra, sym='H') for rct_rad_key in rct_rad_keys: # Add a hydrogen to the radical/unsaturated site rct_h_gra = add_bonded_atom(rct_gra, 'H', rct_rad_key, bnd_atm_key=rct_add_key) # Identify donor sites rct_don_keys = backbone_keys(rct_h_gra) - {rct_rad_key} for rct_don_key in rct_don_keys: rct_hyd_key = atom_neighbor_atom_key(rct_gra, rct_don_key, symbs_first=['H'], symbs_last=[]) if rct_hyd_key in rct_hyd_keys: prd_gra = remove_atoms(rct_h_gra, {rct_hyd_key}) prd_gra = relabel(prd_gra, {rct_add_key: rct_hyd_key}) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[(rct_rad_key, rct_hyd_key)], brk_bnd_keys=[(rct_don_key, rct_hyd_key)]) back_tsg = ts.graph(prd_gra, frm_bnd_keys=[(rct_don_key, rct_hyd_key)], brk_bnd_keys=[(rct_rad_key, rct_hyd_key)]) rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.HYDROGEN_MIGRATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=[atom_keys(rct_gra)], prds_keys=[atom_keys(prd_gra)], )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def eliminations(rct_gras, viable_only=True): """ find all possible elimination reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Eliminations are enumerated by forming a bond between an attacking heavy atom and another atom not initially bonded to it, forming a ring. The bond adjacent to the attacked atom is then broken, along with a second bond in the ring, downstream from the attacking heavy atom, away from the attacked atom. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 1: rct_gra, = rct_gras ngb_keys_dct = atoms_neighbor_atom_keys(rct_gra) # frm1_keys = atom_keys(rct_gra, excl_syms=('H',)) frm1_keys = unsaturated_atom_keys(rct_gra) rct_symbs = atom_symbols(rct_gra) frm1_keys_o = frozenset(key for key in frm1_keys if rct_symbs[key] == 'O') frm2_keys = atom_keys(rct_gra) bnd_keys = bond_keys(rct_gra) frm_bnd_keys = [(frm1_key, frm2_key) for frm1_key, frm2_key in itertools.product( frm1_keys_o, frm2_keys) if frm1_key != frm2_key and not frozenset({frm1_key, frm2_key}) in bnd_keys] for frm1_key, frm2_key in frm_bnd_keys: # Bond the radical atom to the hydrogen atom prds_gra = add_bonds(rct_gra, [(frm2_key, frm1_key)]) # Get keys to the ring formed by this extra bond rng_keys = next((ks for ks in rings_atom_keys(prds_gra) if frm2_key in ks and frm1_key in ks), None) # Eliminations (as far as I can tell) only happen through TSs with # 3- or 4-membered rings if rng_keys is not None and len(rng_keys) < 5: frm1_ngb_key, = ngb_keys_dct[frm1_key] & set(rng_keys) frm2_ngb_key, = ngb_keys_dct[frm2_key] & set(rng_keys) # Break the bonds on either side of the newly formed bond prds_gra = remove_bonds(prds_gra, [(frm1_key, frm1_ngb_key)]) prds_gra = remove_bonds(prds_gra, [(frm2_key, frm2_ngb_key)]) prd_gras = connected_components(prds_gra) if len(prd_gras) == 2: forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[(frm1_key, frm2_key)], brk_bnd_keys=[(frm1_key, frm1_ngb_key), (frm2_key, frm2_ngb_key) ]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[(frm1_key, frm1_ngb_key), (frm2_key, frm2_ngb_key) ], brk_bnd_keys=[(frm1_key, frm2_key)]) rcts_atm_keys = list(map(atom_keys, rct_gras)) prds_atm_keys = list(map(atom_keys, prd_gras)) if frm2_key not in prds_atm_keys[1]: prds_atm_keys = list(reversed(prds_atm_keys)) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.ELIMINATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def ring_forming_scissions(rct_gras, viable_only=True): """ find all possible ring-forming scission reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] Right now it takes the lazy, chemically specific approach of finding C-O-O-H groups and forming a bond between the O of the C-O bond and radical sites of the species, while breaking the O-O bond. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 1: rct_gra, = rct_gras # Identify the radical sites and COOH groups rad_keys = radical_atom_keys(rct_gra) cooh_grps = hydroperoxy_groups(rct_gra) # Get the bnd keys for filtering bnd_keys = bond_keys(rct_gra) # Set the forming and breaking bonds by looping over COOH groups rxn_bnd_keys = () for cooh_grp in cooh_grps: brk_bnd_key = frozenset(cooh_grp[1:3]) for rad_key in rad_keys: frm_bnd_key = frozenset({rad_key, cooh_grp[1]}) # Only includ frm bnd if it does not exist # e.g., CC[C]OO already has frm bnd -> no rxn possible if frm_bnd_key not in bnd_keys: rxn_bnd_keys += ((frm_bnd_key, brk_bnd_key), ) # Form reactions with all combinations of frm and brk bnds for frm_bnd_key, brk_bnd_key in rxn_bnd_keys: prds_gra = rct_gra prds_gra = add_bonds(prds_gra, [frm_bnd_key]) prds_gra = remove_bonds(prds_gra, [brk_bnd_key]) prd_gras = connected_components(prds_gra) if len(prd_gras) == 2: prd_gras = sort_reagents(prd_gras) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[frm_bnd_key], brk_bnd_keys=[brk_bnd_key]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[brk_bnd_key], brk_bnd_keys=[frm_bnd_key]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.RING_FORM_SCISSION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def beta_scissions(rct_gras, viable_only=True): """ find all possible beta scission reactions for these reactants :param rct_gras: graphs for the reactants, without stereo and without overlapping keys :param viable_only: Filter out reactions with non-viable products? :type viable_only: bool :returns: a list of Reaction objects :rtype: tuple[Reaction] FIX DESCRIPTION: Beta scissions are enumerated by identifying all pure single bonds (single bonds with no resonances), and looping over the results of breaking each of them. If this gives rise to two distinct fragments, the reaction is added to the list. """ assert_is_valid_reagent_graph_list(rct_gras) rxns = [] if len(rct_gras) == 1: rct_gra, = rct_gras # Identify all atom keys that neighbor radical sites rad_neighs = frozenset({}) neigh_dct = atoms_neighbor_atom_keys(rct_gra) for rad_key in radical_atom_keys(rct_gra): rad_neighs = rad_neighs | neigh_dct[rad_key] # Identify all pure single bonds involving radical site neighbor avg_bnd_ord_dct = resonance_avg_bond_orders(rct_gra) brk_bnd_keys = dict_.keys_by_value(avg_bnd_ord_dct, lambda x: x == 1) beta_bnd_keys = () for brk_bnd_key in brk_bnd_keys: if brk_bnd_key & rad_neighs: beta_bnd_keys += (brk_bnd_key, ) for brk_bnd_key in beta_bnd_keys: prds_gra = remove_bonds(rct_gra, [brk_bnd_key]) prd_gras = connected_components(prds_gra) if len(prd_gras) == 2: prd_gras = sort_reagents(prd_gras) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[], brk_bnd_keys=[brk_bnd_key]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[brk_bnd_key], brk_bnd_keys=[]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.Typ.BETA_SCISSION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) if viable_only: rxns = filter_viable_reactions(rxns) return ts_unique(rxns)
def two_bond_additions(rct_gras, prd_gras): """ two bond additions """ assert_is_valid_reagent_graph_list(rct_gras) assert_is_valid_reagent_graph_list(prd_gras) rxns = [] if len(rct_gras) == 2 and len(prd_gras) == 1: rct_gras = sort_reagents(rct_gras) x_gra, y_gra = rct_gras prd_gra, = prd_gras x_atm_keys = frozenset().union(unsaturated_atom_keys(x_gra), lone_pair_atom_keys(x_gra)) y_atm_keys = frozenset().union(unsaturated_atom_keys(y_gra), lone_pair_atom_keys(y_gra)) print('x,y keys', x_atm_keys, y_atm_keys) # Generate pairs of forming bonds, where each is a pair of idxs # describing the atoms making up the forming bond: # (frm1, frm2) = ((idx1, idx2), (idx1, idx2)) frm_bnd_pairs = tuple(itertools.product(x_atm_keys, y_atm_keys)) frm_bnds_lst = () for pair in itertools.product(frm_bnd_pairs, frm_bnd_pairs): # Preclude pairs with same idxs (formind same bond twice) if pair[0] != pair[1]: # Preclude multiple bonds formed to same atom X---A---Y if pair[0][0] != pair[1][0] and pair[0][1] != pair[1][1]: # Preclude the reverse if pair[::-1] not in frm_bnds_lst: frm_bnds_lst += (pair, ) for frm_bnd_keys in frm_bnds_lst: xy_gra = add_bonds(union(x_gra, y_gra), [set(frm_bnd_keys[0]), set(frm_bnd_keys[1])]) iso_dct = isomorphism(xy_gra, prd_gra) if iso_dct: rcts_gra = union_from_sequence(rct_gras) prds_gra = prd_gra b_brk_bnd_keys = [[ iso_dct[frm_bnd_keys[0][0]], iso_dct[frm_bnd_keys[0][1]] ], [iso_dct[frm_bnd_keys[1][0]], iso_dct[frm_bnd_keys[1][1]]]] forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=frm_bnd_keys, brk_bnd_keys=[]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[], brk_bnd_keys=b_brk_bnd_keys) # Create the reaction object rxns.append( Reaction( rxn_cls=ReactionClass.Typ.ADDITION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) return ts_unique(rxns)
def hydrogen_migrations(rct_gras, prd_gras): """ find hydrogen migrations consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) 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) rxns = [] if len(rct_gras) == 1 and len(prd_gras) == 1: rct_gra, = rct_gras prd_gra, = prd_gras # Find keys for reactant graph rct_h_key = max(atom_keys(rct_gra)) + 1 rct_rad_keys = unsaturated_atom_keys(rct_gra) # Find keys for product graph prd_h_key = max(atom_keys(prd_gra)) + 1 prd_rad_keys = unsaturated_atom_keys(prd_gra) for rct_rad_key, prd_rad_key in (itertools.product( rct_rad_keys, prd_rad_keys)): # Add hydrogens to each radical site and see if the result matches rct_h_gra = add_bonded_atom(rct_gra, 'H', rct_rad_key, bnd_atm_key=rct_h_key) prd_h_gra = add_bonded_atom(prd_gra, 'H', prd_rad_key, bnd_atm_key=prd_h_key) iso_dct = isomorphism(rct_h_gra, prd_h_gra) if iso_dct: inv_dct = dict(map(reversed, iso_dct.items())) rct_don_key = inv_dct[prd_rad_key] prd_don_key = iso_dct[rct_rad_key] # Check equivalent donor atoms for other possible TSs rct_don_keys = equivalent_atoms(rct_h_gra, rct_don_key) prd_don_keys = equivalent_atoms(prd_h_gra, prd_don_key) for rct_don_key, prd_don_key in (itertools.product( rct_don_keys, prd_don_keys)): rct_hyd_key = atom_neighbor_atom_key(rct_gra, rct_don_key, symbs_first=('H', ), symbs_last=()) prd_hyd_key = atom_neighbor_atom_key(prd_gra, prd_don_key, symbs_first=('H', ), symbs_last=()) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[(rct_rad_key, rct_hyd_key)], brk_bnd_keys=[(rct_don_key, rct_hyd_key)]) back_tsg = ts.graph(prd_gra, frm_bnd_keys=[(prd_rad_key, prd_hyd_key)], brk_bnd_keys=[(prd_don_key, prd_hyd_key)]) if isomorphism(forw_tsg, ts.reverse(back_tsg)): rxns.append( Reaction( rxn_cls=par.ReactionClass.HYDROGEN_MIGRATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=[atom_keys(rct_gra)], prds_keys=[atom_keys(prd_gra)], )) return ts_unique(rxns)
def substitutions(rct_gras, prd_gras): """ find substitutions consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) Substitutions are identified by breaking one bond in the reactants and one bond from the products and checking for isomorphism. Currently it assumes that one of the reactants has a radical site that can attack the other reactants, forming a bond and breaking another. From the perspective of breaking and forming breaking bonds, substitutions are equivalent with hydrogen abstractions. Hence, we remove all cases where the forming bond involves a hydrogen atom off the reactant in which a bond is breaking. """ assert_is_valid_reagent_graph_list(rct_gras) assert_is_valid_reagent_graph_list(prd_gras) rxns = [] if len(rct_gras) == 2 and len(prd_gras) == 2: rct_gra = union_from_sequence(rct_gras) prd_gra = union_from_sequence(prd_gras) # Loop over both orders of reactants: A+B and B+A for rgra1, rgra2 in itertools.permutations(rct_gras): bnd_keys = bond_keys(rgra1) atom_symb_dct = automol.graph.atom_symbols(rgra1) rad_keys = unsaturated_atom_keys(rgra2) # Break all possible bonds in total reactant for bnd_key, rad_key in itertools.product(bnd_keys, rad_keys): gra = remove_bonds(rct_gra, [bnd_key]) # Form all possible bonds between rad site and non-H atoms frm_keys = () for key in bnd_key: frm_symb = atom_symb_dct[key] if frm_symb != 'H': frm_keys += (key, ) for frm_key in frm_keys: gra = add_bonds(gra, [(frm_key, rad_key)]) inv_dct = isomorphism(gra, prd_gra) if inv_dct: brk_key2, = bnd_key - {frm_key} f_frm_bnd_key = (frm_key, rad_key) f_brk_bnd_key = (frm_key, brk_key2) b_frm_bnd_key = (inv_dct[frm_key], inv_dct[brk_key2]) b_brk_bnd_key = (inv_dct[frm_key], inv_dct[rad_key]) forw_tsg = ts.graph(rct_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key]) back_tsg = ts.graph(prd_gra, frm_bnd_keys=[b_frm_bnd_key], brk_bnd_keys=[b_brk_bnd_key]) rcts_atm_keys = [atom_keys(rgra1), atom_keys(rgra2)] prds_atm_keys = list(map(atom_keys, prd_gras)) if inv_dct[rad_key] not in prds_atm_keys[0]: prds_atm_keys = list(reversed(prds_atm_keys)) # Create the reaction object rxns.append( Reaction( rxn_cls=ReactionClass.Typ.SUBSTITUTION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) return ts_unique(rxns)
def hydrogen_migrations(rct_gras, prd_gras): """ find hydrogen migrations consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) 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) rxns = [] if len(rct_gras) == 1 and len(prd_gras) == 1: gra1, = rct_gras gra2, = prd_gras # Get the keys for the reactant graph h_atm_key1 = max(atom_keys(gra1)) + 1 atm_keys1 = unsaturated_atom_keys(gra1) # Generate reactions for all isomorphic graphs of products gra2_lst = (gra2, ) + isomorphic_radical_graphs(gra2) # gra2_lst = (gra2,) for _gra2 in gra2_lst: # Find keys for product graph h_atm_key2 = max(atom_keys(_gra2)) + 1 atm_keys2 = unsaturated_atom_keys(_gra2) # Run identifier 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]}) iso_dct = full_isomorphism(gra1_h, gra2_h) if iso_dct: inv_dct = dict(map(reversed, iso_dct.items())) f_frm_bnd_key = (atm_key1, inv_dct[h_atm_key2]) f_brk_bnd_key = (inv_dct[atm_key2], inv_dct[h_atm_key2]) b_frm_bnd_key = (atm_key2, iso_dct[h_atm_key1]) b_brk_bnd_key = (iso_dct[atm_key1], iso_dct[h_atm_key1]) forw_tsg = ts.graph(gra1, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key]) back_tsg = ts.graph(_gra2, frm_bnd_keys=[b_frm_bnd_key], brk_bnd_keys=[b_brk_bnd_key]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.HYDROGEN_MIGRATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=[atom_keys(gra1)], prds_keys=[atom_keys(_gra2)], )) return tuple(rxns)
def hydrogen_abstractions(rct_gras, prd_gras): """ find hydrogen abstractions consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) 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) rxns = [] if len(rct_gras) == 2 and len(prd_gras) == 2: rct_fmls = list(map(graph_formula, rct_gras)) prd_fmls = list(map(graph_formula, prd_gras)) ret = automol.formula.reac.argsort_hydrogen_abstraction( rct_fmls, prd_fmls) if ret: rct_idxs_, prd_idxs_ = ret rct_gras = list(map(rct_gras.__getitem__, rct_idxs_)) prd_gras = list(map(prd_gras.__getitem__, prd_idxs_)) q1h_gra, q2_gra = rct_gras q2h_gra, q1_gra = prd_gras rets1 = _partial_hydrogen_abstraction(q1h_gra, q1_gra) rets2 = _partial_hydrogen_abstraction(q2h_gra, q2_gra) for ret1, ret2 in itertools.product(rets1, rets2): f_q1h_q_atm_key, f_q1h_h_atm_key, b_q2_q_atm_key = ret1 b_q1h_q_atm_key, b_q1h_h_atm_key, f_q2_q_atm_key = ret2 # Create the forward/backward ts graphs rcts_gra = union_from_sequence(rct_gras) prds_gra = union_from_sequence(prd_gras) f_frm_bnd_key = (f_q2_q_atm_key, f_q1h_h_atm_key) f_brk_bnd_key = (f_q1h_q_atm_key, f_q1h_h_atm_key) b_frm_bnd_key = (b_q2_q_atm_key, b_q1h_h_atm_key) b_brk_bnd_key = (b_q1h_q_atm_key, b_q1h_h_atm_key) forw_tsg = ts.graph(rcts_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key]) back_tsg = ts.graph(prds_gra, frm_bnd_keys=[b_frm_bnd_key], brk_bnd_keys=[b_brk_bnd_key]) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.HYDROGEN_ABSTRACTION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=list(map(atom_keys, rct_gras)), prds_keys=list(map(atom_keys, prd_gras)), )) return ts_unique(rxns)
def eliminations(rct_gras, prd_gras): """ find eliminations consistent with these reactants and products :param rct_gras: reactant graphs (must have non-overlapping keys) :param prd_gras: product graphs (must have non-overlapping keys) Eliminations are identified by forming a bond between an attacking heavy atom and another atom not initially bonded to it, forming a ring. The bond adjacent to the attacked atom is then broken, along with a second bond in the ring, downstream of the attacking heavy atom, away from the attacked atom. """ _assert_is_valid_reagent_graph_list(rct_gras) _assert_is_valid_reagent_graph_list(prd_gras) rxns = [] if len(rct_gras) == 1 and len(prd_gras) == 2: rct_gra, = rct_gras prds_gra = union_from_sequence(prd_gras) ngb_keys_dct = atoms_neighbor_atom_keys(rct_gra) frm1_keys = atom_keys(rct_gra, excl_syms=('H', )) frm2_keys = atom_keys(rct_gra) bnd_keys = bond_keys(rct_gra) frm_bnd_keys = [ (frm1_key, frm2_key) for frm1_key, frm2_key in itertools.product(frm1_keys, frm2_keys) if frm1_key != frm2_key and not frozenset({frm1_key, frm2_key}) in bnd_keys ] for frm1_key, frm2_key in frm_bnd_keys: # Bond the radical atom to the hydrogen atom gra_ = add_bonds(rct_gra, [(frm2_key, frm1_key)]) # Get keys to the ring formed by this extra bond rng_keys = next((ks for ks in rings_atom_keys(gra_) if frm2_key in ks and frm1_key in ks), None) # Eliminations (as far as I can tell) only happen through TSs with # 3- or 4-membered rings if rng_keys is not None and len(rng_keys) < 5: frm1_ngb_key, = ngb_keys_dct[frm1_key] & set(rng_keys) frm2_ngb_key, = ngb_keys_dct[frm2_key] & set(rng_keys) # Break the bonds on either side of the newly formed bond gra_ = remove_bonds(gra_, [(frm1_key, frm1_ngb_key)]) gra_ = remove_bonds(gra_, [(frm2_key, frm2_ngb_key)]) inv_dct = isomorphism(gra_, prds_gra) if inv_dct: f_frm_bnd_key = (frm1_key, frm2_key) f_brk_bnd_key1 = (frm1_key, frm1_ngb_key) f_brk_bnd_key2 = (frm2_key, frm2_ngb_key) inv_ = inv_dct.__getitem__ b_frm_bnd_key1 = tuple(map(inv_, f_brk_bnd_key1)) b_frm_bnd_key2 = tuple(map(inv_, f_brk_bnd_key2)) b_brk_bnd_key = tuple(map(inv_, f_frm_bnd_key)) forw_tsg = ts.graph( rct_gra, frm_bnd_keys=[f_frm_bnd_key], brk_bnd_keys=[f_brk_bnd_key1, f_brk_bnd_key2]) back_tsg = ts.graph( prds_gra, frm_bnd_keys=[b_frm_bnd_key1, b_frm_bnd_key2], brk_bnd_keys=[b_brk_bnd_key]) rcts_atm_keys = list(map(atom_keys, rct_gras)) prds_atm_keys = list(map(atom_keys, prd_gras)) if inv_dct[frm2_key] not in prds_atm_keys[1]: prds_atm_keys = list(reversed(prds_atm_keys)) # Create the reaction object rxns.append( Reaction( rxn_cls=par.ReactionClass.ELIMINATION, forw_tsg=forw_tsg, back_tsg=back_tsg, rcts_keys=rcts_atm_keys, prds_keys=prds_atm_keys, )) return ts_unique(rxns)