Example #1
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)
Example #2
0
def read_xyz(infile, units='ang', hasvec=False, hascom=False):
    """Reads input file in XYZ format.

    XYZ files are in the format:
    natm
    comment
    A X1 Y1 Z1 [Vx1 Vy1 Vz1]
    B X2 Y2 Z2 [Vx2 Vy2 Vz2]
    ...
    where natm is the number of atoms, comment is a comment line, A and B
    are atomic labels and X, Y and Z are cartesian coordinates (in
    Angstroms). The vectors Vx, Vy and Vz are optional and will only be
    read if hasvec = True.

    Due to the preceding number of atoms, multiple XYZ format geometries
    can easily be read from a single file.
    """
    try:
        natm = int(infile.readline())
    except ValueError:
        raise IOError('geometry not in XYZ format.')
    if hascom:
        comment = infile.readline().strip()
    else:
        infile.readline()
        comment = ''
    data = np.array([infile.readline().split() for i in range(natm)])
    elem = data[:, 0]
    xyz = data[:, 1:4].astype(float) * con.conv(units, 'ang')
    if hasvec:
        vec = data[:, 4:7].astype(float)
    else:
        vec = None
    return elem, xyz, vec, comment
Example #3
0
def translate(xyz, amp, axis, ind=None, units='ang'):
    """Translates a set of atoms along a given vector.

    Parameters
    ----------
    xyz : (N, 3) array_like
        The atomic cartesian coordinates.
    amp : float
        The distance for translation.
    axis : array_like or str
        The axis of translation, parsed by :class:`VectorParser`.
    ind : array_like, optional
        List of atomic indices to specify which atoms are displaced. If
        ind is None (default) then all atoms are displaced.
    units : str, optional
        The units of length for displacement. Default is angstroms.

    Returns
    -------
    (N, 3) ndarray
        The atomic cartesian coordinates of the displaced molecule.
    """
    if ind is None:
        ind = range(len(xyz))
    vp = VectorParser(xyz)
    u = vp(axis, unit=True)
    amp *= con.conv(units, 'ang')

    newxyz = np.copy(xyz)
    newxyz[ind] += amp * u
    return newxyz
Example #4
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)
Example #5
0
def read_gdat(infile, units='bohr', hasvec=False, hascom=False):
    """Reads input file in FMS90 Geometry.dat format.

    Geometry.dat files are in the format:
    comment
    natm
    A X1 Y1 Z1
    B X2 Y2 Z2
    ...
    Vx1 Vy1 Vz1
    Vx2 Vy2 Vz2
    ...
    where comment is a comment line, natm is the number of atoms, A and B
    are atomic labels, X, Y and Z are cartesian coordinates and Vq are
    vectors for cartesian coordinates q. The vectors are only read if
    hasvec = True.
    """
    if hascom:
        comment = infile.readline().strip()
    else:
        infile.readline()
        comment = ''
    try:
        natm = int(infile.readline())
    except ValueError:
        raise IOError('geometry not in Geometry.dat format')
    data = np.array([infile.readline().split() for i in range(natm)])
    elem = data[:, 0]
    xyz = data[:, 1:].astype(float) * con.conv(units, 'ang')
    if hasvec:
        vec = np.array([infile.readline().split() for i in range(natm)],
                       dtype=float)
    else:
        vec = None
    return elem, xyz, vec, comment
Example #6
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)
Example #7
0
def angax(rotmat, units='rad'):
    """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 (r_ij + r_ji)/2 and
    (r_ij - r_ji)/2, respectively. Then,

    r_ii = cos(a) + u_i^2 (det(R) - cos(a)),
    cos(a) = (-det(R) + sum_j r_jj) / 2 = (tr(R) - det(R)) / 2.

    From the expression for r_ii, the magnitude of u_i can be found

    |u_i| = sqrt((1 + det(R) [2 r_ii - tr(R)]) / 2),

    which satisfies u.u = 1. Note that if det(R) tr(R) = 3, the axis
    is arbitrary (identity or inversion). Otherwise, the sign can be found
    from the antisymmetric component of R

    u_i sin(a) = (r_jk - r_kj) / 2, i != j != k,
    sign(u_i) = sign(r_jk - r_kj),

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

    This fails when det(R) tr(R) = -1, in which case the symmetric
    component of R is used

    u_i u_j (det(R) - cos(a)) = (r_ij + r_ji) / 2,
    sign(u_i) sign(u_j) = det(R) sign(r_ij + r_ji).

    The signs can then be found by letting sign(u_3) = +1, since a rotation
    of pi or a reflection are equivalent for antiparallel axes. See
    http://scipp.ucsc.edu/~haber/ph251/rotreflect_17.pdf
    """
    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 = np.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
