def site_symm(point, gen_pos, tol=1e-3, lattice=Euclidean_lattice): ''' Given gen_pos (a list of SymmOps), return the list of symmetry operations leaving a point (coordinate or SymmOp) invariant. ''' #Convert point into a SymmOp if type(point) != SymmOp: point = SymmOp.from_rotation_and_translation([[0,0,0],[0,0,0],[0,0,0]], point) symmetry = [] for op in gen_pos: is_symmetry = True #Calculate the effect of applying op to point difference = SymmOp(point.affine_matrix - (op*point).affine_matrix) #Check that the rotation matrix is unaltered by op if not np.allclose(difference.rotation_matrix, np.zeros((3,3)), rtol = 1e-3, atol = 1e-3): is_symmetry = False #Check that the displacement is less than tol displacement = difference.translation_vector if distance(displacement, lattice) > tol: is_symmetry = False if is_symmetry: '''The actual site symmetry's translation vector may vary from op by a factor of +1 or -1 (especially when op contains +-1/2). We record this to distinguish between special Wyckoff positions. As an example, consider the point (-x+1/2,-x,x+1/2) in position 16c of space group Ia-3(206). The site symmetry includes the operations (-z+1,x-1/2,-y+1/2) and (y+1/2,-z+1/2,-x+1). These operations are not listed in the general position, but correspond to the operations (-z,x+1/2,-y+1/2) and (y+1/2,-z+1/2,-x), respectively, just shifted by (+1,-1,0) and (0,0,+1), respectively. ''' el = SymmOp.from_rotation_and_translation(op.rotation_matrix, op.translation_vector + np.round(displacement)) symmetry.append(el) return symmetry
def get_jmol2(self): from pymatgen.core.operations import SymmOp needs_shift = False structure = StructureP.from_file( BASE_DIR + self.entry.path.split('/var/www/materialsweb')[-1] + '/POSCAR') if structure.lattice.a == max(structure.lattice.abc): translation = SymmOp.from_rotation_and_translation( translation_vec=(structure.lattice.a / 2, 0, 0)) for site in structure.sites: if site._frac_coords[0] > 0.9 or site._frac_coords[0] < 0.1: needs_shift = True if needs_shift: structure.apply_operation(translation) structure.make_supercell([1, 6, 6]) elif structure.lattice.b == max(structure.lattice.abc): translation = SymmOp.from_rotation_and_translation( translation_vec=(0, structure.lattice.b / 2, 0)) for site in structure.sites: if site._coords[1] > 0.9 or site._coords[1] < 0.1: needs_shift = True if needs_shift: structure.apply_operation(translation) structure.make_supercell([6, 1, 6]) else: translation = SymmOp.from_rotation_and_translation( translation_vec=(0, 0, structure.lattice.c / 2)) for site in structure.sites: if site._frac_coords[2] > 0.9 or site._frac_coords[2] < 0.1: needs_shift = True if needs_shift: structure.apply_operation(translation) structure.make_supercell([6, 6, 1]) print('frac_coord: ' + str(site._frac_coords[2])) print(structure.lattice.b) xyz_structure = [ str(structure.num_sites), structure.composition.reduced_formula ] for site in structure.sites: element = site._species.reduced_formula.replace('2', '') atom = '{} {} {} {}'.format(element, str(site.x), str(site.y), str(site.z)) xyz_structure.append(atom) #return '+'.join(xyz_structure) string = str(xyz_structure) string = string.replace('[', '') string = string.replace(']', '') string = string.replace(', ', r'\n') string = string.replace("'", "") return string
def combine_slabs(self): """ Combine the slabs generated by get_oriented_slabs into interfaces """ all_substrate_variants = [] sub_labels = [] for i, slab in enumerate(self.modified_substrate_structures): all_substrate_variants.append(slab) sub_labels.append(str(i)) sg = SpacegroupAnalyzer(slab, symprec=1e-3) if not sg.is_laue(): mirrored_slab = slab.copy() reflection_z = SymmOp.from_rotation_and_translation( ((1, 0, 0), (0, 1, 0), (0, 0, -1)), (0, 0, 0)) mirrored_slab.apply_operation(reflection_z, fractional=True) translation = [0, 0, -min(mirrored_slab.frac_coords[:, 2])] mirrored_slab.translate_sites(range(mirrored_slab.num_sites), translation) all_substrate_variants.append(mirrored_slab) sub_labels.append('%dm' % i) all_film_variants = [] film_labels = [] for i, slab in enumerate(self.modified_film_structures): all_film_variants.append(slab) film_labels.append(str(i)) sg = SpacegroupAnalyzer(slab, symprec=1e-3) if not sg.is_laue(): mirrored_slab = slab.copy() reflection_z = SymmOp.from_rotation_and_translation( ((1, 0, 0), (0, 1, 0), (0, 0, -1)), (0, 0, 0)) mirrored_slab.apply_operation(reflection_z, fractional=True) translation = [0, 0, -min(mirrored_slab.frac_coords[:, 2])] mirrored_slab.translate_sites(range(mirrored_slab.num_sites), translation) all_film_variants.append(mirrored_slab) film_labels.append('%dm' % i) # substrate first index, film second index self.interfaces = [] self.interface_labels = [] # self.interfaces = [[None for j in range(len(all_film_variants))] for i in range(len(all_substrate_variants))] for i, substrate in enumerate(all_substrate_variants): for j, film in enumerate(all_film_variants): self.interfaces.append(self.make_interface(substrate, film)) self.interface_labels.append('%s/%s' % (film_labels[j], sub_labels[i]))
def transform_symmop( self, symmop: 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))) w_ = np.mod(w_, 1.0) 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, ) if isinstance(symmop, SymmOp): return SymmOp.from_rotation_and_translation(rotation_matrix=W_, translation_vec=w_, tol=symmop.tol) raise RuntimeError
def parse_symmetry_operations(symmops_str_list): """ Helper method to parse the symmetry operations. Args: symmops_str_list ([str]): 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 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 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 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 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 get_lammps_lattice_and_rotation(structure: Structure, origin=(0, 0, 0)) \ -> Tuple[np.ndarray, SymmOp, np.ndarray]: """ Transform structure to lammps compatible structure. The lattice and rotation matrix are returned Args: structure (Structure): pymatgen structure origin (tuple): origin coordinates Returns: new lattice, rotation symmetry operator, rotation matrix """ lattice = structure.lattice a, b, c = lattice.abc xlo, ylo, zlo = origin xhi = a + xlo m = lattice.matrix xy = np.dot(m[1], m[0] / a) yhi = np.sqrt(b**2 - xy**2) + ylo xz = np.dot(m[2], m[0] / a) yz = (np.dot(m[1], m[2]) - xy * xz) / (yhi - ylo) zhi = np.sqrt(c**2 - xz**2 - yz**2) + zlo # tilt = None if lattice.is_orthogonal else [xy, xz, yz] new_matrix = np.array([[xhi - xlo, 0, 0], [xy, yhi - ylo, 0], [xz, yz, zhi - zlo]]) rot_matrix = np.linalg.solve(new_matrix, m) symmop = SymmOp.from_rotation_and_translation(rot_matrix, origin) return new_matrix, symmop, rot_matrix
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 align_axis(structure, axis='c', direction=(0, 0, 1)): """ Copied from MPInterfaces with some modification. 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. Parameters ---------- 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) Structure object rotated to align axis along direction. """ ## rotate the specified axis to be along the 001 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] rot_axis = np.cross(axis, direction) if not(rot_axis[0] == 0 and rot_axis[1] == 0): theta = (np.arccos(np.dot(axis, direction) / (np.linalg.norm(axis) * np.linalg.norm(direction)))) R = get_rotation_matrix(rot_axis, theta) rotation = SymmOp.from_rotation_and_translation(rotation_matrix=R) structure.apply_operation(rotation,fractional=False) ## rotate such that the 001 direction lies along the 'c' axis axis = structure.lattice._matrix[2] rot_axis = np.cross(direction, axis) if not(rot_axis[0] == 0 and rot_axis[1] == 0): theta = (np.arccos(np.dot(axis, direction) / (np.linalg.norm(axis) * np.linalg.norm(direction)))) R = get_rotation_matrix(rot_axis, theta) rotation = SymmOp.from_rotation_and_translation(rotation_matrix=R) structure.apply_operation(rotation,fractional=True) # structure.lattice._matrix[2][2] = abs(structure.lattice._matrix[2][2]) return structure
def generate_elastic_workflow(structure, tags=None): """ Generates a standard production workflow. Notes: Uses a primitive structure transformed into the conventional basis (for equivalent deformations). Adds the "minimal" category to the minimal portion of the workflow necessary to generate the elastic tensor, and the "minimal_full_stencil" category to the portion that includes all of the strain stencil, but is symmetrically complete """ if tags == None: tags = [] # transform the structure ieee_rot = Tensor.get_ieee_rotation(structure) if not SquareTensor(ieee_rot).is_rotation(tol=0.005): raise ValueError( "Rotation matrix does not satisfy rotation conditions") symm_op = SymmOp.from_rotation_and_translation(ieee_rot) ieee_structure = structure.copy() ieee_structure.apply_operation(symm_op) # construct workflow wf = wf_elastic_constant(ieee_structure) # Set categories, starting with optimization opt_fws = get_fws_and_tasks(wf, fw_name_constraint="optimization") wf.fws[opt_fws[0][0]].spec['elastic_category'] = "minimal" # find minimal set of fireworks using symmetry reduction fws_by_strain = { Strain(fw.tasks[-1]['pass_dict']['strain']): n for n, fw in enumerate(wf.fws) if 'deformation' in fw.name } unique_tensors = symmetry_reduce(list(fws_by_strain.keys()), ieee_structure) for unique_tensor in unique_tensors: fw_index = get_tkd_value(fws_by_strain, unique_tensor) if np.isclose(unique_tensor, 0.005).any(): wf.fws[fw_index].spec['elastic_category'] = "minimal" else: wf.fws[fw_index].spec['elastic_category'] = "minimal_full_stencil" # Add tags if tags: wf = add_tags(wf, tags) wf = add_modify_incar(wf) priority = 500 - structure.num_sites wf = add_priority(wf, priority) for fw in wf.fws: if fw.spec.get('elastic_category') == 'minimal': fw.spec['_priority'] += 2000 elif fw.spec.get('elastic_category') == 'minimal_full_stencil': fw.spec['_priority'] += 1000 return wf
def apply_transformations(self, match): """ Using ZSL match, transform all of the film_structures by the ZSL supercell transformation. Args: match (dict): ZSL match returned by ZSLGenerator.__call__ """ film_transformation = match["film_transformation"] sub_transformation = match["substrate_transformation"] modified_substrate_structures = [struct.copy() for struct in self.substrate_structures] modified_film_structures = [struct.copy() for struct in self.film_structures] # Match angles in lattices with 𝛾=θ° and 𝛾=(180-θ)° if np.isclose(180 - modified_film_structures[0].lattice.gamma, modified_substrate_structures[0].lattice.gamma, atol=3): reflection = SymmOp.from_rotation_and_translation(((-1, 0, 0), (0, 1, 0), (0, 0, 1)), (0, 0, 1)) for modified_film_structure in modified_film_structures: modified_film_structure.apply_operation(reflection, fractional=True) self.oriented_film.apply_operation(reflection, fractional=True) # ------------------------------------------------------------------------------------------------------------------------ sub_scaling = np.diag(np.diag(sub_transformation)) sub_shearing = np.dot(np.linalg.inv(sub_scaling), sub_transformation) # Turn into 3x3 Arrays sub_scaling = np.diag(np.append(np.diag(sub_scaling), 1)) temp_matrix = np.diag([1, 1, 1]) temp_matrix[:2, :2] = sub_transformation sub_shearing = temp_matrix for modified_substrate_structure in modified_substrate_structures: modified_substrate_structure = self.apply_transformation(modified_substrate_structure, temp_matrix) self.modified_substrate_structures.append(modified_substrate_structure) self.oriented_substrate = self.apply_transformation(self.oriented_substrate, temp_matrix) # ------------------------------------------------------------------------------------------------------------------------ film_scaling = np.diag(np.diag(film_transformation)) film_shearing = np.dot(np.linalg.inv(film_scaling), film_transformation) # Turn into 3x3 Arrays film_scaling = np.diag(np.append(np.diag(film_scaling), 1)) temp_matrix = np.diag([1, 1, 1]) temp_matrix[:2, :2] = film_transformation film_shearing = temp_matrix for modified_film_structure in modified_film_structures: modified_film_structure = self.apply_transformation(modified_film_structure, temp_matrix) self.modified_film_structures.append(modified_film_structure) self.oriented_film = self.apply_transformation(self.oriented_film, temp_matrix) return
def get_symmetry(mol, already_oriented=False): ''' Return a list of SymmOps for a molecule's point symmetry already_oriented: whether or not the principle axes of mol are already reoriented ''' pga = PointGroupAnalyzer(mol) #Handle linear molecules if '*' in pga.sch_symbol: if already_oriented == False: #Reorient the molecule oriented_mol, P = reoriented_molecule(mol) pga = PointGroupAnalyzer(oriented_mol) pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) #Add 12-fold and reflections in place of ininitesimal rotation for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]: op = SymmOp.from_rotation_and_translation(aa2matrix(axis, pi / 6), [0, 0, 0]) if pga.is_valid_op(op): symm_m.append(op) #Any molecule with infinitesimal symmetry is linear; #Thus, it possess mirror symmetry for any axis perpendicular #To the rotational axis. pymatgen does not add this symmetry #for all linear molecules - for example, hydrogen if axis == [1, 0, 0]: symm_m.append(SymmOp.from_xyz_string('x,-y,z')) symm_m.append(SymmOp.from_xyz_string('x,y,-z')) elif axis == [0, 1, 0]: symm_m.append(SymmOp.from_xyz_string('-x,y,z')) symm_m.append(SymmOp.from_xyz_string('x,y,-z')) elif axis == [0, 0, 1]: symm_m.append(SymmOp.from_xyz_string('-x,y,z')) symm_m.append(SymmOp.from_xyz_string('x,-y,z')) #Generate a full list of SymmOps for the molecule's pointgroup symm_m = generate_full_symmops(symm_m, 1e-3) break #Reorient the SymmOps into mol's original frame if not already_oriented: new = [] for op in symm_m: new.append(P.inverse * op * P) return new elif already_oriented: return symm_m #Handle nonlinear molecules else: pg = pga.get_pointgroup() symm_m = [] for op in pg: symm_m.append(op) return symm_m
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 __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 __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 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 get_wyckoff_positions(sg): """find the wyckoff positions Args: sg: space group number (19) Return: a list containg the operation matrix sorted by multiplicity 4a [Rot: [[ 1. 0. 0.] [ 0. 1. 0.] [ 0. 0. 1.]] tau [ 0. 0. 0.], Rot: [[-1. 0. 0.] [ 0. -1. 0.] [ 0. 0. 1.]] tau [ 0.5 0. 0.5], Rot: [[-1. 0. 0.] [ 0. 1. 0.] [ 0. 0. -1.]] tau [ 0. 0.5 0.5], Rot: [[ 1. 0. 0.] [ 0. -1. 0.] [ 0. 0. -1.]] tau [ 0.5 0.5 0. ]]] """ array = [] hall_number = hall.hall_from_hm(sg) wyckoff_positions = make_sitesym.get_wyckoff_position_operators('database/Wyckoff.csv', hall_number) for x in wyckoff_positions: temp = [] for y in x: temp.append(SymmOp.from_rotation_and_translation(list(y[0]), filter_site(y[1]/24))) array.append(temp) i = 0 wyckoffs_organized = [[]] #2D Array of Wyckoff positions organized by multiplicity old = len(array[0]) for x in array: mult = len(x) if mult != old: wyckoffs_organized.append([]) i += 1 old = mult wyckoffs_organized[i].append(x) return wyckoffs_organized
def __init__(self, op): if type(op) == deepcopy(SymmOp): self.op = op self.tol = op.tol self.affine_matrix = op.affine_matrix self.m = op.rotation_matrix self.det = det(self.m) elif (type(op) == np.ndarray) or (type(op) == np.matrix): if op.shape == (3, 3): self.op = SymmOp.from_rotation_and_translation(op, [0, 0, 0]) self.m = self.op.rotation_matrix self.det = det(op) else: print("Error: OperationAnalyzer requires a SymmOp or 3x3 array.") #If rotation matrix is not orthogonal if not is_orthogonal(self.m): self.type = "general" self.axis, self.angle, self.order, self.rotation_order = None, None, None, None #If rotation matrix is orthogonal else: #If determinant is positive if det(self.m) > 0: self.inverted = False self.axis, self.angle = matrix2aa(self.m) if isclose(self.angle, 0): self.type = "identity" self.order = int(1) self.rotation_order = int(1) else: self.type = "rotation" self.order = OperationAnalyzer.get_order(self.angle) self.rotation_order = self.order #If determinant is negative elif det(self.m) < 0: self.inverted = True mi = self.m * -1 self.axis, self.angle = matrix2aa(mi) if isclose(self.angle, 0): self.type = "inversion" self.order = int(2) self.rotation_order = int(1) else: self.axis *= -1 self.type = "rotoinversion" self.order = OperationAnalyzer.get_order( self.angle, rotoinversion=True) self.rotation_order = OperationAnalyzer.get_order( self.angle, rotoinversion=False) elif det(self.m) == 0: self.type = "degenerate" self.axis, self.angle = None, None
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.]) return self.transform(sop)
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 _rotate_structure(self, structure, angle): copy_structure = copy.copy(structure) angle = angle * (np.pi / 180) operation = SymmOp.from_rotation_and_translation( rotation_matrix=np.array([ [np.cos(angle), -np.sin(angle), 0], [np.sin(angle), np.cos(angle), 0], [0, 0, 1], ]), translation_vec=[0, 0, 0], ) copy_structure.apply_operation(operation, fractional=False) return copy_structure
def reorient(mol): new_mol = mol.get_centered_molecule() A = get_inertia_tensor(new_mol) #Store the eigenvectors of the inertia tensor P = np.transpose(eigh(A)[1]) if det(P) < 0: P[0] *= -1 #reorient the molecule P = SymmOp.from_rotation_and_translation(P, [0, 0, 0]) new_mol.apply_operation(P) #Our molecule should never be inverted during reorientation. if det(P.rotation_matrix) < 0: print("Error: inverted reorientation applied.") return new_mol, P
def get_symmops(structure, symprec=defaults["symprec"]): """Returns fractional ops as the cartesian ones from pmg have issues.""" from pymatgen.symmetry.analyzer import SpacegroupAnalyzer sga = SpacegroupAnalyzer(structure, symprec=symprec) # fractional rotation matrices from spglib need to be transposed so that the # operation is R.M return [ SymmOp.from_rotation_and_translation( rotation_matrix=o.rotation_matrix.T, translation_vec=o.translation_vector) for o in sga.get_symmetry_operations(cartesian=False) ]
def get_wyckoff_symmetry(sg, molecular=False): ''' 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 molecular: whether or not to return the Euclidean point symmetry operations If True, cuts off translational part of operation, and converts non-orthogonal (3-fold and 6-fold rotation) operations to pure rotations ''' P = SymmOp.from_rotation_and_translation( [[1, -.5, 0], [0, sqrt(3) / 2, 0], [0, 0, 1]], [0, 0, 0]) symmetry_strings = eval(wyckoff_symmetry_df["0"][sg]) symmetry = [] convert = False if molecular is True: if sg >= 143 and sg <= 194: convert = True #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 ops for z in y: op = SymmOp.from_xyz_string(z) if convert is True: #Convert non-orthogonal trigonal/hexagonal operations op = P * op * P.inverse if molecular is False: symmetry[-1][-1].append(op) elif molecular is True: op = SymmOp.from_rotation_and_translation( op.rotation_matrix, [0, 0, 0]) symmetry[-1][-1].append(op) return symmetry
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 __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 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 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 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_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.parameters, latt.parameters) self.assertFalse(np.allclose(aligned_out.parameters, latt2.parameters))
def __init__(self, int_symbol: str) -> None: """ Initializes a Point Group from its international symbol. Args: int_symbol (str): International or Hermann-Mauguin Symbol. """ from pymatgen.core.operations import SymmOp 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 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 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 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 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 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 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 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 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 add_vacuum(delta, cut=0.9): """ Adds vacuum to a POSCAR. Args: delta (float): vacuum thickness in Angstroms cut (delta): fractional height above which atoms will need to be fixed. Defaults to 0.9. """ # Fix the POSCAR to put bottom atoms (even if they are above the # current vacuum layer) at 0.0. structure = Structure.from_file('POSCAR') n_sites = structure.num_sites poscar_lines = open('POSCAR').readlines() with open('POSCAR', 'w') as poscar: for line in poscar_lines[:8]: poscar.write(line) for line in poscar_lines[8:8+n_sites]: split_line = line.split() if float(split_line[2]) > cut: new_line = ' '.join([split_line[0], split_line[1], str(float(split_line[2]) - 1.0)]) else: new_line = ' '.join(split_line) poscar.write(new_line + '\n') min_z = 1 for site in structure.sites: if site._fcoords[2] > cut: height = site._fcoords[2] - 1 else: height = site._fcoords[2] if height < min_z: min_z = height translation = SymmOp.from_rotation_and_translation( translation_vec=(0, 0, -min_z)) structure.apply_operation(translation, fractional=True) structure.to('POSCAR', 'POSCAR') with open('POSCAR', 'r') as poscar: poscar_lines = poscar.readlines() atom_lines = [] for i in range(8, 8+n_sites): atom_lines.append(poscar_lines[i].split()) atom_line_2s = [] for atom_line in atom_lines: atom_line_2s.append(float(atom_line[2])) fixable = False addables = [] for atom_line_2 in atom_line_2s: if float(atom_line_2) > cut or\ (float(atom_line_2) < 0.0 and 1.0 + float(atom_line_2) > cut): if float(atom_line_2) < 0.0 and 1.0 + float(atom_line_2) > cut: atom_line_2 = float(atom_line_2) + 1.0 addables.append(atom_line_2) fixable = True # if fixable: # add_factor = 1.0 - min(addables) # else: add_factor = 0.0 new_atom_lines = [] for atom_line in atom_lines: new_atom_line_2 = str(float(atom_line[2]) + add_factor) if float(new_atom_line_2) >= 1.0: new_atom_line_2 = str(float(new_atom_line_2) - 1.0) new_atom_lines.append('{} {} {}'.format(atom_line[0], atom_line[1], new_atom_line_2)) with open('POSCAR', 'w') as poscar: for line in poscar_lines[0:8]: poscar.write(line) for new_atom_line in new_atom_lines: poscar.write('{}\n'.format(new_atom_line)) # Open files and read in values from POSCAR old_poscar = open('POSCAR', 'r') new_poscar = open('new_POSCAR', 'w') oldlines = old_poscar.readlines() name = oldlines[0].split()[0] lattice_constant = oldlines[1].strip() a_lat_par = [float(x) for x in oldlines[2].split()] b_lat_par = [float(y) for y in oldlines[3].split()] c_lat_par = [abs(float(z)) for z in oldlines[4].split()] elements = oldlines[5].split() stoichiometry = oldlines[6].split() coordinate_type = oldlines[7].strip() # Elongate c-vector by delta save = float(c_lat_par[2]) c_length = float(c_lat_par[2]) * float(lattice_constant) c_length_plus_delta = c_length + float(delta) c_lat_par[2] = c_length_plus_delta / float(lattice_constant) scalar = c_lat_par[2] / save # Create list of atom coordinates and adjust their z-coordinate on # the fly atoms = [] for i in range(8, 8+n_sites): atom = oldlines[i].split() atom[2] = float(atom[2]) / scalar atoms.append(atom) # Write updated values to new_POSCAR, copy it to old_POSCAR, then # close files and delete new_POSCAR new_poscar.write('{}\n'.format(name)) new_poscar.write('{}\n'.format(lattice_constant)) for item in a_lat_par: new_poscar.write('{} '.format(item)) new_poscar.write('\n') for item in b_lat_par: new_poscar.write('{} '.format(item)) new_poscar.write('\n') for item in c_lat_par: new_poscar.write('{} '.format(item)) new_poscar.write('\n') for item in elements: new_poscar.write('{} '.format(item)) new_poscar.write('\n') for item in stoichiometry: new_poscar.write('{} '.format(item)) new_poscar.write('\n') new_poscar.write('{}\n'.format(coordinate_type)) for item in atoms: new_poscar.write('{} {} {}\n'.format(item[0], item[1], item[2])) new_poscar.close() os.remove('POSCAR') new_lines = open('new_POSCAR').readlines() with open('POSCAR', 'w') as poscar: for line in new_lines: poscar.write(line) old_poscar.close() os.remove('new_POSCAR')
def __init__(self, struct, symprec=None): format_str = "{:.8f}" block = OrderedDict() loops = [] spacegroup = ("P 1", 1) if symprec is not None: sf = SpacegroupAnalyzer(struct, symprec) spacegroup = (sf.get_space_group_symbol(), sf.get_space_group_number()) # Needs the refined struture when using symprec. This converts # primitive to conventional structures, the standard for CIF. struct = sf.get_refined_structure() latt = struct.lattice comp = struct.composition no_oxi_comp = comp.element_composition block["_symmetry_space_group_name_H-M"] = spacegroup[0] for cell_attr in ['a', 'b', 'c']: block["_cell_length_" + cell_attr] = format_str.format( getattr(latt, cell_attr)) for cell_attr in ['alpha', 'beta', 'gamma']: block["_cell_angle_" + cell_attr] = format_str.format( getattr(latt, cell_attr)) block["_symmetry_Int_Tables_number"] = spacegroup[1] block["_chemical_formula_structural"] = no_oxi_comp.reduced_formula block["_chemical_formula_sum"] = no_oxi_comp.formula block["_cell_volume"] = latt.volume.__str__() reduced_comp, fu = no_oxi_comp.get_reduced_composition_and_factor() block["_cell_formula_units_Z"] = str(int(fu)) if symprec is None: block["_symmetry_equiv_pos_site_id"] = ["1"] block["_symmetry_equiv_pos_as_xyz"] = ["x, y, z"] else: sf = SpacegroupAnalyzer(struct, symprec) def round_symm_trans(i): for t in TRANSLATIONS.values(): if abs(i - t) < symprec: return t if abs(i - round(i)) < symprec: return 0 raise ValueError("Invalid translation!") symmops = [] for op in sf.get_symmetry_operations(): v = op.translation_vector v = [round_symm_trans(i) for i in v] symmops.append(SymmOp.from_rotation_and_translation( op.rotation_matrix, v)) ops = [op.as_xyz_string() for op in symmops] block["_symmetry_equiv_pos_site_id"] = \ ["%d" % i for i in range(1, len(ops) + 1)] block["_symmetry_equiv_pos_as_xyz"] = ops loops.append(["_symmetry_equiv_pos_site_id", "_symmetry_equiv_pos_as_xyz"]) contains_oxidation = True try: symbol_to_oxinum = OrderedDict([ (el.__str__(), float(el.oxi_state)) for el in sorted(comp.elements)]) except AttributeError: symbol_to_oxinum = OrderedDict([(el.symbol, 0) for el in sorted(comp.elements)]) contains_oxidation = False if contains_oxidation: block["_atom_type_symbol"] = symbol_to_oxinum.keys() block["_atom_type_oxidation_number"] = symbol_to_oxinum.values() loops.append(["_atom_type_symbol", "_atom_type_oxidation_number"]) atom_site_type_symbol = [] atom_site_symmetry_multiplicity = [] atom_site_fract_x = [] atom_site_fract_y = [] atom_site_fract_z = [] atom_site_label = [] atom_site_occupancy = [] count = 1 if symprec is None: for site in struct: for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(sp.__str__()) atom_site_symmetry_multiplicity.append("1") atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(occu.__str__()) count += 1 else: # The following just presents a deterministic ordering. unique_sites = [ (sorted(sites, key=lambda s: tuple([abs(x) for x in s.frac_coords]))[0], len(sites)) for sites in sf.get_symmetrized_structure().equivalent_sites ] for site, mult in sorted( unique_sites, key=lambda t: (t[0].species_and_occu.average_electroneg, -t[1], t[0].a, t[0].b, t[0].c)): for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(sp.__str__()) atom_site_symmetry_multiplicity.append("%d" % mult) atom_site_fract_x.append("{0:f}".format(site.a)) atom_site_fract_y.append("{0:f}".format(site.b)) atom_site_fract_z.append("{0:f}".format(site.c)) atom_site_label.append("{}{}".format(sp.symbol, count)) atom_site_occupancy.append(occu.__str__()) count += 1 block["_atom_site_type_symbol"] = atom_site_type_symbol block["_atom_site_label"] = atom_site_label block["_atom_site_symmetry_multiplicity"] = \ atom_site_symmetry_multiplicity block["_atom_site_fract_x"] = atom_site_fract_x block["_atom_site_fract_y"] = atom_site_fract_y block["_atom_site_fract_z"] = atom_site_fract_z block["_atom_site_occupancy"] = atom_site_occupancy loops.append(["_atom_site_type_symbol", "_atom_site_label", "_atom_site_symmetry_multiplicity", "_atom_site_fract_x", "_atom_site_fract_y", "_atom_site_fract_z", "_atom_site_occupancy"]) d = OrderedDict() d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula) self._cf = CifFile(d)
def test_symmops(self): sg = SpaceGroup("Pnma") op = SymmOp.from_rotation_and_translation([[1, 0, 0], [0, -1, 0], [0, 0, -1]], [0.5, 0.5, 0.5]) self.assertIn(op, sg.symmetry_ops)