예제 #1
0
파일: _rot.py 프로젝트: sjklipp/autochem
def linear_segments_atom_keys(gra, lin_keys=None):
    """ atom keys for linear segments in the graph
    """
    ngb_keys_dct = atoms_neighbor_atom_keys(without_dummy_atoms(gra))

    lin_keys = (dummy_atoms_neighbor_atom_key(gra).values()
                if lin_keys is None else lin_keys)

    lin_keys = [k for k in lin_keys if len(ngb_keys_dct[k]) <= 2]

    lin_segs = connected_components(subgraph(gra, lin_keys))

    lin_keys_lst = []
    for lin_seg in lin_segs:
        lin_seg_keys = atom_keys(lin_seg)
        if len(lin_seg_keys) == 1:
            key, = lin_seg_keys
            lin_keys_lst.append([key])
        else:
            end_key1, end_key2 = sorted([
                key
                for key, ngb_keys in atoms_neighbor_atom_keys(lin_seg).items()
                if len(ngb_keys) == 1
            ])
            ngb_keys_dct = atoms_neighbor_atom_keys(lin_seg)

            key = None
            keys = [end_key1]
            while key != end_key2:
                key, = ngb_keys_dct[keys[-1]] - set(keys)
                keys.append(key)
            lin_keys_lst.append(keys)

    lin_keys_lst = tuple(map(tuple, lin_keys_lst))
    return lin_keys_lst
예제 #2
0
파일: _stereo.py 프로젝트: sjklipp/autochem
def atoms_stereo_sorted_neighbor_atom_keys(sgr):
    """ Obtain neighbor atom keys for all stereo atoms, sorted by stereo
    priority.

    Includes all stereo atoms and atoms constituting stereo bonds. For stereo
    bonds, the neighbors for each atom in the bond exclude the other atom in
    the bond.

    :param sgr: the graph
    :returns: Neighbor atom keys, sorted by stereo priority, keyed by atom.
    :rtype: dict
    """
    atm_ste_keys = atom_stereo_keys(sgr)
    bnd_ste_keys = bond_stereo_keys(sgr)
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(sgr)

    ste_atm_ngb_keys_dct = {}
    for atm_key in atm_ste_keys:
        atm_ngb_keys = atm_ngb_keys_dct[atm_key]

        ste_atm_ngb_keys_dct[atm_key] = atom_stereo_sorted_neighbor_atom_keys(
            sgr, atm_key, atm_ngb_keys)

    for bnd_key in bnd_ste_keys:
        atm1_key, atm2_key = sorted(bnd_key)

        atm1_ngb_keys = atm_ngb_keys_dct[atm1_key] - bnd_key
        atm2_ngb_keys = atm_ngb_keys_dct[atm2_key] - bnd_key

        ste_atm_ngb_keys_dct[atm1_key] = atom_stereo_sorted_neighbor_atom_keys(
            sgr, atm1_key, atm1_ngb_keys)
        ste_atm_ngb_keys_dct[atm2_key] = atom_stereo_sorted_neighbor_atom_keys(
            sgr, atm2_key, atm2_ngb_keys)

    return ste_atm_ngb_keys_dct
예제 #3
0
파일: _stereo.py 프로젝트: sjklipp/autochem
def stereogenic_atom_keys(gra, assigned=False):
    """ Find stereogenic atoms in this graph.

    If the `assigned` flag is set to `False`, only  unassigned stereogenic
    atoms will be detected.

    :param gra: the graph
    :param assigned: Include atoms that already have stereo assignments?
    :param assigned: bool
    :returns: the stereogenic atom keys
    :rtype: frozenset
    """
    gra = without_bond_orders(gra)
    gra = explicit(gra)  # for simplicity, add the explicit hydrogens back in
    atm_keys = dict_.keys_by_value(atom_bond_valences(gra), lambda x: x == 4)
    if not assigned:
        # Remove assigned stereo keys
        atm_keys -= atom_stereo_keys(gra)

    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)

    def _is_stereogenic(atm_key):
        atm_ngb_keys = list(atm_ngb_keys_dct[atm_key])
        pri_vecs = [
            stereo_priority_vector(gra, atm_key, atm_ngb_key)
            for atm_ngb_key in atm_ngb_keys
        ]
        ret = not any(pv1 == pv2
                      for pv1, pv2 in itertools.combinations(pri_vecs, r=2))
        return ret

    ste_gen_atm_keys = frozenset(filter(_is_stereogenic, atm_keys))
    return ste_gen_atm_keys
예제 #4
0
def heuristic_bond_angle(gra, key1, key2, key3, degree=False, check=False,
                         hyb_dct=None):
    """ heuristic bond angle

    If being reused multiple times, you can speed this up by passing in the
    hybridizations, so they don't need to be recalculated
    """
    if check:
        assert {key1, key3} <= set(atoms_neighbor_atom_keys(gra)[key2])

    if hyb_dct is None:
        hyb_dct = resonance_dominant_atom_hybridizations(gra)

    hyb2 = hyb_dct[key2]
    if hyb2 == 3:
        ang = TET_ANG
    elif hyb2 == 2:
        ang = TRI_ANG
    else:
        assert hyb2 == 1
        ang = LIN_ANG

    ang *= 1 if degree else phycon.DEG2RAD

    return ang
