Esempio n. 1
0
def bend(xyz, *inds, units='rad', absv=False):
    """Returns bending angle for 3 atoms in a chain based on index."""
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[1]])
    e2 = con.unit_vec(xyz[inds[2]] - xyz[inds[1]])

    coord = np.arccos(np.dot(e1, e2))
    return coord * con.conv('rad', units)
Esempio n. 2
0
def tors(xyz, *inds, units='rad', absv=False):
    """Returns dihedral angle for 4 atoms in a chain based on index.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    inds : list
        The indices for which the dihedral angle is measured.
    units : str, optional
        The units of angle for the output. Default is radians.
    absv : bool, optional
        Specifies if the absolute value is returned.

    Returns
    -------
    float
        The dihedral angle.
    """
    e1 = xyz[inds[0]] - xyz[inds[1]]
    e2 = xyz[inds[2]] - xyz[inds[1]]
    e3 = xyz[inds[2]] - xyz[inds[3]]

    # get normals to 3-atom planes
    cp1 = con.unit_vec(np.cross(e1, e2))
    cp2 = con.unit_vec(np.cross(e2, e3))

    if absv:
        coord = con.arccos(np.dot(cp1, cp2))
    else:
        # get cross product of plane normals for signed dihedral angle
        cp3 = np.cross(cp1, cp2)
        coord = np.sign(np.dot(cp3, e2)) * con.arccos(np.dot(cp1, cp2))

    return coord * con.conv('rad', units)
Esempio n. 3
0
def _parse_axis(inp):
    """Returns a numpy array based on a specified axis.

    Axis can be given as a string (e.g. 'x' or 'xy'), a vector
    or a set of 3 vectors. If the input defines a plane, the plane
    normal is returned.

    For instance, 'x', 'yz', [1, 0, 0] and [[0, 1, 0], [0, 0, 0], [0, 0, 1]]
    will all return [1, 0, 0].
    """
    if isinstance(inp, str):
        if inp in ['x', 'yz', 'zy']:
            return np.array([1., 0., 0.])
        elif inp in ['y', 'xz', 'zx']:
            return np.array([0., 1., 0.])
        elif inp in ['z', 'xy', 'yx']:
            return np.array([0., 0., 1.])
        elif inp == '-x':
            return np.array([-1., 0., 0.])
        elif inp == '-y':
            return np.array([0., -1., 0.])
        elif inp == '-z':
            return np.array([0., 0., -1.])
    elif len(inp) == 3:
        u = np.array(inp, dtype=float)
        if u.size == 9:
            unew = np.cross(u[0] - u[1], u[2] - u[1])
            return con.unit_vec(unew)
        else:
            return con.unit_vec(u)
    else:
        raise ValueError('Axis specification not recognized')
Esempio n. 4
0
    def __call__(self, inp, unit=False):
        """Evaluates an expression based on a string.

        Parameters
        ----------
        inp : str or array_like
            A string or array used to specify an axis.
        unit : bool, optional
            Specifies if the axis is converted to a unit vector.

        Returns
        -------
        float or ndarray
            The result of the vector operation.

        Raises
        ------
        ValueError
            If input is not a string, 3-vector or 3x3 array.
        """
        if isinstance(inp, str):
            u = self.expr.parseString(inp, parseAll=True)[0]
        elif len(inp) == 3:
            u = np.array(inp, dtype=float)
            if u.size == 9:
                u = np.cross(u[0] - u[1], u[2] - u[1])
        else:
            raise ValueError('Axis specification not recognized')

        if unit:
            return con.unit_vec(u)
        else:
            return u
