Exemple #1
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)
Exemple #2
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)
Exemple #3
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)
Exemple #4
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)
Exemple #5
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)
Exemple #6
0
def align_axis(xyz, test_ax, ref_ax, ind=None, origin=np.zeros(3)):
    """Rotates a set of atoms such that two axes are parallel.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    test_crd : (3,) array_like
        Cartesian coordinates of the original axis.
    test_crd : (3,) array_like
        Cartesian coordinates of the final axis.
    ind : array_like, optional
        List of atomic indices to specify which atoms are displaced. If
        `ind == None` (default) then all atoms are displaced.
    origin : (3,) array_like, optional
        The origin of rotation. Default is the cartesian origin.

    Returns
    -------
    (N, 3) ndarray
        The atomic cartesian coordinates of the displaced molecule.
    """
    vp = VectorParser(xyz)
    test = vp(test_ax, unit=True)
    ref = vp(ref_ax, unit=True)
    if np.allclose(test, ref):
        return xyz
    elif np.allclose(test, -ref):
        rotax = np.array([0., 0., 1.])
        if np.allclose(test, rotax) or np.allclose(test, -rotax):
            rotax = np.array([0., 1., 0.])
        rotax -= np.dot(rotax, test) * test
        return rotate(xyz, np.pi, rotax, ind=ind, origin=origin)
    else:
        angle = con.arccos(np.dot(test, ref))
        rotax = np.cross(test, ref)
        return rotate(xyz, angle, rotax, ind=ind, origin=origin)
def test_arccos_minusone():
    ang = con.arccos(-1 - 1e-10)
    assert np.isclose(ang, np.pi)
def test_arccos_plusone():
    ang = con.arccos(1 + 1e-10)
    assert np.isclose(ang, 0)
Exemple #9
0
def angax(rotmat, units='rad'):
    r"""Returns the angle, axis of rotation and determinant of a
    rotational matrix.

    Based on the form of **R**, it can be separated into symmetric
    and antisymmetric components with :math:`(r_{ij} + r_{ji})/2` and
    :math:`(r_{ij} - r_{ji})/2`, respectively. Then,

    .. math::

        r_{ii} = \cos(a) + u_i^2 (\det(\mathbf{R}) - \cos(a)),

        \cos(a) = (-\det(\mathbf{R}) + \sum_j r_{jj}) / 2 = 
        (\mathrm{tr}(\mathbf{R}) - \det(\mathbf{R})) / 2.

    From the expression for :math:`r_{ii}`, the magnitude of :math:`u_i`
    can be found

    .. math::

        |u_i| = \sqrt{\frac{1 + \det(\mathbf{R}) [2 r_{ii} -
        \mathrm{tr}(\mathbf{R})])}{3 - \det(\mathbf{R}) \mathrm{tr}(\mathbf{R})}},

    which satisfies :math:`u \cdot u = 1`. Note that if
    :math:`\det(\mathbf{R}) \mathrm{tr}(\mathbf{R}) = 3`, the axis is arbitrary
    (identity or inversion). Otherwise, the sign can be found from the
    antisymmetric component of **R**.

    .. math::

        u_i \sin(a) = (r_{jk} - r_{kj}) / 2, \quad i \neq j \neq k,

        \mathrm{sign}(u_i) = \mathrm{sign}(r_{jk} - r_{kj}),

    since :math:`\sin(a)` is positive in the range 0 to :math:`\pi`. :math:`i`,
    :math:`j` and :math:`k` obey the cyclic relation 3 -> 2 -> 1 -> 3 -> ...

    This fails when :math:`det(\mathbf{R}) \mathrm{tr}(\mathbf{R}) = -1`, in
    which case the symmetric component of **R** is used

    .. math::

        u_i u_j (\det(\mathbf{R}) - \cos(a)) = (r_{ij} + r_{ji}) / 2,

        \mathrm{sign}(u_i) \mathrm{sign}(u_j) = \det(\mathbf{R}) \mathrm{sign}(r_{ij} + r_{ji}).

    The signs can then be found by letting :math:`\mathrm{sign}(u_3) = +1`,
    since a rotation of :math:`pi` or a reflection are equivalent for
    antiparallel axes. See
    http://scipp.ucsc.edu/~haber/ph251/rotreflect_17.pdf

    Parameters
    ----------
    rotmat : (3, 3) array_like
        The rotational matrix.
    units : str, optional
        The output units for the angle. Default is radians.

    Returns
    -------
    ang : float
        The angle of rotation.
    u : (3,) ndarray
        The axis of rotation as a 3D vector.
    det : int
        The determinant of the rotation matrix.

    Raises
    ------
    ValueError
        When the absolute value of the determinant is not equal to 1.
    """
    det = np.linalg.det(rotmat)
    if not np.isclose(np.abs(det), 1):
        raise ValueError('Determinant of a rotational matrix must be +/- 1')

    tr = np.trace(rotmat)
    ang = con.arccos((tr - det) / 2) * con.conv('rad', units)
    if np.isclose(det * tr, 3):
        u = np.array([0, 0, 1])
    else:
        u = np.sqrt((1 + det * (2 * np.diag(rotmat) - tr)) / (3 - det * tr))
        if np.isclose(det * tr, -1):
            sgn = np.ones(3)
            sgn[1] = det * _nonzero_sign(rotmat[1, 2] + rotmat[2, 1])
            sgn[0] = det * sgn[1] * _nonzero_sign(rotmat[0, 1] + rotmat[1, 0])
            u *= sgn
        else:
            u[0] *= _nonzero_sign(rotmat[1, 2] - rotmat[2, 1])
            u[1] *= _nonzero_sign(rotmat[2, 0] - rotmat[0, 2])
            u[2] *= _nonzero_sign(rotmat[0, 1] - rotmat[1, 0])

    return ang, u, det