예제 #5
0
def _bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct):
    """ get the current stereo parity of a bond from its geometry
    """
    atm1_key, atm2_key = bnd_key
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    atm1_ngb_keys = atm_ngb_keys_dct[atm1_key] - {atm2_key}
    atm2_ngb_keys = atm_ngb_keys_dct[atm2_key] - {atm1_key}

    atm1_ngb_keys = atom_stereo_sorted_neighbor_atom_keys(
        gra, atm1_key, atm1_ngb_keys)
    atm2_ngb_keys = atom_stereo_sorted_neighbor_atom_keys(
        gra, atm2_key, atm2_ngb_keys)

    # get the top priority neighbor keys on each side
    atm1_ngb_key = atm1_ngb_keys[0]
    atm2_ngb_key = atm2_ngb_keys[0]

    # determine the parity based on the coordinates
    xyzs = coordinates(geo)
    atm1_xyz = xyzs[geo_idx_dct[atm1_key]]
    atm2_xyz = xyzs[geo_idx_dct[atm2_key]]
    atm1_ngb_xyz = xyzs[geo_idx_dct[atm1_ngb_key]]
    atm2_ngb_xyz = xyzs[geo_idx_dct[atm2_ngb_key]]
    atm1_bnd_vec = numpy.subtract(atm1_ngb_xyz, atm1_xyz)
    atm2_bnd_vec = numpy.subtract(atm2_ngb_xyz, atm2_xyz)
    dot_val = numpy.vdot(atm1_bnd_vec, atm2_bnd_vec)
    assert dot_val != 0.  # for now, assume no collinear
    par = dot_val > 0.
    return par
예제 #6
0
def _cumulene_chains(rgr):
    atm_hyb_dct = resonance_dominant_atom_hybridizations(rgr)
    sp1_atm_keys = dict_.keys_by_value(atm_hyb_dct, lambda x: x == 1)
    sp2_atm_keys = dict_.keys_by_value(atm_hyb_dct, lambda x: x == 2)

    atm_ngb_keys_dct = atoms_neighbor_atom_keys(rgr)

    def _cumulene_chain(chain):
        ret = None
        atm_key = chain[-1]
        next_atm_keys = atm_ngb_keys_dct[atm_key] - {chain[-2]}
        if next_atm_keys:
            assert len(next_atm_keys) == 1
            next_atm_key, = next_atm_keys
            if next_atm_key in sp1_atm_keys:
                chain.append(next_atm_key)
                ret = _cumulene_chain(chain)
            elif next_atm_key in sp2_atm_keys:
                chain.append(next_atm_key)
                ret = chain
        return ret

    cum_chains = []
    for atm_key in sp2_atm_keys:
        sp1_atm_ngb_keys = atm_ngb_keys_dct[atm_key] & sp1_atm_keys
        chains = [[atm_key, atm_ngb_key] for atm_ngb_key in sp1_atm_ngb_keys]
        for chain in chains:
            cum_chain = _cumulene_chain(chain)
            if cum_chain is not None:
                cum_chains.append(cum_chain)

    cum_chains = tuple(map(tuple, cum_chains))
    return cum_chains
예제 #7
0
def _radical_graph_isomorphisms(gra):
    """ Generate a set of graphs where the radical has been migrated to a
        new atom and then calculate the isomorphism between the input
        graph and all of the new graphs generated by moving the radical
    """

    # Determine useful keys
    symbols = atom_symbols(gra)
    unsat_keys = unsaturated_atom_keys(gra)
    unsat_key = next(iter(unsat_keys))
    h_atm_key = max(symbols.keys()) + 1

    new_gras, isomorphisms = [], []
    for aidx, symbol in enumerate(symbols.values()):

        # Loop over saturated (non-radical) heavy atoms
        if symbol != 'H' and aidx != unsat_key:

            # Add hydrogen atom to radical atom
            new_graph = add_atom_explicit_hydrogen_keys(
                gra, {unsat_key: [h_atm_key]})

            # Remove hydrogen from saturated atom
            neighbors = atoms_neighbor_atom_keys(new_graph)
            for neigh in neighbors[aidx]:
                if symbols[neigh] == 'H':
                    aneighbor = neigh
                    break
            new_graph = remove_atoms(new_graph, [aneighbor])

            # Build lists to return
            new_gras.append(new_graph)
            isomorphisms.append(full_isomorphism(gra, new_graph))

    return tuple(zip(new_gras, isomorphisms))
