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)
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)
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)
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)
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)
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)
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