Example #8
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)
Example #9
0
def write_xyz(outfile, elem, xyz, vec=None, comment='', units='ang'):
    """Writes geometry to an output file in XYZ format."""
    natm = len(elem)
    write_xyz = xyz * con.conv('ang', units)
    outfile.write(' {}\n{}\n'.format(natm, comment))
    if vec is None:
        for atm, xyzi in zip(elem, write_xyz):
            outfile.write('{:4s}{:12.6f}{:12.6f}{:12.6f}\n'.format(atm, *xyzi))
    else:
        for atm, xyzi, pxyzi in zip(elem, write_xyz, vec):
            outfile.write('{:4s}{:12.6f}{:12.6f}{:12.6f}'.format(atm, *xyzi) +
                          '{:12.6f}{:12.6f}{:12.6f}\n'.format(*pxyzi))
Example #10
0
def write_gdat(outfile, elem, xyz, vec=None, comment='', units='bohr'):
    """Writes geometry to an output file in Geometry.dat format."""
    natm = len(elem)
    write_xyz = xyz * con.conv('ang', units)
    outfile.write('{}\n{}\n'.format(comment, natm))
    for atm, xyzi in zip(elem, write_xyz):
        outfile.write('{:<2s}{:18.8E}{:18.8E}{:18.8E}\n'.format(atm, *xyzi))
    if vec is None:
        for line in range(natm):
            outfile.write(' {:18.8E}{:18.8E}{:18.8E}\n'.format(0, 0, 0))
    else:
        for pxyzi in vec:
            outfile.write(' {:18.8E}{:18.8E}{:18.8E}\n'.format(*pxyzi))
Example #11
0
def write_col(outfile, elem, xyz, vec=None, comment='', units='bohr'):
    """Writes geometry to an output file in COLUMBUS format.

    For the time being, vector output is not supported for the
    COLUMBUS file format.
    """
    write_xyz = xyz * con.conv('ang', units)
    if comment != '':
        outfile.write(comment + '\n')
    for atm, (x, y, z) in zip(elem, write_xyz):
        outfile.write(' {:<2s}{:7.1f}{:14.8f}{:14.8f}{:14.8f}{:14.8f}'
                      '\n'.format(atm, con.get_num(atm), x, y, z,
                                  con.get_mass(atm)))
Example #12
0
def translate(xyz, amp, axis, ind=None, origin=np.zeros(3), units='ang'):
    """Translates a set of atoms along a given vector.

    If no indices are specified, all atoms are displaced.
    """
    if ind is None:
        ind = range(len(xyz))
    u = _parse_axis(axis)
    amp *= con.conv(units, 'ang')

    newxyz = np.copy(xyz)
    newxyz[ind] += amp * u
    return newxyz