예제 #8
0
파일: _graph.py 프로젝트: sjklipp/autochem
def atom_longest_chain(gra, atm_key):
    """ longest chain for a specific atom
    """
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    atm_ngb_keys = atm_ngb_keys_dct[atm_key]

    chains_lst = []
    if atm_ngb_keys:
        next_chains_lst = [[atm_key, atm_ngb_key]
                           for atm_ngb_key in atm_ngb_keys]

        while True:
            chains_lst = next_chains_lst
            next_chains_lst = []
            for chain in chains_lst:
                atm_ngb_keys = atm_ngb_keys_dct[chain[-1]]
                next_atm_keys = sorted(atm_ngb_keys - set(chain))
                for next_atm_key in next_atm_keys:
                    next_chains_lst.append(chain + [next_atm_key])

            if not next_chains_lst:
                break

        max_chain = tuple(chains_lst[0])
    else:
        max_chain = tuple((atm_key, ))
    return max_chain
예제 #9
0
파일: _rot.py 프로젝트: sjklipp/autochem
def rotational_symmetry_number(gra, key1, key2, lin_keys=None):
    """ get the rotational symmetry number along a given rotational axis

    :param gra: the graph
    :param key1: the first atom key
    :param key2: the second atom key
    """
    ngb_keys_dct = atoms_neighbor_atom_keys(without_dummy_atoms(gra))
    imp_hyd_vlc_dct = atom_implicit_hydrogen_valences(implicit(gra))

    axis_keys = {key1, key2}
    # If the keys are part of a linear chain, use the ends of that for the
    # symmetry number calculation
    lin_keys_lst = linear_segments_atom_keys(gra, lin_keys=lin_keys)
    for keys in lin_keys_lst:
        if key1 in keys or key2 in keys:
            if len(keys) == 1:
                key1, key2 = sorted(ngb_keys_dct[keys[0]])
            else:
                key1, = ngb_keys_dct[keys[0]] - {keys[1]}
                key2, = ngb_keys_dct[keys[-1]] - {keys[-2]}
                axis_keys |= set(keys)
                break

    sym_num = 1
    for key in (key1, key2):
        if key in imp_hyd_vlc_dct:
            ngb_keys = ngb_keys_dct[key] - axis_keys
            if len(ngb_keys) == imp_hyd_vlc_dct[key] == 3:
                sym_num = 3
                break
    return sym_num
예제 #10
0
def linear_atoms(geo, gra=None, tol=5.):
    """ find linear atoms in a geometry (atoms with 180 degree bond angle)

        :param geo: the geometry
        :type geo: automol geometry data structure
        :param gra: the graph describing connectivity; if None, a connectivity
            graph will be generated using default distance thresholds
        :type gra: automol graph data structure
        :param tol: the tolerance threshold for linearity, in degrees
        :type tol: float
        :rtype: tuple(int)
    """

    gra = connectivity_graph(geo) if gra is None else gra
    ngb_idxs_dct = atoms_neighbor_atom_keys(gra)

    lin_idxs = []
    for idx in range(count(geo)):
        nidxs = ngb_idxs_dct[idx]
        if len(nidxs) >= 2:
            for nidx1, nidx2 in itertools.combinations(nidxs, 2):
                ang = central_angle(geo, nidx1, idx, nidx2, degree=True)
                if numpy.abs(ang - 180.) < tol:
                    lin_idxs.append(idx)

    lin_idxs = tuple(lin_idxs)

    return lin_idxs
예제 #11
0
파일: _graph.py 프로젝트: sjklipp/autochem
def bond_symmetry_numbers(gra, frm_bnd_key=None, brk_bnd_key=None):
    """ symmetry numbers, by bond

    the (approximate) symmetry number of the torsional potential for this bond,
    based on the hydrogen counts for each atom
    It is reduced to 1 if one of the H atoms in the torsional bond is a
    neighbor to the special bonding atom (the atom that is being transferred)
    """
    imp_gra = implicit(gra)
    atm_imp_hyd_vlc_dct = atom_implicit_hydrogen_valences(imp_gra)

    bnd_keys = bond_keys(imp_gra)

    tfr_atm = None
    if frm_bnd_key and brk_bnd_key:
        for atm_f in list(frm_bnd_key):
            for atm_b in list(brk_bnd_key):
                if atm_f == atm_b:
                    tfr_atm = atm_f

        if tfr_atm:
            neighbor_dct = atoms_neighbor_atom_keys(gra)
            nei_tfr = neighbor_dct[tfr_atm]

            atms = gra[0]
            all_hyds = []
            for atm in atms:
                if atms[atm][0] == 'H':
                    all_hyds.append(atm)
        else:
            nei_tfr = {}

    bnd_symb_num_dct = {}
    bnd_symb_nums = []
    for bnd_key in bnd_keys:
        bnd_sym = 1
        vlc = max(map(atm_imp_hyd_vlc_dct.__getitem__, bnd_key))
        if vlc == 3:
            bnd_sym = 3
            if tfr_atm:
                for atm in nei_tfr:
                    nei_s = neighbor_dct[atm]
                    h_nei = 0
                    for nei in nei_s:
                        if nei in all_hyds:
                            h_nei += 1
                    if h_nei == 3:
                        bnd_sym = 1
        bnd_symb_nums.append(bnd_sym)

    bnd_symb_num_dct = dict(zip(bnd_keys, bnd_symb_nums))

    # fill in the rest of the bonds for completeness
    bnd_symb_num_dct = dict_.by_key(bnd_symb_num_dct,
                                    bond_keys(gra),
                                    fill_val=1)

    return bnd_symb_num_dct
