def test_icp_exact(self): """Ensure that ICP is exact for corresponding inputs""" # Note that we do not bother to test the non-unique matching since we # know it provides very poor results. np.random.seed(0) # First test using unique matching, which should work for i in range(2, 6): num_points = 2**i points = np.random.rand(num_points, 3) rotation = from_axis_angle([0.3, 0.3, 0.3], 0.3) translation = np.random.rand(1, 3) transformed_points = rotate(rotation, points) + translation q, t, indices = mapping.icp(points, transformed_points, return_indices=True) q = from_matrix(q) # In the case of just two points, the mapping is not unique, # so we don't check the mapping itself, just the result. if i > 1: self.assertTrue( np.logical_or( np.allclose(rotation, q), np.allclose(rotation, -q), )) self.assertTrue(np.allclose(translation, t)) self.assertTrue( np.allclose(transformed_points, rotate(q, points[indices]) + t))
def test_icp_mismatched(self): """See how ICP works for non-corresponding inputs. Have set some reasonable threshold for testing purposes.""" np.random.seed(0) # First test using unique matching, which should work for i in range(2, 6): num_points = 2**i points = np.random.rand(num_points, 3) rotation = from_axis_angle([0.3, 0.3, 0.3], 0.3) translation = np.random.rand(1, 3) permutation = np.random.permutation(num_points) transformed_points = rotate(rotation, points[permutation]) + translation q, t, indices = mapping.icp(points, transformed_points, return_indices=True) q = from_matrix(q) deltas = transformed_points - (rotate(q, points[indices]) + t) norms = np.linalg.norm(deltas, axis=-1) # This is purely a heuristic, since we can't guarantee exact matches self.assertTrue(np.mean(norms) < 0.5)
def render( args, grid_size=256, output_size=256, draw_x: float = 10, draw_y: float = 10 ): pipeline = args.scene.selected_pipeline if not pipeline: return data = pipeline.compute(args.frame) view_orientation = rowan.from_matrix(args.viewport.viewMatrix[:, :3]) dp = freud.diffraction.DiffractionPattern( grid_size=grid_size, output_size=output_size, ) dp.compute( system=data, view_orientation=view_orientation, zoom=1, peak_width=1, ) buf = dp.to_image(cmap="afmhot", vmax=np.max(dp.diffraction)) width, height, bytes_per_pixel = buf.shape img = PySide2.QtGui.QImage( buf, width, height, width * bytes_per_pixel, PySide2.QtGui.QImage.Format_RGBA8888, ) # Paint QImage onto viewport canvas args.painter.drawImage(draw_x, draw_y, img)
def test_kabsch(self): """Perform a rotation and ensure that we can recover it""" np.random.seed(0) for i in range(1, 12): num_points = 2**i points = np.random.rand(num_points, 3) rotation = random.rand(1) translation = np.random.rand(1, 3) transformed_points = rotate(rotation, points) + translation R, t = mapping.kabsch(points, transformed_points) q = from_matrix(R) # In the case of just two points, the mapping is not unique, # so we don't check the mapping itself, just the result. if i > 1: self.assertTrue( np.logical_or( np.allclose(rotation, q), np.allclose(rotation, -q), )) self.assertTrue(np.allclose(translation, t)) self.assertTrue( np.allclose(transformed_points, rotate(q, points) + t))
def test_from_matrix(self): self.assertTrue(np.all(rowan.from_matrix(np.eye(3)) == one)) with self.assertRaises(ValueError): self.assertTrue(np.allclose(rowan.from_matrix(2 * np.eye(3)))) mat = np.array([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) self.assertTrue( np.logical_or(np.allclose(rowan.from_matrix(mat), half), np.allclose(rowan.from_matrix(mat), -half))) mat = np.array([[0, 1, 0], [0, 0, -1], [-1, 0, 0]]) v = np.copy(half) v[3] *= -1 self.assertTrue(np.allclose(rowan.from_matrix(mat), v))
def test_to_from_matrix(self): # The equality is only guaranteed up to a sign converted = rowan.from_matrix(rowan.to_matrix(input1)) self.assertTrue( np.all( np.logical_or( np.isclose(input1 - converted, 0), np.isclose(input1 + converted, 0), )))
def test_to_from_matrix(self): """Test conversion from a quaternion to a matrix and back.""" # The equality is only guaranteed up to a sign converted = rowan.from_matrix(rowan.to_matrix(input1)) self.assertTrue( np.all( np.logical_or( np.isclose(input1 - converted, 0), np.isclose(input1 + converted, 0), )))
def rotation(self): camera = self._backend_objects['camera'] norm_out = np.array(camera.position) norm_out /= np.linalg.norm(norm_out) norm_up = np.array(camera.up) norm_up -= np.dot(norm_up, norm_out) * norm_out norm_up /= np.linalg.norm(norm_up) norm_right = np.cross(norm_up, norm_out) rotation_matrix = np.array([norm_right, norm_up, norm_out]).T result = rowan.from_matrix(rotation_matrix, require_orthogonal=False) result = rowan.conjugate(result) return result
def render( args, bins: Tuple[int] = (200, 200), mode: str = "bod", neighbors: dict = {"r_max": 1.4}, image_size: int = 200, draw_x: float = 10, draw_y: float = 10, ): """Render a bond order diagram that rotates with the view. Args: args: OVITO viewport modifier arguments. bins: Passed to freud.environment.BondOrder. mode: Passed to freud.environment.BondOrder. neighbors: Passed to freud.environment.BondOrder.compute. It is recommended to use a cutoff distance at the first trough of the radial distribution function g(r). See https://freud.readthedocs.io/en/latest/topics/querying.html for more information. image_size: Rendered size of the bond order diagram. draw_x: X coordinate of the top-left corner of the drawn image. draw_y: Y coordinate of the top-left corner of the drawn image. """ pipeline = args.scene.selected_pipeline if not pipeline: return data = pipeline.compute(args.frame) view_orientation = rowan.from_matrix(args.viewport.viewMatrix[:, :3]) bod = freud.environment.BondOrder(bins, mode) bod.compute(system=data, neighbors=neighbors) view = to_view(bod, view_orientation, image_size) buf = to_image(view, cmap="afmhot") width, height, bytes_per_pixel = buf.shape img = PySide2.QtGui.QImage( buf, width, height, width * bytes_per_pixel, PySide2.QtGui.QImage.Format_RGBA8888, ) # Paint QImage onto viewport canvas args.painter.drawImage(draw_x, draw_y, img)
def ref_quaternion(self): '''Return quaternion for rotating data into occlusal plane.''' if self._quaternion is None: # 1) start by translating the space so origin sensor is at the origin REF = self.ref_translation MS = self.sensor_mean_coords(self._molar) + self.ref_translation # 2) now find the rotation matrix to the occlusal coordinate system z = np.cross(REF, MS) # z is perpendicular to ms and ref vectors z = z / np.linalg.norm(z) y = np.cross(MS, z) # y is perpendicular to z and ms y = y / np.linalg.norm(y) x = np.cross(y, z) x = x / np.linalg.norm(x) m = np.array([x, y, z]) # rotation matrix directly self._quaternion = rowan.from_matrix(m) return self._quaternion
def get_basis_vectors(self, base_positions, base_type=[], base_quaternions=None, is_complete=False, apply_orientation=False): """Get the basis vectors for the defined crystall structure. :param base_positions: N by 3 np array of the Wyckoff postions :type base_positions: np.ndarray :param base_type: a list of string for particle type name :type base_type: list :param base_quaternions: N by 4 np array of quaternions, default None :type base_quaternions: np.ndarray :param is_complete: bool value to indicate if the positions are complete postions in a unitcell :type is_complete: bool :param apply_orientations: bool value to indicate if the space group symmetry should be applied to orientatioin :type apply_orientations: bool :return: basis_vectors :rtype: np.ndarray """ # check input accuracy if not isinstance(base_positions, np.ndarray) or len(base_positions.shape) == 1 \ or base_positions.shape[1] != 3: raise ValueError( 'base_positions must be an numpy array of shape Nx3') if apply_orientation: if not isinstance(base_quaternions, np.ndarray) or len(base_quaternions.shape) == 1 \ or base_quaternions.shape[1] != 4: raise ValueError( 'base_quaternions must be an numpy array of shape Nx4') if len(base_type): if not isinstance(base_type, list) or len(base_type) != base_positions.shape[0] \ or not all(isinstance(i, str) for i in base_type): raise ValueError( 'base_type must contain a list of type name the same length' 'as the number of basis positions') else: base_type = ['A'] * base_positions.shape[0] threshold = 1e-6 reflection_exist = False for i in range(0, len(self.rotations)): # Generate the new set of positions from the base pos = wrap(self.rotations[i].dot(base_positions.T).T + self.translations[i]) if apply_orientation: if np.linalg.det(self.rotations[i]) == 1: quat_rotate = rowan.from_matrix(self.rotations[i], require_orthogonal=False) quat = rowan.multiply(quat_rotate, base_quaternions) else: quat = base_quaternions reflection_exist = True if i == 0: positions = pos type_list = copy.deepcopy(base_type) if apply_orientation: quaternions = quat else: pos_comparisons = pos - positions[:, np.newaxis, :] norms = np.linalg.norm(pos_comparisons, axis=2) comps = np.all(norms > threshold, axis=0) positions = np.append(positions, pos[comps], axis=0) type_list += [x for x, y in zip(base_type, comps) if y] if apply_orientation: quaternions = np.append(quaternions, quat[comps], axis=0) if norms.min() < threshold: print( 'Orientation quaterions may have multiple values for the same ' 'particle postion under the symmetry operation for this space group ' 'and is not well defined, only the first occurance is used.' ) if reflection_exist: print( 'Warning: reflection operation is included in this space group, ' 'and is ignored for quaternion calculation.') if is_complete and len(positions) != len(base_positions): raise ValueError( 'the complete basis postions vector does not match the space group ' 'chosen. More positions are generated based on the symmetry operation ' 'within the provided space group') if apply_orientation: return wrap(positions), type_list, quaternions else: return wrap(positions), type_list
refl_wgt = calc_XRDreflWeights(structure, hkls, rad='CuKa') refl_wgt = {0: 1.0, 1: 1.0, 2: 1.0} omega = np.radians(np.arange(0, 365, 5)) """ symmetry after """ hkls = normalize(np.array(hkls)) symOps = genSym(crystalSym) symOps = np.unique(np.swapaxes(symOps, 2, 0), axis=0) """ only use proper rotations """ """ complicated, simplify? """ proper = np.where(np.linalg.det(symOps) == 1) #proper orthogonal quatSymOps = quat.from_matrix(symOps[proper]) quatSymOps = np.tile(quatSymOps[:, :, np.newaxis], (1, 1, len(omega))) quatSymOps = quatSymOps.transpose((2, 0, 1)) sliceIndex = range(omega.shape[0]) """ gen quats from bunge grid """ bungeAngs = np.zeros((np.product(od.phi1cen.shape), 3)) for ii, i in enumerate(np.ndindex(od.phi1cen.shape)): bungeAngs[ii, :] = np.array((od.phi1cen[i], od.Phicen[i], od.phi2cen[i])) qgrid = eu2quat(bungeAngs).T # %%
point_group_rotation_matrix_dict[num] = {'rotations': rotations} with open('point_group_rotation_matrix_dict.pickle', 'wb') as f: pickle.dump(point_group_rotation_matrix_dict, f) point_group_list = [ '1', '-1', '2', 'm', '2/m', '222', 'mm2', 'mmm', '4', '-4', '4/m', '422', '4mm', '-42m', '4/mmm', '3', '-3', '32', '3m', '-3m', '6', '-6', '6/m', '622', '6mm', '-6m2', '6/mmm', '23', 'm-3', '432', '-43m', 'm-3m' ] num = range(1, 33) point_group_name_dict = dict(zip(num, point_group_list)) with open('point_group_name_mapping.json', 'w') as f: json.dump(point_group_name_dict, f) point_group_quat_dict = {} for key, item in point_group_rotation_matrix_dict.items(): quats = [] n = item['rotations'].shape[0] for i in range(0, n): qtemp = rowan.from_matrix(item['rotations'][i, :, :], require_orthogonal=False) quats.append(qtemp.tolist()) point_group_quat_dict[key] = quats with open('point_group_quat_dict.json', 'w') as f: json.dump(point_group_quat_dict, f)
def test_zero_beta(self): """Check cases where beta is 0.""" # Since the Euler calculations are all done using matrices, it's easier # to construct the test cases by directly using matrices as well. We # assume gamma is 0 since, due to gimbal lock, only either alpha+gamma # or alpha-gamma is a relevant parameter, and we just scan the other # possible values. The actual function is defined such that gamma will # always be zero in those cases. We define the matrices using lambda # functions to support sweeping a range of values for alpha and beta, # specifically to test cases where signs flip e.g. cos(0) vs cos(pi). # These sign flips lead to changes in the rotation angles that must be # tested. mats_euler_intrinsic = [ ('xzx', 'intrinsic', lambda alpha, beta: [[np.cos(beta), 0, 0], [0, np.cos(beta) * np.cos(alpha), -np.sin(alpha)], [0, np.cos(beta) * np.sin(alpha), np.cos(alpha)]]), ('xyx', 'intrinsic', lambda alpha, beta: [[np.cos(beta), 0, 0], [0, np.cos(alpha), -np.cos(beta) * np.sin(alpha)], [0, np.sin(alpha), np.cos(beta) * np.cos(alpha)]]), ('yxy', 'intrinsic', lambda alpha, beta: [[np.cos(alpha), 0, np.cos(beta) * np.sin(alpha)], [0, np.cos(beta), 0], [-np.sin(alpha), 0, np.cos(beta) * np.cos(alpha)]]), ('yzy', 'intrinsic', lambda alpha, beta: [[np.cos(beta) * np.cos(alpha), 0, np.sin(alpha)], [0, np.cos(beta), 0], [-np.cos(beta) * np.sin(alpha), 0, np.cos(alpha)]]), ('zyz', 'intrinsic', lambda alpha, beta: [[ np.cos(beta) * np.cos(alpha), -np.sin(alpha), 0 ], [np.cos(beta) * np.sin(alpha), np.cos(beta), 0], [0, 0, np.cos(beta)]]), ('zxz', 'intrinsic', lambda alpha, beta: [[np.cos(alpha), -np.cos(beta) * np.sin(alpha), 0], [np.sin(alpha), np.cos(beta) * np.cos(beta), 0], [0, 0, np.cos(beta)]]), ] mats_tb_intrinsic = [ ('xzy', 'intrinsic', lambda alpha, beta: [[0, -np.sin( beta), 0], [np.sin(beta) * np.cos(alpha), 0, -np.sin( alpha)], [np.sin(beta) * np.sin(alpha), 0, np.cos(alpha)]]), ('xyz', 'intrinsic', lambda alpha, beta: [[0, 0, np.sin( beta)], [np.sin(beta) * np.sin(alpha), np.cos(alpha), 0 ], [-np.sin(beta) * np.cos(alpha), np.sin(alpha), 0]]), ('yxz', 'intrinsic', lambda alpha, beta: [[np.cos(alpha), np.sin(beta) * np.sin(alpha), 0], [0, 0, -np.sin(beta)], [-np.sin(alpha), np.sin(beta) * np.cos(alpha), 0]]), ('yzx', 'intrinsic', lambda alpha, beta: [[0, -np.sin(beta) * np.cos(alpha), np.sin(alpha)], [np.sin(beta), 0, 0], [0, np.sin(beta) * np.sin(alpha), np.cos(alpha)]]), ('zyx', 'intrinsic', lambda alpha, beta: [[ 0, -np.sin(alpha), np.sin(beta) * np.cos(alpha) ], [0, np.cos(alpha), np.sin(beta) * np.sin(alpha)], [-np.sin(beta), 0, 0]]), ('zxy', 'intrinsic', lambda alpha, beta: [[ np.cos(alpha), 0, np.sin(beta) * np.sin(alpha) ], [np.sin(alpha), 0, -np.sin(beta) * np.cos(alpha)], [0, -1, 0]]), ] # Extrinsic rotations can be tested identically to intrinsic rotations # in the case of proper Euler angles. mats_euler_extrinsic = [(m[0], 'extrinsic', m[2]) for m in mats_euler_intrinsic] # For Tait-Bryan angles, extrinsic rotations axis order must be # reversed (since axes 1 and 3 are not identical), but more # importantly, due to the sum/difference of alpha and gamma that # arises, we need to test the negative of alpha to catch the dangerous # cases. In practice we get the same results since we're sweeping alpha # values in the tests below, but it's useful to set this up precisely. mats_tb_extrinsic = [(m[0][::-1], 'extrinsic', lambda alpha, beta: m[2] (-alpha, beta)) for m in mats_tb_intrinsic] # Since angle representations may not be unique, checking that # quaternions are equal may not work. Instead we perform rotations and # check that they are identical. For simplicity, we rotate the # simplest vector with all 3 components (otherwise tests won't catch # the problem because there's no component to rotate). test_vector = [1, 1, 1] mats_intrinsic = (mats_euler_intrinsic, mats_tb_intrinsic) mats_extrinsic = (mats_euler_extrinsic, mats_tb_extrinsic) # The beta angles are different for proper Euler angles and Tait-Bryan # angles because the relevant beta terms will be sines and cosines, # respectively. all_betas = ((0, np.pi), (np.pi / 2, -np.pi / 2)) alphas = (0, np.pi / 2, np.pi, 3 * np.pi / 2) for mats in (mats_intrinsic, mats_extrinsic): for betas, mat_set in zip(all_betas, mats): for convention, axis_type, mat_func in mat_set: quaternions = [] for beta in betas: for alpha in alphas: mat = mat_func(alpha, beta) if np.linalg.det(mat) == -1: # Some of these will be improper rotations. continue quat = rowan.from_matrix(mat) quaternions.append(quat) euler = rowan.to_euler(quat, convention, axis_type) converted = rowan.from_euler(*euler, convention=convention, axis_type=axis_type) correct_rotation = rowan.rotate(quat, test_vector) test_rotation = rowan.rotate( converted, test_vector) self.assertTrue(np.allclose(correct_rotation, test_rotation, atol=1e-6), msg=""" Failed for convention {}, axis type {}, alpha = {}, beta = {}. Expected quaternion: {}. Calculated: {}. Expected vector: {}. Calculated vector: {}.""".format( convention, axis_type, alpha, beta, quat, converted, correct_rotation, test_rotation)) # For completeness, also test with broadcasting. quaternions = np.asarray(quaternions).reshape(-1, 4) all_euler = rowan.to_euler(quaternions, convention, axis_type) converted = rowan.from_euler(all_euler[..., 0], all_euler[..., 1], all_euler[..., 2], convention, axis_type) self.assertTrue( np.allclose(rowan.rotate(quaternions, test_vector), rowan.rotate(converted, test_vector), atol=1e-6))
def _regularize_box(position, velocity, orientation, angmom, box_matrix, dtype=None, dimensions=3): """Convert box into a right-handed coordinate frame with only upper triangular entries. Also convert corresponding positions and orientations.""" # First use QR decomposition to compute the new basis Q, R = np.linalg.qr(box_matrix) Q = Q.astype(dtype) R = R.astype(dtype) if not np.allclose(Q[:dimensions, :dimensions], np.eye(dimensions)): # If Q is not the identity matrix, then we will be # changing data, so we have to copy. This only causes # actual failures for non-writeable GSD frames, but could # cause unexpected data corruption for other cases position = np.copy(position) if orientation is not None: orientation = np.copy(orientation) if velocity is not None: velocity = np.copy(velocity) if angmom is not None: angmom = np.copy(angmom) # Since we'll be performing a quaternion operation, # we have to ensure that Q is a pure rotation sign = np.linalg.det(Q) Q = Q * sign R = R * sign # First rotate positions, velocities. # Since they are vectors, we can use the matrix directly. # Conveniently, instead of transposing Q we can just reverse # the order of multiplication here position = position.dot(Q) if velocity is not None: velocity = velocity.dot(Q) # For orientations and angular momenta, we use the quaternion quat = rowan.from_matrix(Q.T) if orientation is not None: for i in range(orientation.shape[0]): orientation[i, :] = rowan.multiply(quat, orientation[i, :]) if angmom is not None: for i in range(angmom.shape[0]): angmom[i, :] = rowan.multiply(quat, angmom[i, :]) # Now we have to ensure that the box is right-handed. We # do this as a second step to avoid introducing reflections # into the rotation matrix before making the quaternion signs = np.diag( np.diag(np.where(R < 0, -np.ones(R.shape), np.ones(R.shape)))) box = R.dot(signs) position = position.dot(signs) if velocity is not None: velocity = velocity.dot(signs) else: box = box_matrix # Construct the box Lx, Ly, Lz = np.diag(box).flatten().tolist() xy = box[0, 1] / Ly xz = box[0, 2] / Lz yz = box[1, 2] / Lz box = Box(Lx=Lx, Ly=Ly, Lz=Lz, xy=xy, xz=xz, yz=yz, dimensions=dimensions) return position, velocity, orientation, angmom, box