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))
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
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))
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)
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]
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)
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)))
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
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])
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
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])
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
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))
def __mul__(self, other): cast.assert_vec(other, length=3, numeric=True) local_atom = self.replicate() local_atom.scale(other) return local_atom
def __sub__(self, other): cast.assert_vec(other, length=3, numeric=True) return self + -np.array(other)