예제 #12
0
파일: _rot.py 프로젝트: sjklipp/autochem
def rotational_bond_keys(gra, lin_keys=None, with_h_rotors=True):
    """ get all rotational bonds for a graph

    :param gra: the graph
    :param lin_keys: keys to linear atoms in the graph
    """
    gra = explicit(gra)
    sym_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    bnd_ord_dct = resonance_dominant_bond_orders(gra)
    rng_bnd_keys = list(itertools.chain(*rings_bond_keys(gra)))

    def _is_rotational_bond(bnd_key):
        ngb_keys_lst = [ngb_keys_dct[k] - bnd_key for k in bnd_key]

        is_single = max(bnd_ord_dct[bnd_key]) <= 1
        has_neighbors = all(ngb_keys_lst)
        not_in_ring = bnd_key not in rng_bnd_keys

        is_h_rotor = any(
            set(map(sym_dct.__getitem__, ks)) == {'H'} for ks in ngb_keys_lst)

        return is_single and has_neighbors and not_in_ring and (
            not is_h_rotor or with_h_rotors)

    rot_bnd_keys = frozenset(filter(_is_rotational_bond, bond_keys(gra)))

    lin_keys_lst = linear_segments_atom_keys(gra, lin_keys=lin_keys)
    dum_keys = tuple(atom_keys(gra, sym='X'))
    for keys in lin_keys_lst:
        bnd_keys = sorted((k for k in rot_bnd_keys if k & set(keys)),
                          key=sorted)

        # Check whether there are neighboring atoms on either side of the
        # linear segment
        excl_keys = set(keys) | set(dum_keys)

        end_key1 = atom_neighbor_atom_key(gra,
                                          keys[0],
                                          excl_atm_keys=excl_keys)

        excl_keys |= {end_key1}
        end_key2 = atom_neighbor_atom_key(gra,
                                          keys[-1],
                                          excl_atm_keys=excl_keys)

        end_keys = {end_key1, end_key2}
        ngb_keys_lst = [ngb_keys_dct[k] - excl_keys for k in end_keys]
        has_neighbors = all(ngb_keys_lst)

        if not has_neighbors:
            rot_bnd_keys -= set(bnd_keys)
        else:
            rot_bnd_keys -= set(bnd_keys[:-1])

    return rot_bnd_keys
예제 #13
0
파일: vmat.py 프로젝트: sjklipp/autochem
def _atoms_missing_neighbors(gra, zma_keys):
    """ get atoms from the list currently in the v-matrix with neighbors that
    are not in the v-matrix
    """
    ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    keys = []
    for key in zma_keys:
        if any(k not in zma_keys for k in ngb_keys_dct[key]):
            keys.append(key)
    keys = tuple(keys)
    return keys
예제 #14
0
def atom_groups(gra, atm):
    """ return a list of groups off of one atom
    """
    adj_atms = atoms_neighbor_atom_keys(gra)
    keys = []
    for atmi in adj_atms[atm]:
        key = [atm, atmi]
        key.sort()
        key = frozenset(key)
        keys.append(key)
    gras = remove_bonds(gra, keys)
    return connected_components(gras)
