Example #1
0
def _extend_chain_to_include_anchoring_atoms(gra, keys, zma_keys):
    """ extend chain to include three atoms already specified in v-matrix

    :param gra: the graph
    :param keys: keys in the chain; the first atom should already be specified
    :param zma_keys: keys currently in the v-matrix
    """
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ))

    symb_dct = atom_symbols(gra)

    key3 = keys[0]
    assert key3 in zma_keys
    key2 = next(k for k in ngb_keys_dct[key3] if k in zma_keys)

    if symb_dct[key2] == 'X':
        key1 = next(k for k in ngb_keys_dct[key3][1:] if k in zma_keys)
    else:
        key1 = next(k for k in ngb_keys_dct[key2]
                    if k in zma_keys and k != key3)

    keys = (
        key1,
        key2,
    ) + tuple(keys)

    return keys
Example #2
0
def start_at(gra, key):
    """ start a v-matrix at a specific atom

    Returns the started vmatrix, along with keys to atoms whose neighbors are
    missing from it
    """
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ))

    ngb_keys = ngb_keys_dct[key]
    if not ngb_keys:
        zma_keys = []
    elif len(ngb_keys) == 1:
        # Need special handling for atoms with only one neighbor
        if symb_dct[key] in ('H', 'X'):
            key2 = ngb_keys[0]
            zma_keys = (key2, ) + ngb_keys_dct[key2]
        else:
            key2 = ngb_keys[0]
            ngb_keys = tuple(k for k in ngb_keys_dct[key2] if k != key)
            zma_keys = (key, key2) + ngb_keys
    else:
        zma_keys = (key, ) + ngb_keys_dct[key]

    vma = ()
    for row, key_ in enumerate(zma_keys):
        idx1 = idx2 = idx3 = None
        if row > 0:
            key1 = next(k for k in ngb_keys_dct[key_] if k in zma_keys[:row])
            idx1 = zma_keys.index(key1)
        if row > 1:
            key2 = next(k for k in ngb_keys_dct[key1]
                        if k in zma_keys[:row] and k != key_)
            idx2 = zma_keys.index(key2)
        if row > 2:
            key3 = next(k for k in zma_keys[:row]
                        if k not in (key_, key1, key2))
            idx3 = zma_keys.index(key3)

        sym = symb_dct[key_]
        key_row = [idx1, idx2, idx3]
        vma = automol.vmat.add_atom(vma, sym, key_row)

    return vma, zma_keys