Example #13
0
def rotmat(ang, u, det=1, units='rad', xyz=None):
    r"""Returns the rotational matrix based on an angle and axis.

    A general rotational matrix in 3D can be formed given an angle and
    an axis by

    .. math::

        \mathbf{R} = \cos(a) \mathbf{I} + (\det(\mathbf{R}) -
        \cos(a)) \mathbf{u} \otimes \mathbf{u} + \sin(a) [\mathbf{u}]_\times

    for identity matrix **I**, angle *a*, axis **u**,
    outer product :math:`\otimes` and cross-product matrix
    :math:`[\mathbf{u}]_\times`.  Determinants of +1 and -1 give proper and
    improper rotation, respectively. Thus, :math:`\det(\mathbf{R}) = -1` and
    :math:`a = 0` is a reflection along the axis. Action of the rotational
    matrix occurs about the origin. See en.wikipedia.org/wiki/Rotation_matrix
    and http://scipp.ucsc.edu/~haber/ph251/rotreflect_17.pdf

    Parameters
    ----------
    ang : float
        The angle of rotation.
    u : array_like or str
        The axis of rotation, converted to a unit vector.
    det : int, optional
        The determinant of the matrix (1 or -1) used to specify proper
        and improper rotations. Default is 1.
    units : str, optional
        The units of angle for the rotation. Default is radians.
    xyz : (N, 3) array_like, optional
        The cartesian coordinates used in axis specification.

    Returns
    -------
    (3, 3) ndarray
        The rotational matrix of the given angle and axis.

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

    u /= np.linalg.norm(u)
    amp = ang * con.conv(units, 'rad')
    ucross = np.array([[0, u[2], -u[1]], [-u[2], 0, u[0]], [u[1], -u[0], 0]])
    return (np.cos(amp) * np.eye(3) + np.sin(amp) * ucross +
            (det - np.cos(amp)) * np.outer(u, u))
Example #14
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)
Example #15
0
def write_traj(outfile, elem, xyz, vec=None, comment='', units='bohr',
               time=0., phase=0., ramp=0., iamp=0., state=0.):
    """Writes geometry to an output file in FMS/nomad trajectory format."""
    natm = len(elem)
    write_xyz = xyz.flatten() * con.conv('ang', units)
    if comment != '':
        outfile.write(comment + '\n')
    if vec is None:
        write_vec = np.zeros_like(write_xyz)
    else:
        write_vec = vec.flatten()
    namp = ramp**2 + iamp**2
    args = np.hstack((write_xyz, write_vec, phase, ramp, iamp, namp, state))
    fmt = '{:10.2f}' + (6*natm + 5)*'{:10.4f}' + '\n'
    outfile.write(fmt.format(time, *args))
Example #16
0
def read_traj(infile, units='bohr', hasvec=False, hascom=False,
              elem=None, time=None, autocom=False):
    """Reads input file in FMS/nomad trajectory format

    trajectory files are in the format:
    T1 X1 Y1 Z1 X2 Y2 ... Vx1 Vy1 Vz1 Vx2 Vy2 ... G Re(A) Im(A) |A| S
    T2 X1 Y1 Z1 X2 Y2 ... Vx1 Vy1 Vz1 Vx2 Vy2 ... G Re(A) Im(A) |A| S
    ...
    where T is the time, Vq are the vectors (momenta) for cartesian
    coordinates q, G is the phase, A is the amplitude and S is the state
    label. The vectors are only read if hasvec = True.

    Trajectory files do not contain atomic labels. If not provided, they are
    set to dummy atoms which may affect calculations involving atomic
    properties. A time should be provided, otherwise the first geometry
    in the file is used.
    """
    if hascom:
        comment = infile.readline().strip()
    else:
        comment = ''
    if time is None:
        rawline = infile.readline().split()
        if rawline == []:
            raise IOError('empty line provided')
        elif 'Time' in rawline:
            line = np.array(infile.readline().split(), dtype=float)
        else:
            line = np.array(rawline, dtype=float)
    else:
        alldata = np.array([line.split() for line in infile.readlines()
                            if 'Time' not in line], dtype=float)
        line = alldata[np.isclose(alldata[:,0], time)][0]
    natm = len(line) // 6 - 1
    if natm < 1 or len(line) % 6 != 0:
        raise IOError('geometry not in trajectory format.')
    if elem is None:
        elem = np.array(['X'] * natm)
    xyz = line[1:3*natm+1].reshape(natm, 3) * con.conv(units,'ang')
    if hasvec:
        vec = line[3*natm+1:6*natm+1].reshape(natm, 3)
    else:
        vec = None
    if autocom:
        fmt = 't={:8.2f}, state={:4d}, a^2={:10.4f}'
        comment += fmt.format(line[0], int(line[-1]), line[-2])
    return elem, xyz, vec, comment
Example #17
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)
Example #18
0
def read_col(infile, units='bohr', hasvec=False, hascom=False):
    """Reads input file in COLUMBUS format.

    COLUMBUS geometry files are in the format:
    A nA X1 Y1 Z1 mA
    B nB X2 Y2 Z2 mB
    ...
    where A and B are atomic labels, nA and nB are corresponding atomic
    numbers, mA and mB are corresponding atomic masses and X, Y and Z
    are cartesian coordinates (in Bohrs).

    COLUMBUS geometry files do not provide the number of atoms in each
    geometry. A comment line (or blank line) must be used to separate
    molecules.

    For the time being, vector input is not supported for the
    COLUMBUS file format.
    """
    if hascom:
        comment = infile.readline().strip()
    else:
        comment = ''
    data = np.empty((0, 6), dtype=str)
    while True:
        pos = infile.tell()
        line = np.array(infile.readline().split())
        try:
            # catch comment line or end-of-file
            line[1:].astype(float)
            data = np.vstack((data, line))
        except (ValueError, IndexError):
            if len(data) < 1:
                raise IOError('geometry not in COLUMBUS format.')
            else:
                # roll back one line before break
                infile.seek(pos)
                break
    elem = data[:, 0]
    xyz = data[:, 2:5].astype(float) * con.conv(units, 'ang')
    if hasvec:
        vec = np.zeros_like(xyz)
    else:
        vec = None
    return elem, xyz, vec, comment
Example #19
0
def stre(xyz, *inds, units='ang'):
    """Returns bond length based on index.

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

    Returns
    -------
    float
        The bond length.
    """
    coord = np.linalg.norm(xyz[inds[0]] - xyz[inds[1]])
    return coord * con.conv('ang', units)
Example #20
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)
Example #21
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)
Example #22
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)
Example #23
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)
Example #24
0
def rotmat(ang, ax, det=1, units='rad'):
    """Returns the rotational matrix based on an angle and axis.

    A general rotational matrix in 3D can be formed given an angle and
    an axis by

    R = cos(a) I + (det(R) - cos(a)) u (x) u + sin(a) [u]_x

    for identity matrix I, angle a, axis u, outer product (x) and
    cross-product matrix [u]_x. Determinants of +1 and -1 give proper
    and improper rotation, respectively. Thus, det(R) = -1 and a = 0
    is a reflection along the axis. Action of the rotational matrix occurs
    about the origin. See en.wikipedia.org/wiki/Rotation_matrix
    and http://scipp.ucsc.edu/~haber/ph251/rotreflect_17.pdf
    """
    if not np.isclose(np.abs(det), 1):
        raise ValueError('Determinant of a rotational matrix must be +/- 1')

    u = _parse_axis(ax)
    amp = ang * con.conv(units, 'rad')
    ucross = np.array([[0, u[2], -u[1]], [-u[2], 0, u[0]], [u[1], -u[0], 0]])
    return (np.cos(amp) * np.eye(3) + np.sin(amp) * ucross +
            (det - np.cos(amp)) * np.outer(u, u))
Example #25
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)
Example #26
0
def test_conv_ang():
    assert np.isclose(con.conv('deg', 'rad'), np.pi/180.)
Example #27
0
def stre(xyz, *inds, units='ang', absv=False):
    """Returns bond length based on index."""
    coord = np.linalg.norm(xyz[inds[0]] - xyz[inds[1]])
    return coord * con.conv('ang', units)
Example #28
0
def test_conv_mas():
    assert np.isclose(con.conv('mp', 'me'), 1836.15267981)
Example #29
0
def test_conv_ene():
    assert np.isclose(con.conv('har', 'ev'), 27.21138505)
Example #30
0
def test_conv_fails():
    with pytest.raises(ValueError, match=r'.* not of same unit type'):
        con.conv('ev', 'fs')
Example #31
0
def test_conv_unit():
    assert np.isclose(con.conv('auto', 'auto'), 1.)
Example #32
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
Example #33
0
def test_conv_len():
    assert np.isclose(con.conv('ang', 'pm'), 100.)
Example #34
0
def read_zmt(infile, units='ang', hasvec=False, hascom=False):
    """Reads input file in Z-matrix format.

    Z-matrix files are in the format:
    A
    B 1 R1
    C indR2 R2 indA2 A2
    D indR3 R3 indA3 A3 indT3 T3
    E indR4 R4 indA4 A4 indT4 T4
    ...
    where A, B, C, D, E are atomic labels, indR, indA, indT are reference
    atom indices, R are bond lengths (in Angstroms), A are bond angles (in
    degrees) and T are dihedral angles (in degrees). For example, E is a
    distance R from atom indR with an E-indR-indA angle of A and an
    E-indR-indA-indT dihedral angle of T. Alternatively, values can be
    assigned to a list of variables after the Z-matrix (preceded by a blank
    line).

    Although the number of atoms is not provided, the unique format of the
    first atom allows multiple geometries to be read without separation
    by a comment line.

    For the time being, vector input is not supported for the
    Z-matrix file format.
    """
    if hascom:
        comment = infile.readline().strip()
    else:
        comment = ''
    data = []
    vlist = dict()
    while True:
        pos = infile.tell()
        line = infile.readline()
        split = line.split()
        if line == '':
            # end-of-file
            break
        elif len(split) == 1 and len(data) > 0:
            # roll back one line before break
            infile.seek(pos)
            break
        elif split == []:
            # blank line before variable assignment
            continue
        elif split[0] in con.sym:
            data.append(split)
        elif split[1] == '=' and len(split) == 3:
            vlist[split[0]] = float(split[2])
        else:
            # assume it's a comment line and roll back
            infile.seek(pos)
            break

    natm = len(data)
    if natm < 1:
        raise IOError('geometry not in Z-matrix format.')
    elem = np.array([line[0] for line in data])
    xyz = np.zeros((natm, 3))
    for i in range(natm):
        if i == 0:
            # leave first molecule at origin
            continue
        elif i == 1:
            # move along z-axis by R
            xyz = displace.translate(xyz, _valvar(data[1][2], vlist),
                                     [0, 0, 1], ind=1)
        elif i == 2:
            indR = int(data[2][1]) - 1
            indA = int(data[2][3]) - 1
            xyz[2] = xyz[indR]
            # move from indR towards indA by R
            xyz = displace.translate(xyz, _valvar(data[2][2], vlist),
                                     xyz[indA]-xyz[indR], ind=2)
            # rotate into xz-plane by A
            xyz = displace.rotate(xyz, _valvar(data[2][4], vlist), [0, 1, 0],
                                  ind=2, origin=xyz[indR], units='deg')
        else:
            indR = int(data[i][1]) - 1
            indA = int(data[i][3]) - 1
            indT = int(data[i][5]) - 1
            xyz[i] = xyz[indR]
            # move from indR towards indA by R
            xyz = displace.translate(xyz, _valvar(data[i][2], vlist),
                                     xyz[indA]-xyz[indR], ind=i)
            # rotate about (indT-indA)x(indR-indA) by A
            xyz = displace.rotate(xyz, _valvar(data[i][4], vlist),
                                  np.cross(xyz[indT]-xyz[indA],
                                           xyz[indR]-xyz[indA]),
                                  ind=i, origin=xyz[indR], units='deg')
            # rotate about indR-indA by T
            xyz = displace.rotate(xyz, _valvar(data[i][6], vlist),
                                  xyz[indR]-xyz[indA],
                                  ind=i, origin=xyz[indR], units='deg')

    xyz = displace.centre_mass(elem, xyz) * con.conv(units, 'ang')
    if hasvec:
        vec = np.zeros_like(xyz)
    else:
        vec = None
    return elem, xyz, vec, comment
Example #35
0
def test_conv_tim():
    assert np.isclose(con.conv('ps', 'fs'), 1e3)