Esempio n. 5
0
def edgetors(xyz, *inds, units='rad', absv=False):
    """Returns the torsional angle based on the vector difference of the
    two external atoms (1-2 and 5-6) to the central 3-4 bond."""
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[2]])
    e2 = con.unit_vec(xyz[inds[1]] - xyz[inds[2]])
    e3 = con.unit_vec(xyz[inds[3]] - xyz[inds[2]])
    e4 = con.unit_vec(xyz[inds[4]] - xyz[inds[3]])
    e5 = con.unit_vec(xyz[inds[5]] - xyz[inds[3]])

    # take the difference between unit vectors of external bonds
    e2 -= e1
    e5 -= e4

    # get cross products of difference vectors and the central bond
    cp1 = con.unit_vec(np.cross(e2, e3))
    cp2 = con.unit_vec(np.cross(e3, e5))

    if absv:
        coord = np.arccos(np.dot(cp1, cp2))
    else:
        # get cross product of vectors for signed dihedral angle
        cp3 = np.cross(cp1, cp2)
        coord = np.sign(np.dot(cp3, e3)) * np.arccos(np.dot(cp1, cp2))

    return coord * con.conv('rad', units)
Esempio n. 6
0
def tors(xyz, *inds, units='rad', absv=False):
    """Returns dihedral angle for 4 atoms in a chain based on index."""
    e1 = xyz[inds[0]] - xyz[inds[1]]
    e2 = xyz[inds[2]] - xyz[inds[1]]
    e3 = xyz[inds[2]] - xyz[inds[3]]

    # get normals to 3-atom planes
    cp1 = con.unit_vec(np.cross(e1, e2))
    cp2 = con.unit_vec(np.cross(e2, e3))

    if absv:
        coord = np.arccos(np.dot(cp1, cp2))
    else:
        # get cross product of plane normals for signed dihedral angle
        cp3 = np.cross(cp1, cp2)
        coord = np.sign(np.dot(cp3, e2)) * np.arccos(np.dot(cp1, cp2))

    return coord * con.conv('rad', units)
Esempio n. 7
0
def planeang(xyz, *inds, units='rad', absv=False):
    """Returns the angle between the 1-2-3 and 4-5-6 planes."""
    e1 = xyz[inds[0]] - xyz[inds[2]]
    e2 = xyz[inds[1]] - xyz[inds[2]]
    e3 = xyz[inds[3]] - xyz[inds[2]]
    e4 = xyz[inds[4]] - xyz[inds[3]]
    e5 = xyz[inds[5]] - xyz[inds[3]]

    # get normals to 3-atom planes
    cp1 = con.unit_vec(np.cross(e1, e2))
    cp2 = con.unit_vec(np.cross(e4, e5))

    if absv:
        coord = np.arccos(np.dot(cp1, cp2))
    else:
        # get cross product of plane norms for signed dihedral angle
        cp3 = np.cross(cp1, cp2)
        coord = np.sign(np.dot(cp3, e3)) * np.arccos(np.dot(cp1, cp2))

    return coord * con.conv('rad', units)
Esempio n. 8
0
def planetors(xyz, *inds, units='rad', absv=False):
    """Returns the plane angle with the central bond projected out.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    inds : list
        The indices for which the plane dihedral angle is measured.
    units : str, optional
        The units of angle for the output. Default is radians.
    absv : bool, optional
        Specifies if the absolute value is returned.

    Returns
    -------
    float
        The plane dihedral angle.
    """
    e1 = xyz[inds[0]] - xyz[inds[2]]
    e2 = xyz[inds[1]] - xyz[inds[2]]
    e3 = con.unit_vec(xyz[inds[3]] - xyz[inds[2]])
    e4 = xyz[inds[4]] - xyz[inds[3]]
    e5 = xyz[inds[5]] - xyz[inds[3]]

    # get normals to 3-atom planes
    cp1 = np.cross(e1, e2)
    cp2 = np.cross(e4, e5)

    # project out component along central bond
    pj1 = con.unit_vec(cp1 - np.dot(cp1, e3) * e3)
    pj2 = con.unit_vec(cp2 - np.dot(cp2, e3) * e3)

    if absv:
        coord = con.arccos(np.dot(pj1, pj2))
    else:
        # get cross product of vectors for signed dihedral angle
        cp3 = np.cross(pj1, pj2)
        coord = np.sign(np.dot(cp3, e3)) * con.arccos(np.dot(pj1, pj2))

    return coord * con.conv('rad', units)
