Example #1
0
def bond_equivalence_class_reps(gra, bnd_keys=None, stereo=True, dummy=True):
    """ Identify isomorphically unique bonds, which do not transform into each
    other by an automorphism

    Optionally, a subset of bonds can be passed in to consider class
    representatives from within that list.

    :param gra: A graph
    :param bnd_keys: An optional list of bond keys from which to determine
        equivalence class representatives. If None, the full set of bond keys
        will be used.
    :param stereo: Consider stereo?
    :type stereo: bool
    :param dummy: Consider dummy atoms?
    :type dummy: bool
    :returns: The list of equivalence class reprentatives/unique bonds.
    :rtype: frozenset[int]
    """
    bnd_keys = bond_keys(gra) if bnd_keys is None else bnd_keys

    def _equiv(bnd1_key, bnd2_key):
        return are_equivalent_bonds(gra, bnd1_key, bnd2_key, stereo=stereo,
                                    dummy=dummy)

    eq_classes = util.equivalence_partition(bnd_keys, _equiv)
    class_reps = frozenset(next(iter(c)) for c in eq_classes)
    return class_reps
Example #2
0
def _hydrogen_layer(gra):
    """ AMChI hydrogen (h) layer from graph

        :param gra: implicit molecular graph
        :type gra: automol graph data structure
        :returns: the hydrogen layer, without prefix
        :rtype: str
    """
    # Determine hydrogen counts
    nhyd_dct = atom_implicit_hydrogen_valences(gra)
    all_keys = sorted(atom_keys(gra), key=nhyd_dct.__getitem__)
    grps = [(nh, sorted(k + 1 for k in ks))
            for nh, ks in itertools.groupby(all_keys, key=nhyd_dct.__getitem__)
            if nh > 0]

    # Build the hydrogen layer string
    slyrs = []
    for nhyd, keys in grps:
        parts = util.equivalence_partition(keys, lambda x, y: y in
                                           (x - 1, x + 1))
        parts = sorted(map(sorted, parts))
        strs = [
            '{:d}-{:d}'.format(min(p), max(p))
            if len(p) > 1 else '{:d}'.format(p[0]) for p in parts
        ]
        if nhyd == 1:
            slyrs.append(','.join(strs) + 'H')
        else:
            slyrs.append(','.join(strs) + f'H{nhyd}')

    nhyd_lyr = ','.join(slyrs)
    return nhyd_lyr
Example #3
0
def ring_systems_bond_keys(gra):
    """ bond keys for polycyclic ring systems in the graph
    """

    def _are_connected(bnd_keys1, bnd_keys2):
        """ see if two rings are connected based on their bond keys
        """
        atm_keys1 = functools.reduce(operator.or_, bnd_keys1)
        atm_keys2 = functools.reduce(operator.or_, bnd_keys2)
        common_bonds = set(bnd_keys1) & set(bnd_keys2)
        common_atoms = set(atm_keys1) & set(atm_keys2)
        return bool(common_bonds) or bool(common_atoms)

    rng_bnd_keys_lst = rings_bond_keys(gra)
    rsy_bnd_keys_lsts = util.equivalence_partition(rng_bnd_keys_lst,
                                                   _are_connected)
    rsy_bnd_keys_lst = [frozenset(functools.reduce(operator.or_, bnd_keys_lst))
                        for bnd_keys_lst in rsy_bnd_keys_lsts]
    return rsy_bnd_keys_lst
Example #4
0
def insert_dummies_on_linear_atoms(geo,
                                   lin_idxs=None,
                                   gra=None,
                                   dist=1.,
                                   tol=5.):
    """ Insert dummy atoms over linear atoms in the geometry.

        :param geo: the geometry
        :type geo: automol molecular geometry data structure
        :param lin_idxs: the indices of the linear atoms; if None, indices are
            automatically determined from the geometry based on the graph
        :type lin_idxs: tuple(int)
        :param gra: the graph describing connectivity; if None, a connectivity
            graph will be generated using default distance thresholds
        :type gra: automol molecular graph data structure
        :param dist: distance of dummy atom from the linear atom, in angstroms
        :type dist: float
        :param tol: the tolerance threshold for linearity, in degrees
        :type tol: float
        :returns: geometry with dummy atoms inserted, along with a dictionary
            mapping the linear atoms onto their associated dummy atoms
        :rtype: automol molecular geometry data structure
    """

    lin_idxs = linear_atoms(geo) if lin_idxs is None else lin_idxs
    gra = connectivity_graph(geo) if gra is None else gra

    dummy_ngb_idxs = set(
        automol.graph.dummy_atoms_neighbor_atom_key(gra).values())
    assert not dummy_ngb_idxs & set(lin_idxs), (
        "Attempting to add dummy atoms on atoms that already have them: {}".
        format(dummy_ngb_idxs & set(lin_idxs)))

    ngb_idxs_dct = automol.graph.atoms_sorted_neighbor_atom_keys(gra)

    xyzs = coordinates(geo, angstrom=True)

    def _perpendicular_direction(idxs):
        """ find a nice perpendicular direction for a series of linear atoms
        """
        triplets = []
        for idx in idxs:
            for n1idx in ngb_idxs_dct[idx]:
                for n2idx in ngb_idxs_dct[n1idx]:
                    if n2idx != idx:
                        ang = central_angle(geo,
                                            idx,
                                            n1idx,
                                            n2idx,
                                            degree=True)
                        if numpy.abs(ang - 180.) > tol:
                            triplets.append((idx, n1idx, n2idx))

        if triplets:
            idx1, idx2, idx3 = min(triplets, key=lambda x: x[1:])
            xyz1, xyz2, xyz3 = map(xyzs.__getitem__, (idx1, idx2, idx3))
            r12 = util.vec.unit_direction(xyz1, xyz2)
            r23 = util.vec.unit_direction(xyz2, xyz3)
            direc = util.vec.orthogonalize(r12, r23, normalize=True)
        else:
            if len(idxs) > 1:
                idx1, idx2 = idxs[:2]
            else:
                idx1, = idxs
                idx2, = ngb_idxs_dct[idx1]

            xyz1, xyz2 = map(xyzs.__getitem__, (idx1, idx2))
            r12 = util.vec.unit_direction(xyz1, xyz2)
            for i in range(3):
                disp = numpy.zeros((3, ))
                disp[i] = -1.
                alt = numpy.add(r12, disp)
                direc = util.vec.unit_perpendicular(r12, alt)
                if numpy.linalg.norm(direc) > 1e-2:
                    break

        return direc

    # partition the linear atoms into adjacent groups, to be handled together
    lin_idxs_lst = sorted(
        map(
            sorted,
            util.equivalence_partition(lin_idxs,
                                       lambda x, y: x in ngb_idxs_dct[y])))

    dummy_key_dct = {}

    for idxs in lin_idxs_lst:
        direc = _perpendicular_direction(idxs)
        for idx in idxs:
            xyz = numpy.add(xyzs[idx], numpy.multiply(dist, direc))
            dummy_key_dct[idx] = count(geo)

            geo = insert(geo, 'X', xyz, angstrom=True)

    return geo, dummy_key_dct