def _decompose_ring_system_atom_keys(rsy): """ decompose a ring system into a ring and a series of arcs """ # sort from smallest to largest rngs_pool = sorted( rings(rsy), key=lambda x: atom_count(x, with_implicit=False)) decomp = () decomp_bnd_keys = set({}) rng = rngs_pool.pop(0) bnd_keys = bond_keys(rng) atm_keys = _sorted_ring_atom_keys(bnd_keys) decomp += (atm_keys,) decomp_bnd_keys.update(bnd_keys) while rngs_pool: decomp_rsy = bond_induced_subgraph(rsy, decomp_bnd_keys) for idx, rng in enumerate(rngs_pool): arcs = ring_arc_complement_atom_keys(decomp_rsy, rng) if arcs: rngs_pool.pop(idx) decomp += arcs decomp_bnd_keys.update(bond_keys(rng)) return decomp
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 rings_bond_keys(gra): """ bond keys for each ring in the graph (minimal basis) """ bnd_keys = bond_keys(gra) def _ring_bond_keys(rng_atm_keys): return frozenset(filter(lambda x: x <= rng_atm_keys, bnd_keys)) nxg = _networkx.from_graph(gra) rng_atm_keys_lst = _networkx.minimum_cycle_basis(nxg) rng_bnd_keys_lst = frozenset(map(_ring_bond_keys, rng_atm_keys_lst)) return rng_bnd_keys_lst
def ring_arc_complement_atom_keys(gra, rng): """ non-intersecting arcs from a ring that shares segments with a graph """ gra_atm_bnd_dct = atom_bond_keys(gra) rng_atm_bnd_dct = atom_bond_keys(rng) # 1. find divergence points, given by the atom at which the divergence # occurs and the bond followed by the ring as it diverges div_dct = {} for atm_key in atom_keys(gra) & atom_keys(rng): div = rng_atm_bnd_dct[atm_key] - gra_atm_bnd_dct[atm_key] if div: bnd_key, = div div_dct[atm_key] = bnd_key # 2. cycle through the ring atoms; if you meet a starting divergence, start # an arc; extend the arc until you meet an ending divergence; repeat until # all divergences are accounted for atm_keys = _sorted_ring_atom_keys(bond_keys(rng)) arcs = [] arc = [] for atm_key, next_atm_key in mit.windowed(itertools.cycle(atm_keys), 2): bnd_key = frozenset({atm_key, next_atm_key}) # if we haven't started an arc, see if we are at a starting divergence; # if so, start the arc now and cross the divergence from our list if not arc: if atm_key in div_dct and div_dct[atm_key] == bnd_key: div_dct.pop(atm_key) arc.append(atm_key) # if we've started an arc, extend it; then, check if we are at an # ending divergence; if so, end the arc and cross the divergence from # our list; add it to our list of arcs else: arc.append(atm_key) if next_atm_key in div_dct and div_dct[next_atm_key] == bnd_key: div_dct.pop(next_atm_key) arc.append(next_atm_key) arcs.append(arc) arc = [] # if no divergences are left, break out of the loop if not div_dct: break arcs = tuple(map(tuple, arcs)) return arcs
def torsion_leading_atom(zma, key1, key2, zgra=None): """ Obtain the leading atom for a torsion coordinate about a torsion axis. The leading atom is the atom whose dihedral defines the torsional coordinate, which must always be the first dihedral coordinate for this bond. A bond is properly decoupled if all other dihedrals along this bond depend on the leading atom. :param zma: the z-matrix :type zma: automol Z-Matrix data structure :param key1: the first key in the torsion axis (rotational bond) :type key1: int :param key2: the second key in the torsion axis (rotational bond) :type key2: int :param gra: an automol graph data structure, aligned to the z-matrix; used to check connectivity when necessary :rtype: int """ key_mat = key_matrix(zma) krs1 = [(key, row) for key, row in enumerate(key_mat) if row[:2] == (key1, key2)] krs2 = [(key, row) for key, row in enumerate(key_mat) if row[:2] == (key2, key1)] lead_key_candidates = [] for krs in (krs1, krs2): if krs: keys, rows = zip(*krs) start_key = keys[0] assert all(row[-1] == start_key for row in rows[1:]), ( "Torsion coordinate along bond {:d}-{:d} not decoupled:\n{}". format(key1, key2, string(zma, one_indexed=False))) if rows[0][-1] is not None: lead_key_candidates.append(start_key) if not lead_key_candidates: lead_key = None elif len(lead_key_candidates) == 1: lead_key = lead_key_candidates[0] else: # If we get to this point, then the z-matrix includes dihedrals across # the key1-key2 bond in both directions and we have to choose which # dihedral to use. This mans there will be two lead_key_candidates. zgra = automol.convert.zmat.graph(zma) if zgra is None else zgra # Let key0 be the lead key and let (key1, key2, key3) be its key row in # the z-matrix. For the torsion coordinate, key0-key1-key2-key3 should # all be connected in a line. For subsidiary dihedral coordinates, key3 # will be connected to key1 instead of key2. # A simple solution is therefore to choose the lead key based on # whether or not key2 and key3 are connected, which is what this code # does. bnd_keys = bond_keys(zgra) lead_key = next((k for k in lead_key_candidates if frozenset(key_mat[k][-2:]) in bnd_keys), None) # If that fails, choose the key that appears earlier. It's possible # that it would be better to choose the later one, in which case we # would replace the min() here with a max(). if lead_key is None: lead_key = min(lead_key_candidates) return lead_key
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