Esempio n. 9
0
def oop(xyz, *inds, units='rad', absv=False):
    """Returns out-of-plane angle of atom 1 connected to atom 4 in the
    2-3-4 plane.

    Contains an additional sign convention such that rotation of the
    out-of-plane atom over (under) the central plane atom gives an angle
    greater than pi/2 (less than -pi/2).
    """
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[3]])
    e2 = con.unit_vec(xyz[inds[1]] - xyz[inds[3]])
    e3 = con.unit_vec(xyz[inds[2]] - xyz[inds[3]])

    sintau = np.dot(np.cross(e2, e3) / np.sqrt(1 - np.dot(e2, e3) ** 2), e1)
    coord = np.sign(np.dot(e2+e3, e1)) * np.arccos(sintau) + np.pi/2
    # sign convention to keep |oop| < pi
    if coord > np.pi:
        coord -= 2 * np.pi
    if absv:
        return abs(coord) * con.conv('rad', units)
    else:
        return coord * con.conv('rad', units)
Esempio n. 10
0
def bend(xyz, *inds, units='rad'):
    """Returns bending angle for 3 atoms in a chain based on index.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    inds : list
        The indices for which the bond angle is measured.
    units : str, optional
        The units of angle for the output. Default is radians.

    Returns
    -------
    float
        The bond angle.
    """
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[1]])
    e2 = con.unit_vec(xyz[inds[2]] - xyz[inds[1]])

    coord = con.arccos(np.dot(e1, e2))
    return coord * con.conv('rad', units)
Esempio n. 11
0
def oop(xyz, *inds, units='rad', absv=False):
    """Returns out-of-plane angle of atom 1 connected to atom 4 in the
    2-3-4 plane.

    Contains an additional sign convention such that rotation of the
    out-of-plane atom over (under) the central plane atom gives an angle
    greater than :math:`\pi/2` (less than :math:`-\pi/2`).

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    inds : list
        The indices for which the out-of-plane angle is measured.
    units : str, optional
        The units of angle for the output. Default is radians.
    absv : bool, optional
        Specifies if the absolute value is returned.

    Returns
    -------
    float
        The out-of-plane angle.
    """
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[3]])
    e2 = con.unit_vec(xyz[inds[1]] - xyz[inds[3]])
    e3 = con.unit_vec(xyz[inds[2]] - xyz[inds[3]])

    sintau = np.dot(np.cross(e2, e3) / np.sqrt(1 - np.dot(e2, e3)**2), e1)
    coord = np.sign(np.dot(e2 + e3, e1)) * con.arccos(sintau) + np.pi / 2
    # sign convention to keep |oop| < pi
    if coord > np.pi:
        coord -= 2 * np.pi
    if absv:
        return abs(coord) * con.conv('rad', units)
    else:
        return coord * con.conv('rad', units)
Esempio n. 12
0
def planetors(xyz, *inds, units='rad', absv=False):
    """Returns the plane angle with the central bond projected out."""
    e1 = xyz[inds[0]] - xyz[inds[2]]
    e2 = xyz[inds[1]] - xyz[inds[2]]
    e3 = con.unit_vec(xyz[inds[3]] - xyz[inds[2]])
    e4 = xyz[inds[4]] - xyz[inds[3]]
    e5 = xyz[inds[5]] - xyz[inds[3]]

    # get normals to 3-atom planes
    cp1 = np.cross(e1, e2)
    cp2 = np.cross(e4, e5)

    # project out component along central bond
    pj1 = con.unit_vec(cp1 - np.dot(cp1, e3) * e3)
    pj2 = con.unit_vec(cp2 - np.dot(cp2, e3) * e3)

    if absv:
        coord = np.arccos(np.dot(pj1, pj2))
    else:
        # get cross product of vectors for signed dihedral angle
        cp3 = np.cross(pj1, pj2)
        coord = np.sign(np.dot(cp3, e3)) * np.arccos(np.dot(pj1, pj2))

    return coord * con.conv('rad', units)