예제 #15
0
파일: _stereo.py 프로젝트: sjklipp/autochem
def stereogenic_bond_keys(gra, assigned=False):
    """ Find stereogenic bonds in this graph.

    If the `assigned` flag is set to `False`, only  unassigned stereogenic
    bonds will be detected.

    :param gra: the graph
    :param assigned: Include bonds that already have stereo assignments?
    :param assigned: bool
    :returns: the stereogenic bond keys
    :rtype: frozenset
    """
    gra = without_bond_orders(gra)
    gra = explicit(gra)  # for simplicity, add the explicit hydrogens back in

    # get candidates: planar bonds
    bnd_keys = sp2_bond_keys(gra)

    if not assigned:
        # remove bonds that already have stereo assignments
        bnd_keys -= bond_stereo_keys(gra)

    bnd_keys -= functools.reduce(  # remove double bonds in small rings
        frozenset.union, filter(lambda x: len(x) < 8, rings_bond_keys(gra)),
        frozenset())

    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)

    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(
                    gra, atm_key, atm_ngb_keys[0]) == stereo_priority_vector(
                        gra, 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
예제 #16
0
파일: _graph.py 프로젝트: sjklipp/autochem
def equivalent_bonds(gra, bnd_key, stereo=True, dummy=True):
    """ Identify sets of isomorphically equivalent bonds

    Two bonds are equivalent if they transform into each other under an
    automorphism

    :param gra: A graph
    :param bnd_key: An bond key for the graph, which may be sorted or unsorted
    :param backbone_only: Compare backbone atoms only?
    :type stereo: bool
    :param dummy: Consider dummy atoms?
    :type dummy: bool
    :returns: Keys to equivalent bonds
    :rtype: frozenset
    """
    bnd_key = tuple(bnd_key)
    bnd_keys = list(map(tuple, map(sorted, bond_keys(gra))))
    bnd_keys += list(map(tuple, map(reversed, bnd_keys)))
    assert bnd_key in bnd_keys, "{} not in {}".format(bnd_key, bnd_keys)

    atm_symb_dct = atom_symbols(gra)
    atm_ngbs_dct = atoms_neighbor_atom_keys(gra)

    def _symbols(bnd_key):
        return list(map(atm_symb_dct.__getitem__, bnd_key))

    def _neighbor_symbols(bnd_key):
        key1, key2 = bnd_key
        nsymbs1 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key1]))
        nsymbs2 = sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key2]))
        return nsymbs1, nsymbs2

    # 1. Find bonds with the same atom types
    bnd_symbs = _symbols(bnd_key)
    cand_keys = [k for k in bnd_keys if _symbols(k) == bnd_symbs]

    # 2. Of those, find bonds with the same neighboring atom types
    bnd_ngb_symbs = _neighbor_symbols(bnd_key)
    cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == bnd_ngb_symbs]

    # 3. Find the equivalent bonds from the list of candidates.
    # Strategy: Change the atom symbols to 'Lv' and 'Ts' and check for
    # isomorphism.  Assumes none of the compounds have element 116 or 117.
    ref_gra = set_atom_symbols(gra, {bnd_key[0]: 'Lv', bnd_key[1]: 'Ts'})
    bnd_keys = []
    for key in cand_keys:
        comp_gra = set_atom_symbols(gra, {key[0]: 'Lv', key[1]: 'Ts'})
        if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy):
            bnd_keys.append(key)

    return frozenset(bnd_keys)
예제 #17
0
파일: _graph.py 프로젝트: sjklipp/autochem
def atom_groups(gra, atm, stereo=False):
    """ return a list of groups off of one atom
    """
    if not stereo:
        gra = without_stereo_parities(gra)
    adj_atms = atoms_neighbor_atom_keys(gra)
    keys = []
    for atmi in adj_atms[atm]:
        key = [atm, atmi]
        key.sort()
        key = frozenset(key)
        keys.append(key)
    gras = remove_bonds(gra, keys)
    return connected_components(gras)
예제 #18
0
파일: _graph.py 프로젝트: sjklipp/autochem
def terminal_heavy_atom_keys(gra):
    """ terminal heavy atoms, sorted by atom type and hydrogen count
    """
    gra = implicit(gra)
    atm_imp_hyd_vlc_dct = atom_implicit_hydrogen_valences(gra)
    atm_keys = [
        key for key, ngb_keys in atoms_neighbor_atom_keys(gra).items()
        if len(ngb_keys) == 1
    ]
    atm_keys = sorted(atm_keys,
                      key=atm_imp_hyd_vlc_dct.__getitem__,
                      reverse=True)
    atm_symbs = dict_.values_by_key(atom_symbols(gra), atm_keys)
    srt = automol.formula.argsort_symbols(atm_symbs, symbs_first=('C', ))
    atm_keys = tuple(map(atm_keys.__getitem__, srt))
    return atm_keys
예제 #19
0
파일: _graph.py 프로젝트: sjklipp/autochem
def atoms_second_degree_neighbor_atom_keys(gra):
    """ keys of second-degree neighboring atoms, by atom

    That is, atoms that are connected through an intermediate atom
    """
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    atm_ngb2_keys_dct = {}
    for atm_key, atm_ngb_keys in atm_ngb_keys_dct.items():
        # Union of neighbors of neighbors
        atm_ngb2_keys = functools.reduce(
            operator.or_, map(atm_ngb_keys_dct.__getitem__, atm_ngb_keys))
        # Subtract of the atom itself and its neighbors (in case of 3-rings)
        atm_ngb2_keys -= {atm_key} | atm_ngb_keys

        atm_ngb2_keys_dct[atm_key] = frozenset(atm_ngb2_keys)
    return atm_ngb2_keys_dct
예제 #20
0
def chirality_constraint_bounds(gra, keys):
    """ bounds for enforcing chirality restrictions
    """
    ste_keys = set(atom_stereo_keys(gra)) & set(keys)
    par_dct = atom_stereo_parities(gra)
    ngb_key_dct = atoms_neighbor_atom_keys(gra)

    def _chirality_constraint(key):
        ngb_keys = ngb_key_dct[key]
        ngb_keys = atom_stereo_sorted_neighbor_atom_keys(gra, key, ngb_keys)
        idxs = tuple(map(keys.index, ngb_keys))
        vol_range = (-999., -7.) if par_dct[key] else (+7., +999.)
        return idxs, vol_range

    chi_dct = dict(map(_chirality_constraint, ste_keys))
    return chi_dct
