Esempio n. 1
0
def is_stereo_compatible(tra, sgr1, sgr2):
    """ is this transformation compatible with the reactant/product stereo
    assignments?
    """
    cgr1 = without_stereo_parities(sgr1)
    cgr2 = without_stereo_parities(sgr2)
    atm_key_dct = _full_isomorphism(apply(tra, cgr1), cgr2)

    # determine the stereo centers which are preserved in the transformation
    sgr1 = _relabel(sgr1, atm_key_dct)
    atm_keys = sorted(atom_stereo_keys(sgr1) & atom_stereo_keys(sgr2))
    bnd_keys = sorted(bond_stereo_keys(sgr1) & bond_stereo_keys(sgr2))

    atm_pars1 = dict_.values_by_key(atom_stereo_parities(sgr1), atm_keys)
    atm_pars2 = dict_.values_by_key(atom_stereo_parities(sgr2), atm_keys)
    bnd_pars1 = dict_.values_by_key(bond_stereo_parities(sgr1), bnd_keys)
    bnd_pars2 = dict_.values_by_key(bond_stereo_parities(sgr2), bnd_keys)

    atm_ngb_keys_dct1 = atom_neighbor_keys(sgr1)
    atm_ngb_keys_dct2 = atom_neighbor_keys(sgr2)

    ret = True

    for atm_key, par1, par2 in zip(atm_keys, atm_pars1, atm_pars2):
        atm_ngb_keys1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm_key, atm_ngb_keys_dct1[atm_key])
        atm_ngb_keys2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm_key, atm_ngb_keys_dct2[atm_key])

        if _permutation_parity(atm_ngb_keys1, atm_ngb_keys2):
            ret &= (par1 == par2)
        else:
            ret &= (par1 != par2)

    for bnd_key, par1, par2 in zip(bnd_keys, bnd_pars1, bnd_pars2):
        atm1_key, atm2_key = bnd_key

        atm1_ngb_key1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm1_key, atm_ngb_keys_dct1[atm1_key] - {atm2_key})[0]
        atm2_ngb_key1 = stereo_sorted_atom_neighbor_keys(
            sgr1, atm2_key, atm_ngb_keys_dct1[atm2_key] - {atm1_key})[0]
        atm1_ngb_key2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm1_key, atm_ngb_keys_dct2[atm1_key] - {atm2_key})[0]
        atm2_ngb_key2 = stereo_sorted_atom_neighbor_keys(
            sgr2, atm2_key, atm_ngb_keys_dct2[atm2_key] - {atm1_key})[0]

        if not ((atm1_ngb_key1 != atm1_ngb_key2) ^
                (atm2_ngb_key1 != atm2_ngb_key2)):
            ret &= (par1 == par2)
        else:
            ret &= (par1 != par2)

    return ret
Esempio n. 2
0
 def _is_compatible(sgr):
     atm_ste_par_dct = _assigned(atom_stereo_parities(sgr))
     bnd_ste_par_dct = _assigned(bond_stereo_parities(sgr))
     _compat_atm_assgns = (set(known_atm_ste_par_dct.items()) <= set(
         atm_ste_par_dct.items()))
     _compat_bnd_assgns = (set(known_bnd_ste_par_dct.items()) <= set(
         bnd_ste_par_dct.items()))
     return _compat_atm_assgns and _compat_bnd_assgns
