Beispiel #1
0
def rotation_matrix(axis, theta, units="deg"):
    '''
    Obtain a left multiplication rotation matrix, given the axis and angle you
    wish to rotate by. By default it assumes units of degrees.  If theta is in
    radians, set units to rad.

    **Parameters**

        axis: *list, float*
            The axis in which to rotate around.
        theta: *float*
            The angle of rotation.
        units: *str, optional*
            The units of theta (deg or rad).

    **Returns**

        rotatation_matrix: *list, list, float*
            The left multiplication rotation matrix.

    **References**

        * http://stackoverflow.com/questions/6802577/python-rotation-of-3d-vector/25709323#25709323
    '''
    cast.assert_vec(axis, length=3, numeric=True)
    axis = np.array(axis)
    assert cast.is_numeric(theta),\
        "Error - Theta should be a numerical value (it is %s)." % str(theta)
    theta = float(theta)
    if "deg" in units.lower():
        theta = np.radians(theta)
    return scipy.linalg.expm(
        np.cross(np.eye(3), axis / scipy.linalg.norm(axis) * theta))
Beispiel #2
0
    def __init__(self,
                 name,
                 box_size=(10.0, 10.0, 10.0),
                 box_angles=(90.0, 90.0, 90.0),
                 periodic=False):
        self.name = str(name)
        self.periodic = periodic
        self.atoms = []
        self.bonds = []
        self.angles = []
        self.dihedrals = []
        self.molecules = []
        self.parameters = None
        self.atom_labels = None

        cast.assert_vec(box_size)
        cast.assert_vec(box_angles)
        self.box_size = np.array(list(map(float, box_size)))
        self.box_angles = np.array(list(map(float, box_angles)))
        a, b, c = self.box_size
        alpha, beta, gamma = self.box_angles

        EPS = 1E-3
        if any([abs(ba - 90) > EPS for ba in box_angles]):
            '''
            If the angles are not 90, the system is triclinic.  So we will set
            the relative information here.  For LAMMPS, trigonal vectors are
            established by using xy, xz, and yz:

                A = (xhi - xlo, 0.0, 0.0)
                B = (xy, yhi - ylo, 0.0)
                C = (xz, yz, zhi - zlo)
            Formula for converting (a, b, c, alpha, beta, gamma) to
            (lx, ly, lz, xy, xz, yz) taken from online lammps help.
            '''
            self.xlo, self.ylo, self.zlo = 0.0, 0.0, 0.0

            self.xhi = a
            self.xy = b * np.cos(np.deg2rad(gamma))
            self.xz = c * np.cos(np.deg2rad(beta))
            self.yhi = np.sqrt(b**2 - self.xy**2)
            self.yz = (b * c * np.cos(np.deg2rad(alpha)) -
                       self.xy * self.xz) / self.yhi
            self.zhi = np.sqrt(c**2 - self.xz**2 - self.yz**2)
        else:
            '''
            Otherwise, we have a monoclinic box.  We will set the
            center to the euclidean origin.
            '''
            self.xlo = -a / 2.0
            self.ylo = -b / 2.0
            self.zlo = -c / 2.0
            self.xhi = a / 2.0
            self.yhi = b / 2.0
            self.zhi = c / 2.0
            self.xy = 0.0
            self.xz = 0.0
            self.yz = 0.0
Beispiel #3
0
    def __sub__(self, other):
        '''
        If given some 3D array, offset all atomic coordinates.

        **Parameters**

            other: *array, float*
                Some 3D array/tuple/list of sorts that indicates a
                translation.

        **Returns**

            None
        '''
        cast.assert_vec(other, length=3, numeric=True)
        return self.__add__(-np.array(other, dtype=float))
Beispiel #4
0
    def scale(self, v):
        '''
        Apply a scalar to this molecule.

        **Parameters**

            v: *list, float*
                A vector of 3 floats specifying the x, y,
                and z scalars to be applied.

        **Returns**

            None
        '''
        cast.assert_vec(v, length=3, numeric=True)
        for a in self.atoms:
            a.scale(v)
Beispiel #5
0
    def set_position(self, pos):
        '''
        Manually set the atomic positions by passing a tuple/list.

        **Parameters**

            pos: *list, float or tuple, float*
                A vector of 3 floats specifying the new x, y, and z coordinate.

        **Returns**

            None
        '''
        cast.assert_vec(pos, length=3, numeric=True)
        self.x = pos[0]
        self.y = pos[1]
        self.z = pos[2]
Beispiel #6
0
    def translate(self, v):
        '''
        Apply a translation to this molecule.

        **Parameters**

            v: *list, float*
                A vector of 3 floats specifying the x, y,
                and z offsets to be applied.

        **Returns**

            None
        '''
        cast.assert_vec(v, length=3, numeric=True)
        for a in self.atoms:
            a.translate(v)
Beispiel #7
0
    def __truediv__(self, other):
        '''
        If given some 3D array, scale all atomic coordinates.

        **Parameters**

            other: *array, float*
                Some 3D array/tuple/list of sorts that indicates a
                scalar operation.

        **Returns**

            None
        '''
        assert 0.0 not in other,\
            "Error - Cannot divide by 0!"
        cast.assert_vec(other, length=3, numeric=True)
        return self.__mul__(np.array(1.0 / np.array(other, dtype=float)))
Beispiel #8
0
    def __add__(self, other):
        '''
        If given some 3D array, offset all atomic coordinates.

        **Parameters**

            other: *array, float*
                Some 3D array/tuple/list of sorts that indicates a
                translation.

        **Returns**

            None
        '''
        cast.assert_vec(other, length=3, numeric=True)
        new = copy.deepcopy(self)
        new.translate(other)
        return new