예제 #21
0
def _atom_stereo_corrected_geometry(gra, atm_ste_par_dct, geo, geo_idx_dct):
    """ correct the atom stereo parities of a geometry, for a subset of atoms
    """
    ring_atm_keys = set(itertools.chain(*rings_atom_keys(gra)))
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)

    atm_keys = list(atm_ste_par_dct.keys())
    for atm_key in atm_keys:
        par = atm_ste_par_dct[atm_key]
        curr_par = _atom_stereo_parity_from_geometry(gra, atm_key, geo,
                                                     geo_idx_dct)

        if curr_par != par:
            atm_ngb_keys = atm_ngb_keys_dct[atm_key]
            # for now, we simply exclude rings from the pivot keys
            # (will not work for stereo atom at the intersection of two rings)
            atm_piv_keys = list(atm_ngb_keys - ring_atm_keys)[:2]
            assert len(atm_piv_keys) == 2
            atm3_key, atm4_key = atm_piv_keys

            # get coordinates
            xyzs = coordinates(geo)
            atm_xyz = xyzs[geo_idx_dct[atm_key]]
            atm3_xyz = xyzs[geo_idx_dct[atm3_key]]
            atm4_xyz = xyzs[geo_idx_dct[atm4_key]]

            # do the rotation
            rot_axis = util.vec.unit_bisector(atm3_xyz,
                                              atm4_xyz,
                                              orig_xyz=atm_xyz)

            rot_atm_keys = (
                atom_keys(branch(gra, atm_key, {atm_key, atm3_key}))
                | atom_keys(branch(gra, atm_key, {atm_key, atm4_key})))
            rot_idxs = list(map(geo_idx_dct.__getitem__, rot_atm_keys))

            geo = automol.geom.rotate(geo,
                                      rot_axis,
                                      numpy.pi,
                                      orig_xyz=atm_xyz,
                                      idxs=rot_idxs)

        assert _atom_stereo_parity_from_geometry(gra, atm_key, geo,
                                                 geo_idx_dct) == par
        gra = set_atom_stereo_parities(gra, {atm_key: par})

    return geo, gra
예제 #22
0
def heuristic_bond_distance(gra, key1, key2, angstrom=True, check=False):
    """ heuristic bond distance (in angstroms)
    """
    if check:
        assert key1 in atoms_neighbor_atom_keys(gra)[key2]

    symb_dct = atom_symbols(gra)
    symb1 = symb_dct[key1]
    symb2 = symb_dct[key2]

    if ptab.to_number(symb1) == 1 or ptab.to_number(symb2) == 1:
        dist = XH_DIST
    else:
        dist = XY_DIST

    dist *= 1 if angstrom else phycon.ANG2BOHR

    return dist
예제 #23
0
def radical_dissociation_prods(gra, pgra1):
    """ given a dissociation product, determine the other product
    """
    gra = without_fractional_bonds(gra)

    pgra2 = None
    rads = sing_res_dom_radical_atom_keys(gra)
    adj_atms = atoms_neighbor_atom_keys(gra)
    # adj_idxs = tuple(adj_atms[rad] for rad in rads)
    for rad in rads:
        for adj in adj_atms[rad]:
            for group in atom_groups(gra, adj):
                if full_isomorphism(explicit(group), explicit(pgra1)):
                    pgra2 = remove_atoms(gra, atom_keys(group))
                    # pgra2 = remove_bonds(pgra2, bond_keys(group))
                    if bond_keys(group) in pgra2:
                        pgra2 = remove_bonds(pgra2, bond_keys(group))
    return (pgra1, pgra2)
예제 #24
0
파일: _graph.py 프로젝트: sjklipp/autochem
def equivalent_atoms(gra, atm_key, stereo=True, dummy=True):
    """ Identify sets of isomorphically equivalent atoms

    Two atoms are equivalent if they transform into each other under an
    automorphism

    :param gra: A graph
    :param atm_key: An atom key for the graph
    :param stereo: Consider stereo?
    :type stereo: bool
    :param dummy: Consider dummy atoms?
    :type dummy: bool
    :returns: Keys to equivalent atoms
    :rtype: frozenset
    """
    assert atm_key in atom_keys(gra), ("{} not in {}".format(
        atm_key, atom_keys(gra)))

    atm_symb_dct = atom_symbols(gra)
    atm_ngbs_dct = atoms_neighbor_atom_keys(gra)

    def _neighbor_symbols(key):
        return sorted(map(atm_symb_dct.__getitem__, atm_ngbs_dct[key]))

    # 1. Find atoms with the same symbols
    atm_symb = atm_symb_dct[atm_key]
    cand_keys = atom_keys(gra, sym=atm_symb)

    # 2. Of those, find atoms with the same neighboring atom types
    atm_ngb_symbs = _neighbor_symbols(atm_key)
    cand_keys = [k for k in cand_keys if _neighbor_symbols(k) == atm_ngb_symbs]

    # 3. Find the equivalent atoms from the list of candidates.
    # Strategy: Change the atom symbol to 'Ts' and check for isomorphism.
    # Assumes none of the compounds have element 117.
    ref_gra = set_atom_symbols(gra, {atm_key: 'Ts'})
    atm_keys = []
    for key in cand_keys:
        comp_gra = set_atom_symbols(gra, {key: 'Ts'})
        if isomorphism(ref_gra, comp_gra, stereo=stereo, dummy=dummy):
            atm_keys.append(key)

    return frozenset(atm_keys)
