def test_quaternions_rotm(rng): # Fifth: test that conversion back to rotation matrices works properly for i in range(TEST_N): rotm1 = rand_rotm(rng) rotm2 = rand_rotm(rng) q1 = Quaternion.from_matrix(rotm1) q2 = Quaternion.from_matrix(rotm2) assert (np.allclose(q1.rotation_matrix(), rotm1)) assert (np.allclose(q2.rotation_matrix(), rotm2)) assert (np.allclose((q1 * q2).rotation_matrix(), np.dot(rotm1, rotm2)))
def _evecs_2_quat(evecs): """Convert a set of eigenvectors to a Quaternion expressing the rotation of the tensor's PAS with respect to the Cartesian axes""" # First, guarantee that the eigenvectors express *proper* rotations evecs = np.array(evecs) * np.linalg.det(evecs)[:, None, None] # Then get the quaternions return [Quaternion.from_matrix(evs.T) for evs in evecs]
def test_quaternions_overload(rng): # Third: test compound rotations and operator overload for i in range(TEST_N): rotm1 = rand_rotm(rng) rotm2 = rand_rotm(rng) q1 = Quaternion.from_matrix(rotm1) q2 = Quaternion.from_matrix(rotm2) # Now test this with a vector v = rng.rand(3) vrotM = np.dot(rotm2, np.dot(rotm1, v)) vrotQ = (q2 * q1).rotate(v) assert np.allclose(vrotM, vrotQ)
def test_quaternions_gimbal(rng): # Second: test the special case of a PI rotation rotm = np.identity(3) rotm[:2, :2] *= -1 # Rotate PI around z axis q = Quaternion.from_matrix(rotm) assert not np.isnan(q.q).any()
def extract(s, force_recalc): if Molecules.default_name not in s.info or force_recalc: Molecules.get(s) mol_quat = [] all_m = s.get_masses() all_pos = s.get_positions() for mol in s.info[Molecules.default_name]: mol_pos = all_pos[mol.indices] mol_pos += np.tensordot(mol.get_array('cell_indices'), s.get_cell(), axes=(1, 1)) mol_ms = all_m[mol.indices] # We still need to correct the positions with the COM mol_com = np.sum(mol_pos * mol_ms[:, None], axis=0) / np.sum(mol_ms) mol_pos -= mol_com tens_i = np.identity(3)[None, :, :] * \ np.linalg.norm(mol_pos, axis=1)[:, None, None]**2 tens_i -= mol_pos[:, None, :] * mol_pos[:, :, None] tens_i *= mol_ms[:, None, None] tens_i = np.sum(tens_i, axis=0) evals, evecs = np.linalg.eigh(tens_i) # General ordering convention: we want the component of the # longest position to be positive along evecs_0, and the component # of the second longest (and non-parallel) position to be positive # along evecs_1, and the triple to be right-handed of course. mol_pos = sorted(mol_pos, key=lambda x: -np.linalg.norm(x)) if len(mol_pos) > 1: evecs[0] *= np.sign(np.dot(evecs[0], mol_pos[0])) e1dirs = np.where( np.linalg.norm(np.cross(mol_pos, mol_pos[0])) > 0)[0] if len(e1dirs) > 0: evecs[1] *= np.sign(np.dot(evecs[1], mol_pos[e1dirs[0]])) evecs[2] *= np.sign(np.dot(evecs[2], np.cross(evecs[0], evecs[1]))) # Evecs must be proper evecs /= np.linalg.det(evecs) quat = Quaternion() quat = quat.from_matrix(evecs.T) mol_quat.append(quat) return mol_quat
def test_quaternions_axang(rng): # Sixth: test conversion to axis + angle q = Quaternion() n, theta = q.axis_angle() assert(theta == 0) u = np.array([1, 0.5, 1]) u /= np.linalg.norm(u) alpha = 1.25 q = Quaternion.from_matrix(axang_rotm(u, alpha)) n, theta = q.axis_angle() assert(np.isclose(theta, alpha)) assert(np.allclose(u, n))
def test_quaternions_rotations(rng): # First: test that rotations DO work for i in range(TEST_N): # n random tests rotm = rand_rotm(rng) q = Quaternion.from_matrix(rotm) # Now test this with a vector v = rng.rand(3) vrotM = np.dot(rotm, v) vrotQ = q.rotate(v) assert np.allclose(vrotM, vrotQ)
elif mode == 'zxz': rotb = axang_rotm([1, 0, 0], b) return np.dot(rotc, np.dot(rotb, rota)) # Random state for testing rndstate = np.random.RandomState(0) test_n = 200 # First: test that rotations DO work for i in range(test_n): # n random tests rotm = rand_rotm(rndstate) q = Quaternion.from_matrix(rotm) # Now test this with a vector v = rndstate.rand(3) vrotM = np.dot(rotm, v) vrotQ = q.rotate(v) assert np.allclose(vrotM, vrotQ) # Second: test the special case of a PI rotation rotm = np.identity(3) rotm[:2, :2] *= -1 # Rotate PI around z axis q = Quaternion.from_matrix(rotm)
# Cross product matrix for u ucpm = np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]]) # Rotation matrix rotm = (np.cos(theta) * np.identity(3) + np.sin(theta) * ucpm + (1 - np.cos(theta)) * np.kron(u[:, None], u[None, :])) return rotm # First: test that rotations DO work for i in range(10): # 10 random tests rotm = rand_rotm() q = Quaternion.from_matrix(rotm) # Now test this with a vector v = np.random.random(3) vrotM = np.dot(rotm, v) vrotQ = q.rotate(v) assert np.allclose(vrotM, vrotQ) # Second: test the special case of a PI rotation rotm = np.identity(3) rotm[:2, :2] *= -1 # Rotate PI around z axis q = Quaternion.from_matrix(rotm)
def test_quaternions(): import numpy as np from ase.quaternions import Quaternion def axang_rotm(u, theta): u = np.array(u, float) u /= np.linalg.norm(u) # Cross product matrix for u ucpm = np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]]) # Rotation matrix rotm = (np.cos(theta) * np.identity(3) + np.sin(theta) * ucpm + (1 - np.cos(theta)) * np.kron(u[:, None], u[None, :])) return rotm def rand_rotm(rndstate=np.random.RandomState(0)): """Axis & angle rotations.""" u = rndstate.rand(3) theta = rndstate.rand() * np.pi * 2 return axang_rotm(u, theta) def eulang_rotm(a, b, c, mode='zyz'): rota = axang_rotm([0, 0, 1], a) rotc = axang_rotm([0, 0, 1], c) if mode == 'zyz': rotb = axang_rotm([0, 1, 0], b) elif mode == 'zxz': rotb = axang_rotm([1, 0, 0], b) return np.dot(rotc, np.dot(rotb, rota)) # Random state for testing rndstate = np.random.RandomState(0) test_n = 200 # First: test that rotations DO work for i in range(test_n): # n random tests rotm = rand_rotm(rndstate) q = Quaternion.from_matrix(rotm) # Now test this with a vector v = rndstate.rand(3) vrotM = np.dot(rotm, v) vrotQ = q.rotate(v) assert np.allclose(vrotM, vrotQ) # Second: test the special case of a PI rotation rotm = np.identity(3) rotm[:2, :2] *= -1 # Rotate PI around z axis q = Quaternion.from_matrix(rotm) assert not np.isnan(q.q).any() # Third: test compound rotations and operator overload for i in range(test_n): rotm1 = rand_rotm(rndstate) rotm2 = rand_rotm(rndstate) q1 = Quaternion.from_matrix(rotm1) q2 = Quaternion.from_matrix(rotm2) # Now test this with a vector v = rndstate.rand(3) vrotM = np.dot(rotm2, np.dot(rotm1, v)) vrotQ = (q2 * q1).rotate(v) assert np.allclose(vrotM, vrotQ) # Fourth: test Euler angles for mode in ['zyz', 'zxz']: for i in range(test_n): abc = rndstate.rand(3) * 2 * np.pi v2 = rndstate.rand(2, 3) # Two random vectors to rotate rigidly q_eul = Quaternion.from_euler_angles(*abc, mode=mode) rot_eul = eulang_rotm(*abc, mode=mode) v2_q = np.array([q_eul.rotate(v) for v in v2]) v2_m = np.array([np.dot(rot_eul, v) for v in v2]) assert np.allclose(v2_q, v2_m) # Fifth: test that conversion back to rotation matrices works properly for i in range(test_n): rotm1 = rand_rotm(rndstate) rotm2 = rand_rotm(rndstate) q1 = Quaternion.from_matrix(rotm1) q2 = Quaternion.from_matrix(rotm2) assert (np.allclose(q1.rotation_matrix(), rotm1)) assert (np.allclose(q2.rotation_matrix(), rotm2)) assert (np.allclose((q1 * q2).rotation_matrix(), np.dot(rotm1, rotm2)))