Esempio n. 3
0
def compatible_reverse_stereomers(ste_tsg):
    """ Given a TS graph with stereo assignments, expand all possible reverse
    graphs compatble with the forward graph.

    :param ste_tsg: The TS graph, with stereo assignments.
    :returns: All possible reverse TS graphs.
    """
    frm_bnd_keys = forming_bond_keys(ste_tsg)
    brk_bnd_keys = breaking_bond_keys(ste_tsg)
    _, des_ste_atm_keys = nonconserved_atom_stereo_keys(ste_tsg)
    _, des_ste_bnd_keys = nonconserved_bond_stereo_keys(ste_tsg)
    cons_atm_keys = sorted(atom_stereo_keys(ste_tsg) - des_ste_atm_keys)
    cons_bnd_keys = sorted(bond_stereo_keys(ste_tsg) - des_ste_bnd_keys)

    # 1. Determine index-based stereo assignments for conserved stereo centers
    idx_tsg = to_index_based_stereo(ste_tsg)
    cons_idx_atm_pars = dict_.values_by_key(
        atom_stereo_parities(idx_tsg), cons_atm_keys)
    cons_idx_bnd_pars = dict_.values_by_key(
        bond_stereo_parities(idx_tsg), cons_bnd_keys)

    # 2. Determine all possible index-based stereo assignments for the reverse
    #    reaction.
    prds_gra = without_stereo_parities(products_graph(ste_tsg))
    prds_sgrs = _stereomers(prds_gra)
    prds_idx_sgrs = list(map(_to_index_based_stereo, prds_sgrs))
    rev_idx_tsgs_pool = [
        graph(p, brk_bnd_keys, frm_bnd_keys) for p in prds_idx_sgrs]

    # 3. Find possibilities which match the assignments for the conserved
    #    stereo centers.
    rev_idx_tsgs = []
    for rev_idx_tsg in rev_idx_tsgs_pool:
        rev_cons_idx_atm_pars = dict_.values_by_key(
            atom_stereo_parities(rev_idx_tsg), cons_atm_keys)
        rev_cons_idx_bnd_pars = dict_.values_by_key(
            bond_stereo_parities(rev_idx_tsg), cons_bnd_keys)
        if (rev_cons_idx_atm_pars == cons_idx_atm_pars and
                rev_cons_idx_bnd_pars == cons_idx_bnd_pars):
            rev_idx_tsgs.append(rev_idx_tsg)

    # 4. Convert the matching reverse graphs back from index-based stereo
    #    assignments to absolute stereo assignments.
    rev_ste_tsgs = list(map(from_index_based_stereo, rev_idx_tsgs))
    return rev_ste_tsgs