Beispiel #9
0
    def translate(self, v):
        '''
        Translate the atom by a vector.

        **Parameters**

            v: *list, float*
                A vector of 3 floats specifying the x, y,
                and z offsets to be applied.

        **Returns**

            None
        '''
        cast.assert_vec(v, length=3, numeric=True)
        self.x += float(v[0])
        self.y += float(v[1])
        self.z += float(v[2])
Beispiel #10
0
    def __mul__(self, other):
        '''
        If given some 3D array, scale all atomic coordinates.

        **Parameters**

            other: *array, float*
                Some 3D array/tuple/list of sorts that indicates a
                scalar operation.

        **Returns**

            None
        '''
        cast.assert_vec(other, length=3, numeric=True)
        new = copy.deepcopy(self)
        for i, mol in enumerate(new.molecules):
            new.molecules[i] = mol * other
        return new
Beispiel #11
0
    def scale(self, v):
        '''
        Scale the atom by a vector.  This can be useful if we want to
        change coordinate systems.

        **Parameters**

            v: *list, float*
                A vector of 3 floats specifying the x, y,
                and z scalars to be applied.

        **Returns**

            None
        '''
        cast.assert_vec(v, length=3, numeric=True)
        self.x *= float(v[0])
        self.y *= float(v[1])
        self.z *= float(v[2])
Beispiel #12
0
def orthogonal_procrustes(A, ref_matrix, reflection=False):
    '''
    Using the orthogonal procrustes method, we find the unitary matrix R with
    det(R) > 0 such that ||A*R - ref_matrix||^2 is minimized.  This varies
    from that within scipy by the addition of the reflection term, allowing
    and disallowing inversion.  NOTE - This means that the rotation matrix is
    used for right side multiplication!

    **Parameters**

        A: *list,* :class:`squid.structures.atom.Atom`
            A list of atoms for which R will minimize the frobenius
            norm ||A*R - ref_matrix||^2.
        ref_matrix: *list,* :class:`squid.structures.atom.Atom`
            A list of atoms for which *A* is being rotated towards.
        reflection: *bool, optional*
            Whether inversion is allowed (True) or not (False).

    **Returns**

        R: *list, list, float*
            Right multiplication rotation matrix to best overlay A onto the
            reference matrix.
        scale: *float*
            Scalar between the matrices.

    **Derivation**

        Goal: minimize ||A\*R - ref||^2, switch to trace

        trace((A\*R-ref).T\*(A\*R-ref)), now we distribute

        trace(R'\*A'\*A\*R) + trace(ref.T\*ref) - trace((A\*R).T\*ref) -
        trace(ref.T\*(A\*R)), trace doesn't care about order, so re-order

        trace(R\*R.T\*A.T\*A) + trace(ref.T\*ref) - trace(R.T\*A.T\*ref) -
        trace(ref.T\*A\*R), simplify

        trace(A.T\*A) + trace(ref.T\*ref) - 2\*trace(ref.T\*A\*R)

        Thus, to minimize we want to maximize trace(ref.T \* A \* R)

        u\*w\*v.T = (ref.T\*A).T

        ref.T \* A = w \* u.T \* v

        trace(ref.T \* A \* R) = trace (w \* u.T \* v \* R)

        differences minimized when trace(ref.T \* A \* R) is maximized, thus
        when trace(u.T \* v \* R) is maximized

        This occurs when u.T \* v \* R = I (as u, v and R are all unitary
        matrices so max is 1)

        R is a rotation matrix so R.T = R^-1

        u.T \* v \* I = R^-1 = R.T

        R = u \* v.T

        Thus, R = u.dot(vt)


    **References**

        * https://github.com/scipy/scipy/blob/v0.16.0/scipy/linalg/
          _procrustes.py#L14
        * http://compgroups.net/comp.soft-sys.matlab/procrustes-analysis
          -without-reflection/896635
    '''

    assert hasattr(A, "__len__") and hasattr(ref_matrix, "__len__"),\
        "Error - A and ref_matrix must be lists of atomic coordinates!"
    cast.assert_vec(A[0], length=3, numeric=True)
    cast.assert_vec(ref_matrix[0], length=3, numeric=True)

    A = np.asarray_chkfinite(A)
    ref_matrix = np.asarray_chkfinite(ref_matrix)

    if A.ndim != 2:
        raise ValueError('expected ndim to be 2, but observed %s' % A.ndim)
    if A.shape != ref_matrix.shape:
        raise ValueError('the shapes of A and ref_matrix differ (%s vs %s)' %
                         (A.shape, ref_matrix.shape))

    u, w, vt = svd(A.T.dot(ref_matrix))

    R = u.dot(vt)  # Get the rotation matrix, including reflections
    if not reflection and scipy.linalg.det(R) < 0:
        # To remove reflection, we change the sign of the rightmost column of
        # u (or v) and the scalar associated
        # with that column
        u[:, -1] *= -1
        w[-1] *= -1
        R = u.dot(vt)

    scale = w.sum()  # Get the scaled difference

    return R, scale
Beispiel #13
0
 def __truediv__(self, other):
     assert 0.0 not in other,\
         "Error - Cannot divide by 0!"
     cast.assert_vec(other, length=3, numeric=True)
     return self * np.array(1.0 / np.array(other, dtype=float))
Beispiel #14
0
 def __mul__(self, other):
     cast.assert_vec(other, length=3, numeric=True)
     local_atom = self.replicate()
     local_atom.scale(other)
     return local_atom
Beispiel #15
0
 def __sub__(self, other):
     cast.assert_vec(other, length=3, numeric=True)
     return self + -np.array(other)