def _find_mirror(self, axis): """ Looks for mirror symmetry of specified type about axis. Possible types are "h" or "vd". Horizontal (h) mirrors are perpendicular to the axis while vertical (v) or diagonal (d) mirrors are parallel. v mirrors has atoms lying on the mirror plane while d mirrors do not. """ mirror_type = "" # First test whether the axis itself is the normal to a mirror plane. if self.is_valid_op(SymmOp.reflection(axis)): self.symmops.append(SymmOp.reflection(axis)) mirror_type = "h" else: # Iterate through all pairs of atoms to find mirror for s1, s2 in itertools.combinations(self.centered_mol, 2): if s1.species_and_occu == s2.species_and_occu: normal = s1.coords - s2.coords if np.dot(normal, axis) < self.tol: op = SymmOp.reflection(normal) if self.is_valid_op(op): self.symmops.append(op) if len(self.rot_sym) > 1: mirror_type = "d" for v, r in self.rot_sym: if not np.linalg.norm(v - axis) < self.tol: if np.dot(v, normal) < self.tol: mirror_type = "v" break else: mirror_type = "v" break return mirror_type
def align_axis(structure, axis='c', direction=(0, 0, 1)): """ Rotates a structure so that the specified axis is along the [001] direction. This is useful for adding vacuum, and in general for using vasp compiled with no z-axis relaxation. Args: structure (Structure): Pymatgen Structure object to rotate. axis: Axis to be rotated. Can be 'a', 'b', 'c', or a 1x3 vector. direction (vector): Final axis to be rotated to. Returns: structure. Rotated to align axis along direction. """ if axis == 'a': axis = structure.lattice._matrix[0] elif axis == 'b': axis = structure.lattice._matrix[1] elif axis == 'c': axis = structure.lattice._matrix[2] proj_axis = np.cross(axis, direction) if not(proj_axis[0] == 0 and proj_axis[1] == 0): theta = ( np.arccos(np.dot(axis, direction) / (np.linalg.norm(axis) * np.linalg.norm(direction))) ) R = get_rotation_matrix(proj_axis, theta) rotation = SymmOp.from_rotation_and_translation(rotation_matrix=R) structure.apply_operation(rotation) if axis == 'c' and direction == (0, 0, 1): structure.lattice._matrix[2][2] = abs(structure.lattice._matrix[2][2]) return structure
def from_spacegroup_number(sgnum): datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sg_data') filename = str(sgnum).zfill(3) + "*" files = sorted(glob.glob(os.path.join(datadir, filename))) with open(files[0], "r") as fid: symmops = [] rots = [] lines = fid.readlines() sgname = lines[0].strip() for i in xrange(1, len(lines)): toks = re.split(",", lines[i].strip()) if len(toks) == 3: rot = np.zeros((3, 3)) trans = [0, 0, 0] for j in xrange(3): tok = toks[j] m = re.search("([\+\-]*)([xyz])", tok) if m: factor = -1 if m.group(1) == "-" else 1 loc = ord(m.group(2)) - 120 rot[j, loc] = factor tok = re.sub("([\+\-]*)([xyz])", "", tok) if tok.strip() != '': trans[j] = eval(tok) rots.append(rot) symmops.append(SymmOp.from_rotation_matrix_and_translation_vector(rot, trans)) return Spacegroup(sgname, sgnum, symmops)
def trilayer(doped = None): a = 5.43 fcc = Lattice([[a/2,a/2,0],[a/2,0,a/2],[0,a/2,a/2]]) trilayer = Structure(fcc,['Si']*2,[[0.00,0.00,0.00],[0.25,0.25,0.25]]) # Make the cell cubic trilayer.make_supercell([[1,1,-1],[1,-1,1],[-1,1,1]]) trilayer.make_supercell([[1,1,0],[1,-1,0],[0,0,4]]) # Rotate the cell rt = 0.70710678118654746 symmop = SymmOp.from_rotation_and_translation([[rt,rt,0],[rt,-rt,0],[0,0,1]]) trilayer.apply_operation(symmop) if doped is not None: frac_coords = numpy.array([0.5,0.0,0.5]) for i,atom in enumerate(trilayer): if numpy.linalg.norm(atom.frac_coords-frac_coords) < 0.001: trilayer.replace(i,doped,frac_coords) for z in [0.375,0.625]: for xy in [0.00,0.50]: trilayer.append('Mn',[xy,xy,z]) return trilayer
def rotate(self, matrix, tol=1e-5): matrix = SquareTensor(matrix) if not matrix.is_rotation(tol): raise ValueError("Rotation matrix is not valid.") sop = SymmOp.from_rotation_and_translation(matrix, [0., 0., 0.]) return self.transform(sop)
def test_structure_transform(self): # Test trivial case trivial = self.fit_r4.structure_transform(self.structure, self.structure.copy()) self.assertArrayAlmostEqual(trivial, self.fit_r4) # Test simple rotation rot_symm_op = SymmOp.from_axis_angle_and_translation([1, 1, 1], 55.5) rot_struct = self.structure.copy() rot_struct.apply_operation(rot_symm_op) rot_tensor = self.fit_r4.rotate(rot_symm_op.rotation_matrix) trans_tensor = self.fit_r4.structure_transform(self.structure, rot_struct) self.assertArrayAlmostEqual(rot_tensor, trans_tensor) # Test supercell bigcell = self.structure.copy() bigcell.make_supercell([2, 2, 3]) trans_tensor = self.fit_r4.structure_transform(self.structure, bigcell) self.assertArrayAlmostEqual(self.fit_r4, trans_tensor) # Test rotated primitive to conventional for fcc structure sn = self.get_structure("Sn") sn_prim = SpacegroupAnalyzer(sn).get_primitive_standard_structure() sn_prim.apply_operation(rot_symm_op) rotated = self.fit_r4.rotate(rot_symm_op.rotation_matrix) transformed = self.fit_r4.structure_transform(sn, sn_prim) self.assertArrayAlmostEqual(rotated, transformed)
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = read_structure(os.path.join(test_dir, "LiFePO4.cif")) nfp = read_structure(os.path.join(test_dir, "NaFePO4.cif")) self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), {Composition("Li"): Composition("Na")}) self.assertAlmostEqual(sm.get_minimax_rms_anonymous(lfp, nfp)[0], 0.096084154118549828) #Test partial occupancies. s1 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.25}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.75}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure([[3, 0, 0], [0, 3, 0], [0, 0, 3]], [{"Fe": 0.25}, {"Fe": 0.25}, {"Fe": 0.25}, {"Fe": 0.25}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), {Composition("Fe0.5"): Composition("Fe0.25")}) self.assertAlmostEqual(sm.get_minimax_rms_anonymous(s1, s2)[0], 0)
def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0,0,1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix, 5) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt2.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt.lengths_and_angles)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16) #catch the singular matrix error latt = Lattice.from_lengths_and_angles([1,1,1], [10,10,10]) for l, _, _ in latt.find_all_mappings(latt, ltol=0.05, atol=11): self.assertTrue(isinstance(l, Lattice))
def parse_symmetry_operations(symmops_str_list): """ Help method to parse the symmetry operations. Args: symmops_str_list: List of symmops strings of the form ['x, y, z', '-x, -y, z', '-y+1/2, x+1/2, z+1/2', ...] Returns: List of SymmOps """ ops = [] for op_str in symmops_str_list: rot_matrix = np.zeros((3, 3)) trans = np.zeros(3) toks = op_str.strip().split(",") for i, tok in enumerate(toks): for m in re.finditer("([\+\-]*)\s*([x-z\d]+)/*(\d*)", tok): factor = -1 if m.group(1) == "-" else 1 if m.group(2) in ("x", "y", "z"): j = ord(m.group(2)) - 120 rot_matrix[i, j] = factor else: num = float(m.group(2)) if m.group(3) != "": num /= float(m.group(3)) trans[i] = factor * num op = SymmOp.from_rotation_and_translation(rot_matrix, trans) ops.append(op) return ops
def test_init(self): fitter = StructureFitter(self.b, self.a) self.assertTrue(fitter.mapping_op != None, "No fit found!") #Now to try with rotated structure op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0, 0, 1])) editor = StructureEditor(self.a) editor.apply_operation(op) fitter = StructureFitter(self.b, editor.modified_structure) self.assertTrue(fitter.mapping_op != None, "No fit found!") #test with a supercell mod = SupercellMaker(self.a, scaling_matrix=[[2, 0, 0], [0, 1, 0], [0, 0, 1]]) a_super = mod.modified_structure fitter = StructureFitter(self.b, a_super) self.assertTrue(fitter.mapping_op != None, "No fit found!") # Test with a structure with a translated point editor = StructureEditor(self.a) site = self.a[0] editor.delete_site(0) trans = np.random.randint(0, 1000, 3) editor.insert_site(0, site.species_and_occu, site.frac_coords + trans, False, False) fitter = StructureFitter(self.b, editor.modified_structure) self.assertTrue(fitter.mapping_op != None, "No fit found for translation {}!".format(trans)) parser = CifParser(os.path.join(test_dir, "FePO4a.cif")) a = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, "FePO4b.cif")) b = parser.get_structures()[0] fitter = StructureFitter(b, a) self.assertTrue(fitter.mapping_op != None, "No fit found!")
def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.structure.apply_operation(op) self.assertArrayAlmostEqual( self.structure.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5)
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = self.get_structure("LiFePO4") nfp = self.get_structure("NaFePO4") self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), True) self.assertAlmostEqual(sm.get_rms_anonymous(lfp, nfp)[0], 0.060895871160262717) #Test partial occupancies. s1 = Structure(Lattice.cubic(3), [{"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure(Lattice.cubic(3), [{"Fe": 0.25}, {"Fe": 0.5}, {"Fe": 0.5}, {"Fe": 0.75}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure(Lattice.cubic(3), [{"Mn": 0.5}, {"Mn": 0.5}, {"Mn": 0.5}, {"Mn": 0.5}], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), True) self.assertAlmostEqual(sm.get_rms_anonymous(s1, s2)[0], 0)
def test_reflection(self): normal = np.random.rand(3) origin = np.random.rand(3) refl = SymmOp.reflection(normal, origin) point = np.random.rand(3) newcoord = refl.operate(point) # Distance to the plane should be negatives of each other. self.assertAlmostEqual(np.dot(newcoord - origin, normal), -np.dot(point - origin, normal))
def __init__(self, axis, angle, angle_in_radians=False): """ """ self._axis = axis self._angle = angle self._angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation( self._axis, self._angle, self._angle_in_radians)
def _check_R2_axes_asym(self): """ Test for 2-fold rotation along the principal axes. Used to handle asymetric top molecules. """ for v in self.principal_axes: op = SymmOp.from_axis_angle_and_translation(v, 180) if self.is_valid_op(op): self.symmops.append(op) self.rot_sym.append((v, 2))
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (aligned_out, rot_out, scale_out) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) rotated = SymmOp.from_rotation_and_translation(rot_out).operate_multi(latt.matrix) self.assertArrayAlmostEqual(rotated, aligned_out.matrix) self.assertArrayAlmostEqual(np.dot(scale_out, latt2.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt2.lengths_and_angles))
def test_list_based_functions(self): # zeroed tc = TensorCollection([1e-4*Tensor(np.eye(3))]*4) for t in tc.zeroed(): self.assertArrayEqual(t, np.zeros((3, 3))) for t in tc.zeroed(1e-5): self.assertArrayEqual(t, 1e-4*np.eye(3)) self.list_based_function_check("zeroed", tc) self.list_based_function_check("zeroed", tc, tol=1e-5) # transform symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) self.list_based_function_check("transform", self.seq_tc, symm_op=symm_op) # symmetrized self.list_based_function_check("symmetrized", self.seq_tc) # rotation a = 3.14 * 42.5 / 180 rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.list_based_function_check("rotate", self.diff_rank, matrix=rotation) # is_symmetric self.assertFalse(self.seq_tc.is_symmetric()) self.assertTrue(self.diff_rank.is_symmetric()) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # voigt self.list_based_function_check("voigt", self.diff_rank) # is_voigt_symmetric self.assertTrue(self.diff_rank.is_voigt_symmetric()) self.assertFalse(self.seq_tc.is_voigt_symmetric()) # Convert to ieee for entry in self.ieee_data[:2]: xtal = entry['xtal'] tc = TensorCollection([entry['original_tensor']]*3) struct = entry['structure'] self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt tc_input = [t for t in np.random.random((3, 6, 6))] tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)
def fit_with_mapper(self, mapper): coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.089000], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000]] mol1 = Molecule(["C", "H", "H", "H", "H"], coords) op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60) rotcoords = [op.operate(c) for c in coords] mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords) mm = MoleculeMatcher(mapper=mapper) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "benzene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "benzene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t2.xyz")) self.assertFalse(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "c1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "c2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "j1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "j2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "ethene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "ethene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "toluene1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "toluene2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "cyclohexane1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "cyclohexane2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mol1 = Molecule.from_file(os.path.join(test_dir, "oxygen1.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "oxygen2.xyz")) self.assertTrue(mm.fit(mol1, mol2)) mm = MoleculeMatcher(tolerance=0.001, mapper=mapper) mol1 = Molecule.from_file(os.path.join(test_dir, "t3.xyz")) mol2 = Molecule.from_file(os.path.join(test_dir, "t4.xyz")) self.assertFalse(mm.fit(mol1, mol2))
def __init__(self, int_symbol): """ Initializes a Point Group from its international symbol. Args: int_symbol (str): International or Hermann-Mauguin Symbol. """ self.symbol = int_symbol self.generators = [GENERATOR_MATRICES[c] for c in POINT_GROUP_ENC[int_symbol]] self._symmetry_ops = set([SymmOp.from_rotation_and_translation(m) for m in self._generate_full_symmetry_ops()]) self.order = len(self._symmetry_ops)
def __init__(self, int_symbol): """ Initializes a Point Group from its international symbol. Args: int_symbol (str): International or Hermann-Mauguin Symbol. """ self.symbol = int_symbol self.generators = [get_symm_data("generator_matrices")[c] for c in get_symm_data("point_group_encoding")[int_symbol]] self._symmetry_ops = set([SymmOp.from_rotation_and_translation(m) for m in self._generate_full_symmetry_ops()]) self.order = len(self._symmetry_ops)
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (latt, rot, scale2) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) self.assertTrue(np.allclose(scale2, scale) or np.allclose(scale2, -scale))
def get_symmetry_operations(self, cartesian=False): """ Return symmetry operations as a list of SymmOp objects. By default returns fractional coord symmops. But cartesian can be returned too. """ (rotation, translation) = self.get_symmetry() symmops = [] for rot, trans in zip(rotation, translation): if cartesian: rot = np.dot(self._structure.lattice.md2c, np.dot(rot, self._structure.lattice.mc2d)) trans = np.dot(self._structure.lattice.md2c, trans) symmops.append(SymmOp.from_rotation_matrix_and_translation_vector(rot, trans)) return symmops
def rotate(self, matrix, tol=1e-3): """ Applies a rotation directly, and tests input matrix to ensure a valid rotation. Args: matrix (3x3 array-like): rotation matrix to be applied to tensor tol (float): tolerance for testing rotation matrix validity """ matrix = SquareTensor(matrix) if not matrix.is_rotation(tol): raise ValueError("Rotation matrix is not valid.") sop = SymmOp.from_rotation_and_translation(matrix, [0.0, 0.0, 0.0]) return self.transform(sop)
def test_xyz(self): op = SymmOp([[1, -1, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]]) s = op.as_xyz_string() self.assertEqual(s, 'x-y, -y, -z') self.assertEqual(op, SymmOp.from_xyz_string(s)) op2 = SymmOp([[0, -1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5+1e-7], [0, 0, 0, 1]]) s2 = op2.as_xyz_string() self.assertEqual(s2, '-y+1/2, x+1/2, z+1/2') self.assertEqual(op2, SymmOp.from_xyz_string(s2)) op2 = SymmOp([[3, -2, -1, 0.5], [-1, 0, 0, 12./13], [0, 0, 1, 0.5+1e-7], [0, 0, 0, 1]]) s2 = op2.as_xyz_string() self.assertEqual(s2, '3x-2y-z+1/2, -x+12/13, z+1/2') self.assertEqual(op2, SymmOp.from_xyz_string(s2)) op3 = SymmOp.from_xyz_string('3x - 2y - z+1 /2 , -x+12/ 13, z+1/2') self.assertEqual(op2, op3) self.assertRaises(ValueError, self.op.as_xyz_string)
def _find_spherical_axes(self): """ Looks for R5, R4, R3 and R2 axes in speherical top molecules. Point group T molecules have only one unique 3-fold and one unique 2-fold axis. O molecules have one unique 4, 3 and 2-fold axes. I molecules have a unique 5-fold axis. """ rot_present = defaultdict(bool) origin_site, dist_el_sites = cluster_sites(self.centered_mol, self.tol) test_set = min(dist_el_sites.values(), key=lambda s: len(s)) coords = [s.coords for s in test_set] for c1, c2, c3 in itertools.combinations(coords, 3): for cc1, cc2 in itertools.combinations([c1, c2, c3], 2): if not rot_present[2]: test_axis = cc1 + cc2 if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation(test_axis, 180) rot_present[2] = self.is_valid_op(op) if rot_present[2]: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) test_axis = np.cross(c2 - c1, c3 - c1) if np.linalg.norm(test_axis) > self.tol: for r in (3, 4, 5): if not rot_present[r]: op = SymmOp.from_axis_angle_and_translation( test_axis, 360 / r) rot_present[r] = self.is_valid_op(op) if rot_present[r]: self.symmops.append(op) self.rot_sym.append((test_axis, r)) break if rot_present[2] and rot_present[3] and ( rot_present[4] or rot_present[5]): break
def get_rot(slab): """ Gets the transformation to rotate the z axis into the miller index """ new_z = get_mi_vec(slab) a, b, c = slab.lattice.matrix new_x = a / np.linalg.norm(a) new_y = np.cross(new_z, new_x) x, y, z = np.eye(3) rot_matrix = np.array([np.dot(*el) for el in itertools.product([x, y, z], [new_x, new_y, new_z])]).reshape(3, 3) rot_matrix = np.transpose(rot_matrix) sop = SymmOp.from_rotation_and_translation(rot_matrix) return sop
def _check_perpendicular_r2_axis(self, axis): """ Checks for R2 axes perpendicular to unique axis. For handling symmetric top molecules. """ min_set = self._get_smallest_set_not_on_axis(axis) for s1, s2 in itertools.combinations(min_set, 2): test_axis = np.cross(s1.coords - s2.coords, axis) if np.linalg.norm(test_axis) > self.tol: op = SymmOp.from_axis_angle_and_translation(test_axis, 180) r2present = self.is_valid_op(op) if r2present: self.symmops.append(op) self.rot_sym.append((test_axis, 2)) return True
def _proc_cyclic(self): """ Handles cyclic group molecules. """ main_axis, rot = max(self.rot_sym, key=lambda v: v[1]) self.sch_symbol = "C{}".format(rot) mirror_type = self._find_mirror(main_axis) if mirror_type == "h": self.sch_symbol += "h" elif mirror_type == "v": self.sch_symbol += "v" elif mirror_type == "": if self.is_valid_op(SymmOp.rotoreflection(main_axis, angle=180 / rot)): self.sch_symbol = "S{}".format(2 * rot)
def __init__(self, axis, angle, angle_in_radians=False): """ Args: axis: Axis of rotation, e.g., [1, 0, 0] angle: Angle to rotate angle_in_radians: Set to True if angle is supplied in radians. Else degrees are assumed. """ self._axis = axis self._angle = angle self._angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation(self._axis, self._angle, self._angle_in_radians)
def _align_monomer(self, monomer, mon_vector, move_direction): """ rotate the monomer so that it is aligned along the move direction Args: monomer (Molecule) mon_vector (numpy.array): molecule vector that starts from the start atom index to the end atom index move_direction (numpy.array): the direction of the polymer chain extension """ axis = np.cross(mon_vector, move_direction) origin = monomer[self.start].coords angle = get_angle(mon_vector, move_direction) op = SymmOp.from_origin_axis_angle(origin, axis, angle) monomer.apply_operation(op)
def get_point_group_operations(self, cartesian=False): """ Return symmetry operations as a list of SymmOp objects. By default returns fractional coord symmops. But cartesian can be returned too. """ (rotation, translation) = self._get_symmetry() symmops = [] mat = self._structure.lattice.matrix.T invmat = np.linalg.inv(mat) for rot, trans in zip(rotation, translation): if cartesian: rot = np.dot(mat, np.dot(rot, invmat)) op = SymmOp.from_rotation_and_translation(rot, np.array([0, 0, 0])) symmops.append(op) return symmops
def __init__(self, axis, angle, angle_in_radians=False): """ Args: axis: Axis of rotation, e.g., [1, 0, 0] angle: Angle to rotate angle_in_radians: Set to True if angle is supplied in radians. Else degrees are assumed. """ self._axis = axis self._angle = angle self._angle_in_radians = angle_in_radians self._symmop = SymmOp.from_axis_angle_and_translation( self._axis, self._angle, self._angle_in_radians)
def get_rot(slab): """ Gets the transformation to rotate the z axis into the miller index """ new_z = get_mi_vec(slab) a, b, c = slab.lattice.matrix new_x = a / np.linalg.norm(a) new_y = np.cross(new_z, new_x) x, y, z = np.eye(3) rot_matrix = np.array([ np.dot(*el) for el in itertools.product([x, y, z], [new_x, new_y, new_z]) ]).reshape(3, 3) rot_matrix = np.transpose(rot_matrix) sop = SymmOp.from_rotation_and_translation(rot_matrix) return sop
def _align_monomer(self, monomer, mon_vector, move_direction): """ rotate the monomer so that it is aligned along the move direction Args: monomer (Molecule) mon_vector (numpy.array): molecule vector that starts from the start atom index to the end atom index move_direction (numpy.array): the direction of the polymer chain extension """ axis = np.cross(mon_vector, move_direction) origin = monomer[self.start].coords angle = get_angle(mon_vector, move_direction) op = SymmOp.from_origin_axis_angle(origin, axis, angle) monomer.apply_operation(op)
def __init__(self, int_symbol): """ Initializes a Point Group from its international symbol. Args: int_symbol (str): International or Hermann-Mauguin Symbol. """ self.symbol = int_symbol self.generators = [ GENERATOR_MATRICES[c] for c in POINT_GROUP_ENC[int_symbol] ] self.symmetry_ops = [ SymmOp.from_rotation_and_translation(m) for m in self._generate_full_symmetry_ops() ] self.order = len(self.symmetry_ops)
def test_rotated_molecule(self): coords = [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 1.089000], [1.026719, 0.000000, -0.363000], [-0.513360, -0.889165, -0.363000], [-0.513360, 0.889165, -0.363000]] op = SymmOp.from_origin_axis_angle([0, 0, 0], [0.1, 0.2, 0.3], 60) rotcoords = [op.operate(c) for c in coords] mol1 = Molecule(["C", "H", "H", "H", "H"], coords) mol2 = Molecule(["C", "H", "H", "H", "H"], rotcoords) mm = GeneticOrderMatcher(mol1, threshold=0.3) _, rmsd = mm.fit(mol2)[0] self.assertAlmostEqual(rmsd, 0., places=6)
def _analyze(self): if len(self.centered_mol) == 1: self.sch_symbol = "Kh" else: inertia_tensor = np.zeros((3, 3)) total_inertia = 0 for site in self.mol: c = site.coords wt = site.species_and_occu.weight for i in range(3): inertia_tensor[i, i] += wt * (c[(i + 1) % 3]**2 + c[(i + 2) % 3]**2) for i, j in itertools.combinations(list(range(3)), 2): inertia_tensor[i, j] += -wt * c[i] * c[j] inertia_tensor[j, i] += -wt * c[j] * c[i] total_inertia += wt * np.dot(c, c) # Normalize the inertia tensor so that it does not scale with size # of the system. This mitigates the problem of choosing a proper # comparison tolerance for the eigenvalues. inertia_tensor /= total_inertia eigvals, eigvecs = np.linalg.eig(inertia_tensor) self.principal_axes = eigvecs.T self.eigvals = eigvals v1, v2, v3 = eigvals eig_zero = abs(v1 * v2 * v3) < self.eig_tol**3 eig_all_same = abs(v1 - v2) < self.eig_tol and abs( v1 - v3) < self.eig_tol eig_all_diff = abs(v1 - v2) > self.eig_tol and abs( v1 - v3) > self.eig_tol and abs(v2 - v3) > self.eig_tol self.rot_sym = [] self.symmops = [SymmOp(np.eye(4))] if eig_zero: logger.debug("Linear molecule detected") self._proc_linear() elif eig_all_same: logger.debug("Spherical top molecule detected") self._proc_sph_top() elif eig_all_diff: logger.debug("Asymmetric top molecule detected") self._proc_asym_top() else: logger.debug("Symmetric top molecule detected") self._proc_sym_top()
def test_transform(self): # Rank 3 tensor = TensorBase(np.arange(0, 27).reshape(3, 3, 3)) symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) new_tensor = tensor.transform(symm_op) self.assertArrayAlmostEqual(new_tensor, [[[-0.871, -2.884, -1.928], [-2.152, -6.665, -4.196], [-1.026, -2.830, -1.572]], [[0.044, 1.531, 1.804], [4.263, 21.008, 17.928], [5.170, 23.026, 18.722]], [[1.679, 7.268, 5.821], [9.268, 38.321, 29.919], [8.285, 33.651, 26.000]]], 3)
def __init__(self, int_symbol): """ Initializes a Point Group from its international symbol. Args: int_symbol (str): International or Hermann-Mauguin Symbol. """ self.symbol = int_symbol self.generators = [ _get_symm_data("generator_matrices")[c] for c in _get_symm_data("point_group_encoding")[int_symbol] ] self._symmetry_ops = { SymmOp.from_rotation_and_translation(m) for m in self._generate_full_symmetry_ops() } self.order = len(self._symmetry_ops)
def get_op(self, angle="random"): """ Generate a SymmOp object consistent with the orientation's constraints. Allows for specification of an angle (possibly random) to rotate about the constraint axis. Args: angle: an angle to rotate about the constraint axis. If "random", chooses a random rotation angle. If self.degrees==2, chooses a random 3d rotation matrix to multiply by. If the original matrix is wanted, set angle=0, or call self.matrix Returns: pymatgen.core.structure. SymmOp object """ #If "random", rotates by a random amount m = self.get_matrix(angle=angle) return SymmOp.from_rotation_and_translation(m,[0,0,0])
def test_xyzt_string(self): xyzt_strings = [ 'x, y, z, +1', 'x, y, z, -1', '-y+1/2, x+1/2, x+1/2, +1' ] for xyzt_string in xyzt_strings: op = MagSymmOp.from_xyzt_string(xyzt_string) xyzt_string_out = op.as_xyzt_string() self.assertEqual(xyzt_string, xyzt_string_out) op = SymmOp([[3, -2, -1, 0.5], [-1, 0, 0, 12. / 13], [0, 0, 1, 0.5 + 1e-7], [0, 0, 0, 1]]) magop = MagSymmOp.from_symmop(op, -1) magop_str = magop.as_xyzt_string() self.assertEqual(magop.time_reversal, -1) self.assertEqual(magop_str, '3x-2y-z+1/2, -x+12/13, z+1/2, -1')
def _check_rot_sym(self, axis): """ Determines the rotational symmetry about supplied axis. Used only for symmetric top molecules which has possible rotational symmetry operations > 2. """ min_set = self._get_smallest_set_not_on_axis(axis) max_sym = len(min_set) for i in range(max_sym, 0, -1): if max_sym % i != 0: continue op = SymmOp.from_axis_angle_and_translation(axis, 360 / i) rotvalid = self.is_valid_op(op) if rotvalid: self.symmops.append(op) self.rot_sym.append((axis, i)) return i return 1
def get_wyckoff_symmetry(sg): ''' Returns a list of Wyckoff position site symmetry for a given space group. 1st index: index of WP in sg (0 is the WP with largest multiplicity) 2nd index: a point within the WP 3rd index: a site symmetry SymmOp of the point ''' symmetry_strings = eval(wyckoff_symmetry_df["0"][sg]) symmetry = [] #Loop over Wyckoff positions for x in symmetry_strings: symmetry.append([]) #Loop over points in WP for y in x: symmetry[-1].append([]) #Loop over for z in y: symmetry[-1][-1].append(SymmOp.from_xyz_string(z)) return symmetry
def project_point(point, op, lattice=Euclidean_lattice, PBC=[1,1,1]): """ Given a 3-vector and a Wyckoff position operator, returns the projection of that point onto the axis, plane, or point. Args: point: a 3-vector (numeric list, tuple, or array) op: a SymmOp object representing a symmetry element within a symmetry group lattice: 3x3 matrix describing the unit cell vectors PBC: A periodic boundary condition list, where 1 means periodic, 0 means not periodic. Ex: [1,1,1] -> full 3d periodicity, [0,0,1] -> periodicity along the z axis Returns: a transformed 3-vector (numpy array) """ if PBC == [0,0,0]: #Temporarily move the point in the opposite direction of the translation translation = op.translation_vector point -= translation new_vector = np.array([0.0,0.0,0.0]) #Loop over basis vectors of the symmetry element for basis_vector in np.transpose(op.rotation_matrix): b = np.linalg.norm(basis_vector) if not np.isclose(b, 0): new_vector += basis_vector*(np.dot(point, basis_vector)/(b**2)) new_vector += translation return new_vector else: #With PBC, the point could be projected onto multiple places on the symmetry element point = filtered_coords(point) #Generate translation vectors for the equivalent symmetry elements m = create_matrix(PBC=PBC) #Create new symmetry elements for each of the PBC vectors new_vectors = [] distances = [] for v in m: #Get the direct projection onto each of the new symmetry elements new_op = SymmOp.from_rotation_and_translation(op.rotation_matrix, op.translation_vector + v) new_vector = project_point(point, new_op, lattice=lattice, PBC=[0,0,0]) new_vectors.append(new_vector) distances.append(distance(new_vector - point, lattice=lattice, PBC=[0,0,0])) i = np.argmin(distances) return filtered_coords(new_vectors[i], PBC=PBC)
def get_shared_symmetry_operations(struc, pointops, tol=0.1): """ Get all the point group operations shared by a pair of atomic sites in the form [[point operations of site index 1],[],...,[]] Args: struc: Pymatgen structure pointops: list of point group operations from get_site_symmetries method Return: list of lists of shared point operations for each pair of atomic sites """ numsites = len(struc) sharedops = [[0 for x in range(numsites)] for y in range(numsites)] for site1 in range(numsites): for site2 in range(numsites): sharedops[site1][site2] = [] for op1 in range(len(pointops[site1])): for op2 in range(len(pointops[site2])): if np.allclose( pointops[site1][op1].rotation_matrix, pointops[site2][op2].rotation_matrix, ): sharedops[site1][site2].append(pointops[site1][op1]) for site1 in range(len(sharedops)): for site2 in range(len(sharedops[site1])): uniqueops = [] for ops in range(len(sharedops[site1][site2])): op = SymmOp.from_rotation_and_translation( rotation_matrix=sharedops[site1][site2] [ops].rotation_matrix, translation_vec=(0, 0, 0), tol=tol, ) if op in uniqueops: continue else: uniqueops.append(op) sharedops[site1][site2] = uniqueops return sharedops
def get_symmetry_operations(self, cartesian=False): """ Return symmetry operations as a list of SymmOp objects. By default returns fractional coord symmops. But cartesian can be returned too. Returns: ([SymmOp]): List of symmetry operations. """ rotation, translation = self._get_symmetry() symmops = [] mat = self._structure.lattice.matrix.T invmat = np.linalg.inv(mat) for rot, trans in zip(rotation, translation): if cartesian: rot = np.dot(mat, np.dot(rot, invmat)) trans = np.dot(trans, self._structure.lattice.matrix) op = SymmOp.from_rotation_and_translation(rot, trans) symmops.append(op) return symmops
def transform_symmop(self, symmop): # type: (Union[SymmOp, MagSymmOp]) -> Union[SymmOp, MagSymmOp] """ Takes a symmetry operation and transforms it. :param symmop: SymmOp or MagSymmOp :return: """ W = symmop.rotation_matrix w = symmop.translation_vector Q = np.linalg.inv(self.P) W_ = np.matmul(np.matmul(Q, W), self.P) I = np.identity(3) w_ = np.matmul(Q, (w + np.matmul(W - I, self.p))) if isinstance(symmop, MagSymmOp): return MagSymmOp.from_rotation_and_translation_and_time_reversal(rotation_matrix=W_, translation_vec=w_, time_reversal=symmop.time_reversal, tol=symmop.tol) elif isinstance(symmop, SymmOp): return SymmOp.from_rotation_and_translation(rotation_matrix=W_, translation_vec=w_, tol=symmop.tol)
def test_inverse(self): coord0 = [0.35, 0.1, 0.4] coords = np.array([ [0.350, 0.100, 0.400], [0.350, 0.100, 0.000], [0.350, 0.100, 0.000], [0.350, 0.000, 0.667], [0.350, 0.000, 0.250], [0.350, 0.350, 0.400], [0.350, 0.350, 0.500], [0.350, 0.350, 0.000], [0.350, 0.350, 0.350], [0.100, 0.100, 0.100], [0.400, 0.400, 0.400], [0.350, 0.000, 0.000], [0.000, 0.100, 0.400], [0.350, 0.000, 0.400], ]) xyzs = [ 'x,y,z', 'x,y,0', 'y,x,0', 'x,0,2/3', '0,x,1/4', 'x,x,z', 'x,-x,1/2', '2x,x,0', '-2x,-0.5x,-x+1/4', '-2y,-0.5y,-y+1/4', '-2z,-0.5z,-z+1/4', '0,0,x', '-y/2+1/2,-z,0', '-z,-x/2+1/2,0', ] for i, xyz in enumerate(xyzs): op = SymmOp.from_xyz_string(xyz) inv_op = get_inverse(op) coord1 = op.operate(coord0) coord2 = inv_op.operate(coord1) self.assertTrue(np.allclose(coord2, coords[i], rtol=1e-2))
def get_symmops(structure, symprec): """ Helper function to get the symmetry operations of the structure in the reciprocal lattice fractional coordinates. Args: structure (pymatgen.core.structure.Structure) symprec (number): symmetry precision for pymatgen SpacegroupAnalyzer """ sga = SpacegroupAnalyzer(structure, symprec * max(structure.lattice.abc)) symmops = sga.get_symmetry_operations(cartesian=True) lattice = structure.lattice.matrix invlattice = structure.lattice.inv_matrix newops = [] for op in symmops: newrot = np.dot(lattice, op.rotation_matrix) newrot = np.dot(newrot, invlattice) newtrans = np.dot(op.translation_vector, invlattice) newops.append(SymmOp.from_rotation_and_translation(newrot, newtrans)) return newops
def rotate_mols(self): """ rotate the molecules wrt each other using the provided info """ # rotate the molecules around an axis that is # perpendicular to the molecular axes if self.angle: for mol in range(len(self.mols)): for ind_key, rot in self.angle[str(mol)].items(): perp_vec = np.cross(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) # if the vectors are parllel, # then perp_vec = (-y, x, 0) if np.abs(np.dot(self.mol_vecs[int(ind_key)], self.mol_vecs[mol]) - \ np.linalg.norm(self.mol_vecs[mol]) ** 2) < 1e-6: perp_vec = np.array([-self.mol_vecs[mol][1], self.mol_vecs[mol][0], 0]) org_pt = self.vec_indices[mol][0] op = SymmOp.from_origin_axis_angle( self.mols[mol].cart_coords[org_pt], axis=perp_vec, angle=rot) self.mols[mol].apply_operation(op)
def get_point_group_operations(self, cartesian=False): """ Return symmetry operations as a list of SymmOp objects. By default returns fractional coord symmops. But cartesian can be returned too. Args: cartesian (bool): Whether to return SymmOps as cartesian or direct coordinate operations. Returns: ([SymmOp]): List of point group symmetry operations. """ rotation, translation = self._get_symmetry() symmops = [] mat = self._structure.lattice.matrix.T invmat = np.linalg.inv(mat) for rot in rotation: if cartesian: rot = np.dot(mat, np.dot(rot, invmat)) op = SymmOp.from_rotation_and_translation(rot, np.array([0, 0, 0])) symmops.append(op) return symmops
def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix, 5) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.parameters, latt2.parameters) self.assertFalse(np.allclose(aligned_out.parameters, latt.parameters)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16) # catch the singular matrix error latt = Lattice.from_parameters(1, 1, 1, 10, 10, 10) for l, _, _ in latt.find_all_mappings(latt, ltol=0.05, atol=11): self.assertTrue(isinstance(l, Lattice))
def test_find_all_mappings(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt2.lengths_and_angles) self.assertFalse( np.allclose(aligned_out.lengths_and_angles, latt.lengths_and_angles)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16)
def align_c_axis_along_001(structure): """ Given a structure with a c-axis not along [001], returns the same structure rotated so that the c-axis is along the [001] direction. This is useful for vasp compiled with no z-axis relaxation. Args: structure (Structure): Pymatgen Structure object to rotate. Returns: structure. Rotated to align c-axis along [001]. """ c = structure.lattice._matrix[2] z = [0, 0, 1] axis = np.cross(c, z) if not(axis[0] == 0 and axis[1] == 0): theta = (np.arccos(np.dot(c, z) / (np.linalg.norm(c) * np.linalg.norm(z)))) R = get_rotation_matrix(axis, theta) rotation = SymmOp.from_rotation_and_translation(rotation_matrix=R) structure.apply_operation(rotation) return structure
def get_wyckoffs(sg, organized=False): ''' Returns a list of Wyckoff positions for a given space group. 1st index: index of WP in sg (0 is the WP with largest multiplicity) 2nd index: a SymmOp object in the WP ''' wyckoff_strings = eval(wyckoff_df["0"][sg]) wyckoffs = [] for x in wyckoff_strings: wyckoffs.append([]) for y in x: wyckoffs[-1].append(SymmOp.from_xyz_string(y)) if organized: wyckoffs_organized = [[]] #2D Array of WP's organized by multiplicity old = len(wyckoffs[0]) for wp in wyckoffs: mult = len(wp) if mult != old: wyckoffs_organized.append([]) old = mult wyckoffs_organized[-1].append(wp) return wyckoffs_organized else: return wyckoffs
def cover_surface(self, site_indices): """ puts the ligand molecule on the given list of site indices """ num_atoms = len(self.ligand) normal = self.normal # get a vector that points from one atom in the botton plane # to one atom on the top plane. This is required to make sure # that the surface normal points outwards from the surface on # to which we want to adsorb the ligand vec_vac = self.cart_coords[self.top_atoms[0]] - \ self.cart_coords[self.bottom_atoms[0]] # mov_vec = the vector along which the ligand will be displaced mov_vec = normal * self.displacement angle = get_angle(vec_vac, self.normal) # flip the orientation of normal if it is not pointing in # the right direction. if (angle > 90): normal_frac = self.lattice.get_fractional_coords(normal) normal_frac[2] = -normal_frac[2] normal = self.lattice.get_cartesian_coords(normal_frac) mov_vec = normal * self.displacement # get the index corresponding to the given atomic species in # the ligand that will bond with the surface on which the # ligand will be adsorbed adatom_index = self.get_index(self.adatom_on_lig) adsorbed_ligands_coords = [] # set the ligand coordinates for each adsorption site on # the surface for sindex in site_indices: # align the ligand wrt the site on the surface to which # it will be adsorbed origin = self.cart_coords[sindex] self.ligand.translate_sites( list(range(num_atoms)), origin - self.ligand[adatom_index].coords) # displace the ligand by the given amount in the direction # normal to surface self.ligand.translate_sites(list(range(num_atoms)), mov_vec) # vector pointing from the adatom_on_lig to the # ligand center of mass vec_adatom_cm = self.ligand.center_of_mass - \ self.ligand[adatom_index].coords # rotate the ligand with respect to a vector that is # normal to the vec_adatom_cm and the normal to the surface # so that the ligand center of mass is aligned along the # outward normal to the surface origin = self.ligand[adatom_index].coords angle = get_angle(vec_adatom_cm, normal) if 1 < abs(angle % 180) < 179: # For angles which are not 0 or 180, # perform a rotation about the origin along an axis # perpendicular to both bonds to align bonds. axis = np.cross(vec_adatom_cm, normal) op = SymmOp.from_origin_axis_angle(origin, axis, angle) self.ligand.apply_operation(op) elif abs(abs(angle) - 180) < 1: # We have a 180 degree angle. # Simply do an inversion about the origin for i in range(len(self.ligand)): self.ligand[i] = (self.ligand[i].species_and_occu, origin - (self.ligand[i].coords - origin)) # x - y - shifts x = self.x_shift y = self.y_shift rot = self.rot if x: self.ligand.translate_sites(list(range(num_atoms)), np.array([x, 0, 0])) if y: self.ligand.translate_sites(list(range(num_atoms)), np.array([0, y, 0])) if rot: self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (1, 0, 0), rot[0], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 1, 0), rot[1], angle_in_radians=False, translation_vec=(0, 0, 0))) self.ligand.apply_operation( SymmOp.from_axis_angle_and_translation( (0, 0, 1), rot[2], angle_in_radians=False, translation_vec=(0, 0, 0))) # 3d numpy array adsorbed_ligands_coords.append(self.ligand.cart_coords) # extend the slab structure with the adsorbant atoms adsorbed_ligands_coords = np.array(adsorbed_ligands_coords) for j in range(len(site_indices)): [ self.append(self.ligand.species_and_occu[i], adsorbed_ligands_coords[j, i, :], coords_are_cartesian=True) for i in range(num_atoms) ]
def __init__(self, int_symbol): """ Initializes a Space Group from its full or abbreviated international symbol. Only standard settings are supported. Args: int_symbol (str): Full International (e.g., "P2/m2/m2/m") or Hermann-Mauguin Symbol ("Pmmm") or abbreviated symbol. The notation is a LaTeX-like string, with screw axes being represented by an underscore. For example, "P6_3/mmc". Alternative settings can be access by adding a ":identifier". For example, the hexagonal setting for rhombohedral cells can be accessed by adding a ":H", e.g., "R-3m:H". To find out all possible settings for a spacegroup, use the get_settings classmethod. Alternative origin choices can be indicated by a translation vector, e.g., 'Fm-3m(a-1/4,b-1/4,c-1/4)'. """ int_symbol = re.sub(r" ", "", int_symbol) if int_symbol in SpaceGroup.abbrev_sg_mapping: int_symbol = SpaceGroup.abbrev_sg_mapping[int_symbol] elif int_symbol in SpaceGroup.full_sg_mapping: int_symbol = SpaceGroup.full_sg_mapping[int_symbol] for spg in SpaceGroup.SYMM_OPS: if int_symbol in [spg["hermann_mauguin"], spg["universal_h_m"]]: ops = [SymmOp.from_xyz_string(s) for s in spg["symops"]] self.symbol = re.sub(r":", "", re.sub(r" ", "", spg["universal_h_m"])) if int_symbol in SpaceGroup.sgencoding: self.full_symbol = SpaceGroup.sgencoding[int_symbol][ "full_symbol"] self.point_group = SpaceGroup.sgencoding[int_symbol][ "point_group"] else: self.full_symbol = re.sub(r" ", "", spg["universal_h_m"]) self.point_group = spg["schoenflies"] self.int_number = spg["number"] self.order = len(ops) self._symmetry_ops = ops break else: if int_symbol not in SpaceGroup.sgencoding: raise ValueError("Bad international symbol %s" % int_symbol) data = SpaceGroup.sgencoding[int_symbol] self.symbol = int_symbol # TODO: Support different origin choices. enc = list(data["enc"]) inversion = int(enc.pop(0)) ngen = int(enc.pop(0)) symm_ops = [np.eye(4)] if inversion: symm_ops.append( np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])) for i in range(ngen): m = np.eye(4) m[:3, :3] = SpaceGroup.gen_matrices[enc.pop(0)] m[0, 3] = SpaceGroup.translations[enc.pop(0)] m[1, 3] = SpaceGroup.translations[enc.pop(0)] m[2, 3] = SpaceGroup.translations[enc.pop(0)] symm_ops.append(m) self.generators = symm_ops self.full_symbol = data["full_symbol"] self.point_group = data["point_group"] self.int_number = data["int_number"] self.order = data["order"] self._symmetry_ops = None
def test_fit(self): """ Take two known matched structures 1) Ensure match 2) Ensure match after translation and rotations 3) Ensure no-match after large site translation 4) Ensure match after site shuffling """ sm = StructureMatcher() self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) # Test rotational/translational invariance op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, np.array([0.4, 0.7, 0.9])) self.struct_list[1].apply_operation(op) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test failure under large atomic translation self.struct_list[1].translate_sites([0], [.4, .4, .2], frac_coords=True) self.assertFalse(sm.fit(self.struct_list[0], self.struct_list[1])) self.struct_list[1].translate_sites([0], [-.4, -.4, -.2], frac_coords=True) # random.shuffle(editor._sites) self.assertTrue(sm.fit(self.struct_list[0], self.struct_list[1])) #Test FrameworkComporator sm2 = StructureMatcher(comparator=FrameworkComparator()) lfp = self.get_structure("LiFePO4") nfp = self.get_structure("NaFePO4") self.assertTrue(sm2.fit(lfp, nfp)) self.assertFalse(sm.fit(lfp, nfp)) #Test anonymous fit. self.assertEqual(sm.fit_anonymous(lfp, nfp), True) self.assertAlmostEqual( sm.get_rms_anonymous(lfp, nfp)[0], 0.060895871160262717) #Test partial occupancies. s1 = Structure(Lattice.cubic(3), [{ "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.5 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) s2 = Structure(Lattice.cubic(3), [{ "Fe": 0.25 }, { "Fe": 0.5 }, { "Fe": 0.5 }, { "Fe": 0.75 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertFalse(sm.fit(s1, s2)) self.assertFalse(sm.fit(s2, s1)) s2 = Structure(Lattice.cubic(3), [{ "Mn": 0.5 }, { "Mn": 0.5 }, { "Mn": 0.5 }, { "Mn": 0.5 }], [[0, 0, 0], [0.25, 0.25, 0.25], [0.5, 0.5, 0.5], [0.75, 0.75, 0.75]]) self.assertEqual(sm.fit_anonymous(s1, s2), True) self.assertAlmostEqual(sm.get_rms_anonymous(s1, s2)[0], 0)
def generate_doc(self, dir_name, vasprun_files, outcar_files): """ Adapted from matgendb.creator.generate_doc """ try: # basic properties, incl. calcs_reversed and run_stats fullpath = os.path.abspath(dir_name) d = {k: v for k, v in self.additional_fields.items()} d["schema"] = {"code": "atomate", "version": VaspDrone.__version__} d["dir_name"] = fullpath d["calcs_reversed"] = [self.process_vasprun(dir_name, taskname, filename) for taskname, filename in vasprun_files.items()] outcar_data = [Outcar(os.path.join(dir_name, filename)).as_dict() for taskname, filename in outcar_files.items()] run_stats = {} for i, d_calc in enumerate(d["calcs_reversed"]): run_stats[d_calc["task"]["name"]] = outcar_data[i].pop("run_stats") if d_calc.get("output"): d_calc["output"].update({"outcar": outcar_data[i]}) else: d_calc["output"] = {"outcar": outcar_data[i]} try: overall_run_stats = {} for key in ["Total CPU time used (sec)", "User time (sec)", "System time (sec)", "Elapsed time (sec)"]: overall_run_stats[key] = sum([v[key] for v in run_stats.values()]) run_stats["overall"] = overall_run_stats except: logger.error("Bad run stats for {}.".format(fullpath)) d["run_stats"] = run_stats # reverse the calculations data order so newest calc is first d["calcs_reversed"].reverse() # set root formula/composition keys based on initial and final calcs d_calc_init = d["calcs_reversed"][-1] d_calc_final = d["calcs_reversed"][0] d["chemsys"] = "-".join(sorted(d_calc_final["elements"])) comp = Composition(d_calc_final["composition_unit_cell"]) d["formula_anonymous"] = comp.anonymized_formula d["formula_reduced_abc"] = comp.reduced_composition.alphabetical_formula for root_key in ["completed_at", "nsites", "composition_unit_cell", "composition_reduced", "formula_pretty", "elements", "nelements"]: d[root_key] = d_calc_final[root_key] # store the input key based on initial calc # store any overrides to the exchange correlation functional xc = d_calc_init["input"]["incar"].get("GGA") if xc: xc = xc.upper() p = d_calc_init["input"]["potcar_type"][0].split("_") pot_type = p[0] functional = "lda" if len(pot_type) == 1 else "_".join(p[1:]) d["input"] = {"structure": d_calc_init["input"]["structure"], "is_hubbard": d_calc_init.pop("is_hubbard"), "hubbards": d_calc_init.pop("hubbards"), "is_lasph": d_calc_init["input"]["incar"].get("LASPH", False), "potcar_spec": d_calc_init["input"].get("potcar_spec"), "xc_override": xc, "pseudo_potential": {"functional": functional.lower(), "pot_type": pot_type.lower(), "labels": d_calc_init["input"]["potcar"]}, "parameters": d_calc_init["input"]["parameters"], "incar": d_calc_init["input"]["incar"] } # store the output key based on final calc d["output"] = { "structure": d_calc_final["output"]["structure"], "density": d_calc_final.pop("density"), "energy": d_calc_final["output"]["energy"], "energy_per_atom": d_calc_final["output"]["energy_per_atom"], "forces": d_calc_final["output"]["ionic_steps"][-1].get("forces"), "stress": d_calc_final["output"]["ionic_steps"][-1].get("stress")} # patch calculated magnetic moments into final structure if len(d_calc_final["output"]["outcar"]["magnetization"]) != 0: magmoms = [m["tot"] for m in d_calc_final["output"]["outcar"]["magnetization"]] s = Structure.from_dict(d["output"]["structure"]) s.add_site_property('magmom', magmoms) d["output"]["structure"] = s.as_dict() calc = d["calcs_reversed"][0] try: d["output"].update({"bandgap": calc["output"]["bandgap"], "cbm": calc["output"]["cbm"], "vbm": calc["output"]["vbm"], "is_gap_direct": calc["output"]["is_gap_direct"], "is_metal": calc["output"]["is_metal"]}) if not calc["output"]["is_gap_direct"]: d["output"]["direct_gap"] = calc["output"]["direct_gap"] if "transition" in calc["output"]: d["output"]["transition"] = calc["output"]["transition"] except Exception: if self.bandstructure_mode is True: import traceback logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise sg = SpacegroupAnalyzer(Structure.from_dict(d_calc_final["output"]["structure"]), 0.1) if not sg.get_symmetry_dataset(): sg = SpacegroupAnalyzer(Structure.from_dict(d_calc_final["output"]["structure"]), 1e-3, 1) d["output"]["spacegroup"] = { "source": "spglib", "symbol": sg.get_space_group_symbol(), "number": sg.get_space_group_number(), "point_group": sg.get_point_group_symbol(), "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall()} if d["input"]["parameters"].get("LEPSILON"): for k in ['epsilon_static', 'epsilon_static_wolfe', 'epsilon_ionic']: d["output"][k] = d_calc_final["output"][k] if SymmOp.inversion() not in sg.get_symmetry_operations(): for k in ["piezo_ionic_tensor", "piezo_tensor"]: d["output"][k] = d_calc_final["output"]["outcar"][k] d["state"] = "successful" if d_calc["has_vasp_completed"] else "unsuccessful" self.set_analysis(d) d["last_updated"] = datetime.datetime.today() return d except Exception: import traceback logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise
def test_list_based_functions(self): # zeroed tc = TensorCollection([1e-4 * Tensor(np.eye(3))] * 4) for t in tc.zeroed(): self.assertArrayEqual(t, np.zeros((3, 3))) for t in tc.zeroed(1e-5): self.assertArrayEqual(t, 1e-4 * np.eye(3)) self.list_based_function_check("zeroed", tc) self.list_based_function_check("zeroed", tc, tol=1e-5) # transform symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) self.list_based_function_check("transform", self.seq_tc, symm_op=symm_op) # symmetrized self.list_based_function_check("symmetrized", self.seq_tc) # rotation a = 3.14 * 42.5 / 180 rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.list_based_function_check("rotate", self.diff_rank, matrix=rotation) # is_symmetric self.assertFalse(self.seq_tc.is_symmetric()) self.assertTrue(self.diff_rank.is_symmetric()) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # voigt self.list_based_function_check("voigt", self.diff_rank) # is_voigt_symmetric self.assertTrue(self.diff_rank.is_voigt_symmetric()) self.assertFalse(self.seq_tc.is_voigt_symmetric()) # Convert to ieee for entry in self.ieee_data[:2]: xtal = entry['xtal'] tc = TensorCollection([entry['original_tensor']] * 3) struct = Structure.from_dict(entry['structure']) self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt tc_input = [t for t in np.random.random((3, 6, 6))] tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)