예제 #25
0
def _atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct):
    """ get the current stereo parity of an atom from its geometry
    """
    atm_ngb_keys_dct = atoms_neighbor_atom_keys(gra)
    atm_ngb_keys = atm_ngb_keys_dct[atm_key]

    # sort the neighbor keys by stereo priority
    atm_ngb_keys = atom_stereo_sorted_neighbor_atom_keys(
        gra, atm_key, atm_ngb_keys)

    # determine the parity based on the coordinates
    xyzs = coordinates(geo)
    atm_ngb_idxs = dict_.values_by_key(geo_idx_dct, atm_ngb_keys)
    atm_ngb_xyzs = [xyzs[idx] for idx in atm_ngb_idxs]
    det_mat = numpy.ones((4, 4))
    det_mat[:, :3] = atm_ngb_xyzs
    det_val = numpy.linalg.det(det_mat)
    assert det_val != 0.  # for now, assume no four-atom planes
    par = det_val > 0.
    return par
예제 #26
0
def _extend_chain_to_include_terminal_hydrogens(gra, keys,
                                                start=True, end=True):
    """ extend each end of a chain to include terminal hydrogens, if any
    """
    symb_dct = atom_symbols(gra)
    atm_ngb_dct = atoms_neighbor_atom_keys(gra)

    sta_ngbs = atm_ngb_dct[keys[0]] - {keys[1]}
    end_ngbs = atm_ngb_dct[keys[-1]] - {keys[-2]}

    sta_ngb = min((k for k in sta_ngbs if symb_dct[k] == 'H'), default=None)
    end_ngb = min((k for k in end_ngbs if symb_dct[k] == 'H'), default=None)

    keys = tuple(keys)

    if start and sta_ngb is not None:
        keys = (sta_ngb,) + keys

    if end and end_ngb is not None:
        keys = keys + (end_ngb,)

    return keys
예제 #27
0
def neighbors_of_type(gra, aidx, symb):
    """ For a given atom, determine the indices of all the atoms
        which neighbor it that are of the type specified.

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param aidx: index of atom for which to find neighbors
        :type aidx: int
        :param symb: symbols of desired atom types for neighbors
        :type symb: str
    """

    idx_symb_dct = atom_symbols(gra)
    neighs = atoms_neighbor_atom_keys(gra)[aidx]
    neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct)

    idxs_of_type = tuple()
    for nidx, nsymb in zip(neighs, neigh_symbs):
        if nsymb == symb:
            idxs_of_type += (nidx, )

    return idxs_of_type
예제 #28
0
def planarity_constraint_bounds(gra, keys):
    """ bounds for enforcing planarity restrictions
    """
    ngb_key_dct = atoms_neighbor_atom_keys(gra)
    ngb_dct = bond_neighborhoods(gra)
    bnd_keys = [bnd_key for bnd_key in sp2_bond_keys(gra)
                if atom_keys(ngb_dct[bnd_key]) <= set(keys)]

    def _planarity_constraints(bnd_key):
        key1, key2 = sorted(bnd_key)
        key1ab = sorted(ngb_key_dct[key1] - {key2})
        key2ab = sorted(ngb_key_dct[key2] - {key1})

        lst = []

        # I don't think the order of the keys matters, but I tried to be
        # roughly consistent with Figure 8 in the Blaney Dixon paper
        if len(key1ab) == 2 and len(key2ab) == 2:
            lst.append(tuple(map(keys.index, key1ab + key2ab)))
        if len(key1ab) == 2:
            lst.append(tuple(map(keys.index, [key1, key2] + key1ab)))
        if len(key2ab) == 2:
            lst.append(tuple(map(keys.index, [key1, key2] + key2ab)))
        if (len(key1ab) == 2 and len(key2ab) == 1) or (
                len(key1ab) == 1 and len(key2ab) == 2):
            lst.append(tuple(map(keys.index, [key1] + key1ab + key2ab)))
            lst.append(tuple(map(keys.index, [key2] + key1ab + key2ab)))
        if len(key1ab) == 1 and len(key2ab) == 1:
            lst.append(tuple(map(keys.index, [key1, key2] + key1ab + key2ab)))

        return tuple(lst)

    const_dct = {
        idxs: (-0.5, +0.5) for idxs in
        itertools.chain(*map(_planarity_constraints, bnd_keys))}

    return const_dct