Esempio n. 4
0
def to_index_based_stereo(sgr):
    """ Convert a graph to index-based stereo assignments, where parities are
    defined relative to the ordering of indices rather than the absolute stereo
    priority.

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

    abs_srt_keys_dct = atoms_stereo_sorted_neighbor_atom_keys(sgr)
    atm_ste_keys = atom_stereo_keys(sgr)
    bnd_ste_keys = bond_stereo_keys(sgr)

    abs_atm_ste_par_dct = atom_stereo_parities(sgr)
    abs_bnd_ste_par_dct = bond_stereo_parities(sgr)

    idx_atm_ste_par_dct = {}
    idx_bnd_ste_par_dct = {}

    # Determine index-based stereo assignments for atoms
    for atm_key in atm_ste_keys:
        abs_srt_keys = abs_srt_keys_dct[atm_key]
        idx_srt_keys = sorted(abs_srt_keys)

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

    # Determine index-based stereo assignments for bonds
    for bnd_key in bnd_ste_keys:
        atm1_key, atm2_key = sorted(bnd_key)

        atm1_abs_srt_keys = abs_srt_keys_dct[atm1_key]
        atm2_abs_srt_keys = abs_srt_keys_dct[atm2_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])):
            idx_bnd_ste_par_dct[bnd_key] = abs_bnd_ste_par_dct[bnd_key]
        else:
            idx_bnd_ste_par_dct[bnd_key] = not abs_bnd_ste_par_dct[bnd_key]

    sgr = set_atom_stereo_parities(sgr, idx_atm_ste_par_dct)
    sgr = set_bond_stereo_parities(sgr, idx_bnd_ste_par_dct)
    return sgr
Esempio n. 5
0
def from_graph(gra):
    """ networkx graph object from a molecular graph
    """
    nxg = networkx.Graph()
    nxg.add_nodes_from(atom_keys(gra))
    nxg.add_edges_from(bond_keys(gra))
    networkx.set_node_attributes(nxg, atom_symbols(gra), 'symbol')
    networkx.set_node_attributes(nxg, atom_implicit_hydrogen_valences(gra),
                                 'implicit_hydrogen_valence')
    networkx.set_node_attributes(nxg, atom_stereo_parities(gra),
                                 'stereo_parity')
    networkx.set_edge_attributes(nxg, bond_orders(gra), 'order')
    networkx.set_edge_attributes(nxg, bond_stereo_parities(gra),
                                 'stereo_parity')

    return nxg
Esempio n. 6
0
def substereomers(gra):
    """ all stereomers compatible with this graph's assignments
    """
    _assigned = functools.partial(dict_.filter_by_value,
                                  func=lambda x: x is not None)

    known_atm_ste_par_dct = _assigned(atom_stereo_parities(gra))
    known_bnd_ste_par_dct = _assigned(bond_stereo_parities(gra))

    def _is_compatible(sgr):
        atm_ste_par_dct = _assigned(atom_stereo_parities(sgr))
        bnd_ste_par_dct = _assigned(bond_stereo_parities(sgr))
        _compat_atm_assgns = (set(known_atm_ste_par_dct.items()) <= set(
            atm_ste_par_dct.items()))
        _compat_bnd_assgns = (set(known_bnd_ste_par_dct.items()) <= set(
            bnd_ste_par_dct.items()))
        return _compat_atm_assgns and _compat_bnd_assgns

    sgrs = tuple(filter(_is_compatible, stereomers(gra)))
    return sgrs
Esempio n. 7
0
def _stereo_corrected_geometry(sgr, geo, geo_idx_dct):
    """ correct the stereo parities of a geometry

    (works iterately to handle cases of higher-order stereo)
    """
    assert sgr == explicit(sgr)
    gra = without_stereo_parities(sgr)

    if has_stereo(sgr):
        full_atm_ste_par_dct = atom_stereo_parities(sgr)
        full_bnd_ste_par_dct = bond_stereo_parities(sgr)

        atm_keys = set()
        bnd_keys = set()

        last_gra = None

        while last_gra != gra:
            last_gra = gra

            atm_keys.update(stereogenic_atom_keys(gra))
            bnd_keys.update(stereogenic_bond_keys(gra))

            atm_ste_par_dct = {
                atm_key: full_atm_ste_par_dct[atm_key]
                for atm_key in atm_keys
            }
            bnd_ste_par_dct = {
                bnd_key: full_bnd_ste_par_dct[bnd_key]
                for bnd_key in bnd_keys
            }
            geo, gra = _atom_stereo_corrected_geometry(gra, atm_ste_par_dct,
                                                       geo, geo_idx_dct)
            geo, gra = _bond_stereo_corrected_geometry(gra, bnd_ste_par_dct,
                                                       geo, geo_idx_dct)

    return geo
Esempio n. 8
0
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
Esempio n. 9
0
def path_distance_bounds_(gra):
    """ upper distance bound between two ends of a path

    :param gra: molecular graph
    :param path: the shortest path between two atoms
    :type path: list or tuple
    """

    hyb_dct = resonance_dominant_atom_hybridizations(gra)
    rng_keys_lst = rings_atom_keys(gra)
    atm_ngb_keys = atoms_neighbor_atom_keys(gra)

    ste_bnd_keys = bond_stereo_keys(gra)
    bnd_par_dct = bond_stereo_parities(gra)

    def _distance_bounds(path):
        # if the path is 0, the atoms are disconnected and could be arbitrarily
        # far apart
        rsz = shared_ring_size(path, rng_keys_lst)
        if len(path) == 1:
            ldist = udist = 0
        elif len(path) == 2:
            ldist = udist = heuristic_bond_distance(gra, *path)
        elif len(path) == 3:
            if rsz == 0:
                ldist = udist = heuristic_bond_angle_distance(
                    gra, *path, hyb_dct=hyb_dct)
            else:
                a123 = (rsz - 2.) * 180. / rsz
                la123 = a123 - 10.
                ua123 = a123 + 10.
                lrdist = heuristic_bond_angle_distance(gra, *path, a123=la123)
                urdist = heuristic_bond_angle_distance(gra, *path, a123=ua123)
                odist = heuristic_bond_angle_distance(
                    gra, *path, hyb_dct=hyb_dct)
                ldist = min(lrdist, odist)
                udist = max(urdist, odist)
        elif len(path) == 4:
            if rsz == 0:
                key2, key3 = path[1:3]
                bnd_key23 = frozenset({key2, key3})
                # handle bond stereo here
                if bnd_key23 in ste_bnd_keys:
                    key2_ngbs = atom_stereo_sorted_neighbor_atom_keys(
                        gra, key2, atm_ngb_keys[key2]-{key3})
                    key3_ngbs = atom_stereo_sorted_neighbor_atom_keys(
                        gra, key3, atm_ngb_keys[key3]-{key2})
                    pos2 = key2_ngbs.index(path[0])
                    pos3 = key3_ngbs.index(path[-1])
                    cis = bnd_par_dct[bnd_key23] != (pos2 != pos3)
                    dih = 0. if cis else 180.
                    ldist = udist = heuristic_torsion_angle_distance(
                        gra, *path, d1234=dih, degree=True, hyb_dct=hyb_dct)
                else:
                    ldist = heuristic_torsion_angle_distance(
                        gra, *path, d1234=0., degree=True, hyb_dct=hyb_dct)
                    udist = heuristic_torsion_angle_distance(
                        gra, *path, d1234=180., degree=True, hyb_dct=hyb_dct)
            else:
                ang = (rsz - 2.) * 180. / rsz
                rdist = heuristic_torsion_angle_distance(
                    gra, *path, d1234=0., degree=True, a123=ang, a234=ang)
                cdist = heuristic_torsion_angle_distance(
                    gra, *path, d1234=0., degree=True, hyb_dct=hyb_dct)
                tdist = heuristic_torsion_angle_distance(
                    gra, *path, d1234=180., degree=True, hyb_dct=hyb_dct)
                ldist = min(rdist, cdist)
                udist = max(rdist, tdist)
        # otherwise, just do the sum of the distances between atoms along the
        # path
        else:
            # we can't handle disconnected points, because in that case the
            # path is [] and there is no way to recover the keys
            assert len(path) > 2
            ldist = closest_approach(gra, path[0], path[-1])
            udist = 999.

        return ldist, udist

    return _distance_bounds
Esempio n. 10
0
def qualitative_convergence_checker_(gra, keys, rqq_bond_max=1.8,
                                     rqh_bond_max=1.3, rhh_bond_max=1.1,
                                     bond_nobond_diff=0.3):
    """ a convergence checker for error minimization, checking that the
    geometry is qualitatively correct (correct connectivity and stereo)
    """
    symb_dct = atom_symbols(gra)
    pairs = set(map(frozenset, itertools.combinations(keys, 2)))

    bnd_keys = pairs & bond_keys(gra)
    nob_keys = pairs - bond_keys(gra)

    nob_symbs = tuple(tuple(map(symb_dct.__getitem__, nob_key))
                      for nob_key in nob_keys)
    bnd_symbs = tuple(tuple(map(symb_dct.__getitem__, bnd_key))
                      for bnd_key in bnd_keys)
    nob_idxs = tuple(tuple(map(keys.index, nob_key)) for nob_key in nob_keys)
    bnd_idxs = tuple(tuple(map(keys.index, bnd_key)) for bnd_key in bnd_keys)

    bnd_udists = tuple((rqq_bond_max if 'H' not in symb else
                        rhh_bond_max if set(symb) == {'H'} else
                        rqh_bond_max) for symb in bnd_symbs)

    diff = bond_nobond_diff
    nob_ldists = tuple((rqq_bond_max+diff if 'H' not in symb else
                        rhh_bond_max+diff if set(symb) == {'H'} else
                        rqh_bond_max+diff) for symb in nob_symbs)

    bnd_idxs += tuple(map(tuple, map(reversed, bnd_idxs)))
    bnd_idx_vecs = tuple(map(list, zip(*bnd_idxs)))
    bnd_udists *= 2

    nob_idxs += tuple(map(tuple, map(reversed, nob_idxs)))
    nob_idx_vecs = tuple(map(list, zip(*nob_idxs)))
    nob_ldists *= 2

    symbs = tuple(map(symb_dct.__getitem__, keys))
    geo_idx_dct = dict(map(reversed, enumerate(keys)))
    atm_ste_keys = atom_stereo_keys(gra) & set(keys)
    bnd_ste_keys = bond_stereo_keys(gra) & bnd_keys
    atm_ste_par_dct = atom_stereo_parities(gra)
    bnd_ste_par_dct = bond_stereo_parities(gra)

    def _is_converged(xmat, err, grad):
        assert err and numpy.any(grad)
        xyzs = xmat[:, :3]
        dmat = embed.distance_matrix_from_coordinates(xyzs)

        # check for correct connectivity
        connectivity_check = (
            (numpy.all(dmat[bnd_idx_vecs] < bnd_udists)
             if bnd_udists else True) and
            (numpy.all(dmat[nob_idx_vecs] > nob_ldists)
             if nob_ldists else True))

        # check for correct stereo parities
        geo = _create.geom.from_data(symbs, xyzs, angstrom=True)
        atom_stereo_check = all(
            (_atom_stereo_parity_from_geometry(gra, atm_key, geo, geo_idx_dct)
             == atm_ste_par_dct[atm_key])
            for atm_key in atm_ste_keys)

        bond_stereo_check = all(
            (_bond_stereo_parity_from_geometry(gra, bnd_key, geo, geo_idx_dct)
             == bnd_ste_par_dct[bnd_key])
            for bnd_key in bnd_ste_keys)

        return connectivity_check and atom_stereo_check and bond_stereo_check

    return _is_converged