Esempio n. 13
0
    def add_subs(self, *lbls, inds=-1):
        """Returns the element list and cartesian geometry from a
        combination of substituents.

        Parameters
        ----------
        lbls : list
            A list of substituent labels to be combined.
        inds : int or array_like, optional
            The indices for substitution between substituents. Setting
            inds=-1 (default) makes the last atom the subtituted atom.
            Otherwise a list of indices can be given for the first of
            each pair of substituents.

        Returns
        -------
        elem : (N,) ndarray
            The atomic symbols of the combined substituent.
        xyz : (N, 3) ndarray
            The atomic cartesian coordinates of the combined substituent.
        """
        if isinstance(inds, int):
            inds = (len(lbls) - 1) * [inds]
        elif len(inds) != len(lbls) - 1:
            raise ValueError('Number of inds != number of labels - 1')

        rot = 0
        lbl0 = self.syn[lbls[0].lower()]
        elem = self.elem[lbl0]
        xyz = self.xyz[lbl0]
        for i, label in zip(inds, lbls[1:]):
            dist = np.linalg.norm(xyz - xyz[i], axis=1)
            dist[i] += np.max(dist)
            ibond = np.argmin(dist)
            rot = (rot + 1) % 2
            ax = con.unit_vec(xyz[i] - xyz[ibond])
            lbl = self.syn[label.lower()]
            new_elem = self.elem[lbl]
            new_xyz = displace.rotate(self.xyz[lbl], rot * np.pi, 'Z')
            new_xyz = displace.align_axis(new_xyz, 'Z', ax)
            blen = con.get_covrad(elem[ibond]) + con.get_covrad(new_elem[0])
            new_xyz += xyz[ibond] + blen * ax
            elem = np.hstack((np.delete(elem, i), new_elem))
            xyz = np.vstack((np.delete(xyz, i, axis=0), new_xyz))

        return elem, xyz
Esempio n. 14
0
def edgetors(xyz, *inds, units='rad', absv=False):
    """Returns the torsional angle based on the vector difference of the
    two external atoms (1-2 and 5-6) to the central 3-4 bond.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    inds : list
        The indices for which the edge dihedral angle is measured.
    units : str, optional
        The units of angle for the output. Default is radians.
    absv : bool, optional
        Specifies if the absolute value is returned.

    Returns
    -------
    float
        The edge dihedral angle.
    """
    e1 = con.unit_vec(xyz[inds[0]] - xyz[inds[2]])
    e2 = con.unit_vec(xyz[inds[1]] - xyz[inds[2]])
    e3 = con.unit_vec(xyz[inds[3]] - xyz[inds[2]])
    e4 = con.unit_vec(xyz[inds[4]] - xyz[inds[3]])
    e5 = con.unit_vec(xyz[inds[5]] - xyz[inds[3]])

    # take the difference between unit vectors of external bonds
    e2 -= e1
    e5 -= e4

    # get cross products of difference vectors and the central bond
    cp1 = con.unit_vec(np.cross(e2, e3))
    cp2 = con.unit_vec(np.cross(e3, e5))

    if absv:
        coord = con.arccos(np.dot(cp1, cp2))
    else:
        # get cross product of vectors for signed dihedral angle
        cp3 = np.cross(cp1, cp2)
        coord = np.sign(np.dot(cp3, e3)) * con.arccos(np.dot(cp1, cp2))

    return coord * con.conv('rad', units)
