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 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)