예제 #29
0
def two_bond_idxs(gra, symb1, cent, symb2):
    """ Determine the triplet of indices of atoms of specified
        types that are connected in a chain by two bonds:
        (symb1_idx, cent_idx, symb2_idx).

        :param gra: molecular graph
        :type gra: molecular graph data structure
        :param symb1: symbol of atom at one end of chain
        :type symb1: str
        :param cent: symbol of atom in the middle of a chain
        :type cent: str
        :param symb2: symbol of atom at other end of chain
        :type symb2: str
    """

    grps = tuple()

    neigh_dct = atoms_neighbor_atom_keys(gra)
    idx_symb_dct = atom_symbols(gra)
    symb_idx_dct = atom_symbol_idxs(gra)

    cent_idxs = symb_idx_dct.get(cent, tuple())
    for cent_idx in cent_idxs:
        neighs = tuple(neigh_dct[cent_idx])
        neigh_symbs = atom_idx_to_symb(neighs, idx_symb_dct)
        if neigh_symbs == (symb1, symb2):
            grp_idxs = (neighs[0], cent_idx, neighs[1])
        elif neigh_symbs == (symb2, symb1):
            grp_idxs = (neighs[1], cent_idx, neighs[0])
        else:
            grp_idxs = ()

        if grp_idxs:
            grps += ((grp_idxs), )

    return grps
예제 #30
0
파일: _stereo.py 프로젝트: sjklipp/autochem
def from_index_based_stereo(sgr):
    """ Convert a graph from index-based stereo assignments back to absolute
    stereo assignments, where parities are independent of atom ordering.

    :param sgr: a graph with index-based stereo assignments
    :returns: a graph with absolute stereo assignments
    """
    assert sgr == explicit(sgr), ("Not an explicit graph:\n{}".format(
        string(sgr, one_indexed=False)))

    gra = without_stereo_parities(sgr)

    if has_stereo(sgr):
        atm_keys_pool = atom_stereo_keys(sgr)
        bnd_keys_pool = bond_stereo_keys(sgr)

        idx_atm_ste_par_dct = atom_stereo_parities(sgr)
        idx_bnd_ste_par_dct = bond_stereo_parities(sgr)

        atm_ngb_keys_dct = atoms_neighbor_atom_keys(sgr)

        atm_keys = set()
        bnd_keys = set()

        last_gra = None

        # Do the assignments iteratively to handle higher-order stereo
        while last_gra != gra:
            last_gra = gra

            abs_atm_ste_par_dct = {}
            abs_bnd_ste_par_dct = {}

            atm_keys.update(stereogenic_atom_keys(gra) & atm_keys_pool)
            bnd_keys.update(stereogenic_bond_keys(gra) & bnd_keys_pool)

            # Determine absolute stereo assignments for atoms
            for atm_key in atm_keys:
                abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys(
                    gra, atm_key, atm_ngb_keys_dct[atm_key])
                idx_srt_keys = sorted(abs_srt_keys)

                if automol.util.is_even_permutation(idx_srt_keys,
                                                    abs_srt_keys):
                    abs_atm_ste_par_dct[atm_key] = (
                        idx_atm_ste_par_dct[atm_key])
                else:
                    abs_atm_ste_par_dct[atm_key] = (
                        not idx_atm_ste_par_dct[atm_key])

            # Determine absolute stereo assignments for bonds
            for bnd_key in bnd_keys:
                atm1_key, atm2_key = sorted(bnd_key)

                atm1_abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys(
                    gra, atm1_key, atm_ngb_keys_dct[atm1_key] - bnd_key)
                atm2_abs_srt_keys = atom_stereo_sorted_neighbor_atom_keys(
                    gra, atm2_key, atm_ngb_keys_dct[atm2_key] - bnd_key)
                atm1_idx_srt_keys = sorted(atm1_abs_srt_keys)
                atm2_idx_srt_keys = sorted(atm2_abs_srt_keys)

                if not ((atm1_idx_srt_keys[0] != atm1_abs_srt_keys[0]) ^
                        (atm2_idx_srt_keys[0] != atm2_abs_srt_keys[0])):
                    abs_bnd_ste_par_dct[bnd_key] = (
                        idx_bnd_ste_par_dct[bnd_key])
                else:
                    abs_bnd_ste_par_dct[bnd_key] = (
                        not idx_bnd_ste_par_dct[bnd_key])

            gra = set_atom_stereo_parities(gra, abs_atm_ste_par_dct)
            gra = set_bond_stereo_parities(gra, abs_bnd_ste_par_dct)

    atm_ste_keys = atom_stereo_keys(gra)
    bnd_ste_keys = bond_stereo_keys(gra)
    assert atm_ste_keys == atm_keys_pool, (
        "Index-based to absolute stereo conversion failed:\n"
        "{} != {}".format(str(atm_ste_keys), str(atm_keys_pool)))
    assert bnd_ste_keys == bnd_keys_pool, (
        "Index-based to absolute stereo conversion failed:\n"
        "{} != {}".format(str(bnd_ste_keys), str(bnd_keys_pool)))

    return gra