Example #3
0
def complete_branch(gra, key, vma, zma_keys, branch_keys=None):
    """ continue constructing a v-matrix along a chain

    All neighboring atoms along the chain will be included

    Exactly one atom in the chain must already be in the v-matrix

    :param gra: the graph for which the v-matrix will be constructed
    :param keys: the keys for atoms along the chain, which must be contiguous;
        the first atom must already appear in the v-matrix
    :param vma: a partial v-matrix from which to continue
    :param zma_keys: row keys for the partial v-matrix, identifying the atom
        specified by each row of `vma` in order
    :param branch_keys: optionally, restrict the v-matrix to these keys and
        their neighbors; if `None`, the entire branch will be included
    """
    branch_keys = atom_keys(gra) if branch_keys is None else branch_keys
    keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys)

    zma_keys = list(zma_keys)
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(gra,
                                                   symbs_first=(
                                                       'X',
                                                       'C',
                                                   ),
                                                   symbs_last=('H', ),
                                                   ords_last=(0.1, ),
                                                   prioritize_keys=branch_keys)

    # If this z-matrix is being continued from a partial z-matrix, the leading
    # atom for a torsion may have already be defined. To handle this case, I
    # make a dictionary of these leading atoms and use them below where needed.
    lead_key_dct = {}
    for idx, key_row in enumerate(automol.vmat.key_matrix(vma)):
        axis = key_row[:2]
        if None not in axis:
            axis = tuple(map(zma_keys.__getitem__, axis))
            if axis not in lead_key_dct:
                lead_key_dct[axis] = zma_keys[idx]

    def _continue(key1, key2, key3, vma, zma_keys):
        k3ns = list(ngb_keys_dct[key3])
        for k3n in set(k3ns) & set(zma_keys):
            k3ns.remove(k3n)

        if k3ns:
            key4 = k3ns.pop(0)

            lead_key = None
            if (key3, key2) in lead_key_dct:
                lead_key = lead_key_dct[(key3, key2)]

            # Add the leading atom to the v-matrix
            symb = symb_dct[key4]
            dkey = key1 if lead_key is None else lead_key
            key_row = list(map(zma_keys.index, (key3, key2, dkey)))
            vma = automol.vmat.add_atom(vma, symb, key_row)
            assert key4 not in zma_keys, (
                "Atom {:d} already in v-matrix.".format(key4))
            zma_keys.append(key4)

            dkey = key4 if lead_key is None else lead_key
            # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled
            # from atom 1 for properly decopuled torsions
            for k3n in k3ns:
                sym = symb_dct[k3n]

                if symb_dct[dkey] == 'X':
                    key_row = list(map(zma_keys.index, (key3, dkey, key2)))
                else:
                    key_row = list(map(zma_keys.index, (key3, key2, dkey)))

                vma = automol.vmat.add_atom(vma, sym, key_row)
                assert k3n not in zma_keys, (
                    "Atom {:d} already in v-matrix.".format(k3n))
                zma_keys.append(k3n)

            # Recursion
            if key4 in branch_keys:
                vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys)

            if symb_dct[key4] == 'X':
                key2 = key4

            for k3n in k3ns:
                if k3n in branch_keys:
                    vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys)

        return vma, zma_keys

    key1, key2, key3 = keys[:3]
    vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys)

    return vma, zma_keys
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(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 = 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
Example #5
0
def complete_branch(gra, key, vma, zma_keys, branch_keys=None):
    """ continue constructing a v-matrix along a chain

    All neighboring atoms along the chain will be included

    Exactly one atom in the chain must already be in the v-matrix

    :param gra: the graph for which the v-matrix will be constructed
    :param vma: a partial v-matrix from which to continue
    :param zma_keys: row keys for the partial v-matrix, identifying the atom
        specified by each row of `vma` in order
    :param branch_keys: optionally, restrict the v-matrix to these keys and
        their neighbors; if `None`, the entire branch will be included
    """
    branch_keys = atom_keys(gra) if branch_keys is None else branch_keys
    keys = _extend_chain_to_include_anchoring_atoms(gra, [key], zma_keys)

    zma_keys = list(zma_keys)
    symb_dct = atom_symbols(gra)
    ngb_keys_dct = atoms_sorted_neighbor_atom_keys(
        gra, symbs_first=('X', 'C',), symbs_last=('H',), ords_last=(0.1,))

    def _continue(key1, key2, key3, vma, zma_keys):
        k3ns = list(ngb_keys_dct[key3])
        for k3n in set(k3ns) & set(zma_keys):
            k3ns.remove(k3n)

        if k3ns:
            key4 = k3ns.pop(0)

            # Add the leading atom to the v-matrix
            symb = symb_dct[key4]
            key_row = list(map(zma_keys.index, (key3, key2, key1)))
            vma = automol.vmat.add_atom(vma, symb, key_row)
            assert key4 not in zma_keys, ("Atom {:d} already in v-matrix."
                                          .format(key4))
            zma_keys.append(key4)

            # Add the neighbors of atom 3 (if any) to the v-matrix, decoupled
            # from atom 1 for properly decopuled torsions
            for k3n in k3ns:
                sym = symb_dct[k3n]

                if symb_dct[key4] == 'X':
                    key_row = list(map(zma_keys.index, (key3, key4, key2)))
                else:
                    key_row = list(map(zma_keys.index, (key3, key2, key4)))

                vma = automol.vmat.add_atom(vma, sym, key_row)
                assert k3n not in zma_keys, ("Atom {:d} already in v-matrix."
                                             .format(k3n))
                zma_keys.append(k3n)

            # Recursion
            if key4 in branch_keys:
                vma, zma_keys = _continue(key2, key3, key4, vma, zma_keys)

            if symb_dct[key4] == 'X':
                key2 = key4

            for k3n in k3ns:
                if k3n in branch_keys:
                    vma, zma_keys = _continue(key2, key3, k3n, vma, zma_keys)

        return vma, zma_keys

    key1, key2, key3 = keys[:3]
    vma, zma_keys = _continue(key1, key2, key3, vma, zma_keys)

    return vma, zma_keys