Esempio n. 15
0
def subst(elem, xyz, sublbl, isub, ibond=None, pl=None, vec=None):
    """Returns a molecular geometry with an specified atom replaced by
    substituent.

    Labels are case-insensitive. The index isub gives the position to be
    substituted. If specified, ibond gives the atom bonded to the
    substituent. Otherwise, the nearest atom to isub is used. The
    orientation of the substituent can be given as a vector (the plane
    normal) or an index (the plane containing isub, ibond and pl).

    If isub is given as a list, the entire list of atoms is removed
    and the first index is treated as the position of the substituent.

    Parameters
    ----------
    elem : (N,) array_like
        The atomic symbols of the unsubstituted molecule.
    xyz : (N, 3) array_like
        The atomic cartesian coordinates of the unsubstituted molecule.
    sublbl : str
        The substituent label.
    isub : int or list
        The atomic index (or indices) to be replaced by the substituent.
    ibond : int, optional
        The atomic index of the atom bonded to position isub. If None
        (default), the nearest atom is chosen.
    pl : int or array_like, optional
        The atomic index or vector defining the xz-plane of the
        substituent. If an index is given, the plane normal to the
        isub-ibond-pl plane is used. If None (default), the plane
        is arbitrarily set to [1, 1, 1] and the bond axis is projected
        out.
    vec : (N, 3) array_like, optional
        The atomic cartesian vectors of the unsubstitued molecule. Default
        is None.

    Returns
    -------
    new_elem : (N,) ndarray
        The atomic symbols of the substituted molecule.
    new_xyz : (N, 3) ndarray
        The atomic cartesian coordinates of the substituted molecule.
    new_vec : (N, 3) ndarray
        The atomic cartesian vectors of the substituted molecule.
        Substituent atoms are all set of zero. If vec is None, new_vec
        is all zeros.
    """
    elem = np.array(elem)
    xyz = np.atleast_2d(xyz)
    if not isinstance(isub, int):
        ipos = isub[0]
    else:
        isub = [isub]
        ipos = isub[0]

    if ibond is None:
        dist = np.linalg.norm(xyz - xyz[ipos], axis=1)
        dist[ipos] += np.max(dist)
        ibond = np.argmin(dist)
    elif ibond == ipos:
        raise ValueError('sub and bond indices cannot be the same')

    ax = con.unit_vec(xyz[ipos] - xyz[ibond])
    if pl is None:
        # choose an arbitrary axis and project out the bond axis
        pl = np.ones(3)
        pl -= np.dot(pl, ax) * ax
    elif isinstance(pl, int):
        if pl == ipos:
            raise ValueError('plane and sub indices cannot be the same')
        elif pl == ibond:
            raise ValueError('plane and bond indices cannot be the same')
        pl = np.cross(xyz[ipos] - xyz[ibond], xyz[pl] - xyz[ibond])

    sub_el, sub_xyz = import_sub(sublbl)
    if elem[ipos] == sub_el[0]:
        blen = np.linalg.norm(xyz[ipos] - xyz[ibond])
    else:
        blen = con.get_covrad(elem[ibond]) + con.get_covrad(sub_el[0])

    # rotate to correct orientation and displace to correct position
    sub_xyz = displace.align_axis(sub_xyz, 'Z', ax)
    sub_pl = displace.align_axis([0., 1., 0.], 'Z', ax)
    sub_xyz = displace.align_axis(sub_xyz, sub_pl, pl)
    sub_xyz += xyz[ibond] + blen * ax

    # build the final geometry
    ind1 = [i for i in range(ipos) if i not in isub[1:]]
    ind2 = [i for i in range(ipos + 1, len(elem)) if i not in isub[1:]]
    new_elem = np.hstack((elem[ind1], sub_el, elem[ind2]))
    new_xyz = np.vstack((xyz[ind1], sub_xyz, xyz[ind2]))
    if vec is None:
        return new_elem, new_xyz, None
    else:
        new_vec = np.vstack((vec[ind1], np.zeros((len(sub_el), 3)), vec[ind2]))
        return new_elem, new_xyz, new_vec
Esempio n. 16
0
def test_unit_vec_fails():
    with pytest.raises(ValueError, match=r'Cannot make unit vector from .*'):
        uvec = con.unit_vec(np.zeros(3))
Esempio n. 17
0
def test_unit_vec():
    vec_len = np.linalg.norm(con.unit_vec([1., -1., 2.]))
    assert np.isclose(vec_len, 1.)