def __init__(self, group_uuid, structure, sc_size, process_label, energy_threshold=0.01, symprec=1e-3, if_with_energy=True): """ Parameters: ----------- uuid : int A uuid of a given group to generate the cluster structure : Structure A structure object to generate the symmetry operations sc_size : int A list of supercell size to generate symmetry operations process_label : str workchain name to query calculation from energy_threshold : float Tolerance of energy difference threshold. Default = 0.01 symprec : float Tolerance in atomic distance to test if atoms are symmetrically similar. Default = 0.1 (if for positions obtain from electronic structure) if_with_energy : bool If False, to not query energy . Default=True """ self.group_uuid = group_uuid self.structure = structure self.process_label = process_label self.energy_threshold = energy_threshold self.symprec = symprec self.if_with_energy = if_with_energy #self.frac_coords = frac_coords if sc_size is None or sc_size == "": self.sc_size = "1 1 1" else: self.sc_size = sc_size self.structure_sc = structure.copy() self.structure_sc.make_supercell( [int(x) for x in self.sc_size.split()]) # Query nodes, energy, positions QG = QueryCalculationsFromGroup(self.group_uuid, self.process_label) self.uuid = QG.get_relaxed_nodes() self.positions = QG.get_positions() if self.if_with_energy: self.energies = QG.get_energies() else: self.energies = np.zeros([len(self.uuid)]) """#To find the space group symmetry operations of the structure """ SA = SpacegroupAnalyzer(self.structure_sc) self.SG = SpacegroupOperations( SA.get_space_group_number(), SA.get_space_group_symbol(), SA.get_symmetry_operations(cartesian=False))
def symm_reduce(self, coords_set, threshold=1e-6): """ Reduces the set of adsorbate sites by finding removing symmetrically equivalent duplicates Args: coords_set: coordinate set in cartesian coordinates threshold: tolerance for distance equivalence, used as input to in_coord_list_pbc for dupl. checking """ surf_sg = SpacegroupAnalyzer(self.slab, 0.1) symm_ops = surf_sg.get_symmetry_operations() unique_coords = [] # Convert to fractional coords_set = [ self.slab.lattice.get_fractional_coords(coords) for coords in coords_set ] for coords in coords_set: incoord = False for op in symm_ops: if in_coord_list_pbc(unique_coords, op.operate(coords), atol=threshold): incoord = True break if not incoord: unique_coords += [coords] # convert back to cartesian return [ self.slab.lattice.get_cartesian_coords(coords) for coords in unique_coords ]
def print_info(struc): """ Read a Structure object and print structure information. Args: struc : pymatgen Structure object. Returns: None. """ print(struc) print('\n') sga = SpacegroupAnalyzer(struc) print('The space group: {} {}\n'.format(sga.get_space_group_number(), sga.get_space_group_symbol())) equi_sites = sga.get_symmetrized_structure().equivalent_sites for sites in equi_sites: print('the symmtry equivalent sites are \n{}\n'.format(sites)) sym_operations = sga.get_symmetry_operations() print('there are {} symmetry operations: \n {} \n'.format( len(sym_operations), sym_operations))
def set_miller_family(self): """ get all miller indices for the given maximum index get the list of indices that correspond to the given family of indices """ recp_structure = Structure(self.recp_lattice, ["H"], [[0, 0, 0]]) analyzer = SpacegroupAnalyzer(recp_structure, symprec=0.001) symm_ops = analyzer.get_symmetry_operations() max_index = max(max(m) for m in self.hkl_family) r = list(range(-max_index, max_index + 1)) r.reverse() miller_indices = [] self.all_equiv_millers = [] self.all_surface_energies = [] for miller in itertools.product(r, r, r): if any([i != 0 for i in miller]): d = abs(reduce(gcd, miller)) miller_index = tuple([int(i / d) for i in miller]) for op in symm_ops: for i, u_miller in enumerate(self.hkl_family): if in_coord_list(u_miller, op.operate(miller_index)): self.all_equiv_millers.append(miller_index) self.all_surface_energies.append( self.surface_energies[i])
def get_reciprocal_point_group_operations( structure: Structure, symprec: float = _SYMPREC, time_reversal_symmetry: bool = True ): from pymatgen.symmetry.analyzer import SpacegroupAnalyzer frac_real_to_frac_recip = np.dot( np.linalg.inv(structure.lattice.reciprocal_lattice.matrix.T), structure.lattice.matrix.T, ) frac_recip_to_frac_real = np.linalg.inv(frac_real_to_frac_recip) sga = SpacegroupAnalyzer(structure, symprec=symprec) isomorphic_ops = [op.rotation_matrix for op in sga.get_symmetry_operations()] parity = -np.eye(3) if time_reversal_symmetry: reciprocal_ops = [-np.eye(3)] else: reciprocal_ops = [np.eye(3)] for op in isomorphic_ops: # convert to reciprocal primitive basis op = np.around( np.dot(frac_real_to_frac_recip, np.dot(op, frac_recip_to_frac_real)), decimals=2, ) reciprocal_ops.append(op) if time_reversal_symmetry: reciprocal_ops.append(np.dot(parity, op)) return np.unique(reciprocal_ops, axis=0)
def is_structure_invertible(structure): ''' This function figures out whether or not an `pymatgen.Structure` object has symmetricity. In this function, the affine matrix is a rotation matrix that is multiplied with the XYZ positions of the crystal. If the z,z component of that is negative, it means symmetry operation exist, it could be a mirror operation, or one that involves multiple rotations/etc. Regardless, it means that the top becomes the bottom and vice-versa, and the structure is the symmetric. i.e. structure_XYZ = structure_XYZ*M. Arg: structure A `pymatgen.Structure` object. Returns A boolean indicating whether or not your `ase.Atoms` object is symmetric in z-direction (i.e. symmetric with respect to x-y plane). ''' # If any of the operations involve a transformation in the z-direction, # then the structure is invertible. sga = SpacegroupAnalyzer(structure, symprec=0.1) for operation in sga.get_symmetry_operations(): xform_matrix = operation.affine_matrix z_xform = xform_matrix[2, 2] if z_xform == -1: return True return False
def create_saturated_interstitial_structure(interstitial_def, dist_tol=0.1): """ this takes a Interstitial defect object and generates the sublattice for it based on the structure's space group. Useful for understanding multiplicity of an interstitial defect in thermodynamic analysis. NOTE: if large relaxation happens to interstitial or defect involves a complex then there may be additional degrees of freedom that need to be considered for the multiplicity. Args: dist_tol: changing distance tolerance of saturated structure, allowing for possibly overlapping sites but ensuring space group is maintained Returns: Structure object decorated with interstitial site equivalents """ sga = SpacegroupAnalyzer(interstitial_def.bulk_structure.copy()) sg_ops = sga.get_symmetry_operations(cartesian=True) # copy bulk structure to make saturated interstitial structure out of # artificially lower distance_tolerance to allow for distinct interstitials # with lower symmetry to be replicated - This is OK because one would never # actually use this structure for a practical calcualtion... saturated_defect_struct = interstitial_def.bulk_structure.copy() saturated_defect_struct.DISTANCE_TOLERANCE = dist_tol for sgo in sg_ops: new_interstit_coords = sgo.operate(interstitial_def.site.coords[:]) poss_new_site = PeriodicSite( interstitial_def.site.specie, new_interstit_coords, saturated_defect_struct.lattice, to_unit_cell=True, coords_are_cartesian=True, ) try: # will raise value error if site already exists in structure saturated_defect_struct.append( poss_new_site.specie, poss_new_site.coords, coords_are_cartesian=True, validate_proximity=True, ) except ValueError: pass # do final space group analysis to make sure symmetry not lowered by saturating defect structure saturated_sga = SpacegroupAnalyzer(saturated_defect_struct) if saturated_sga.get_space_group_number() != sga.get_space_group_number(): raise ValueError( "Warning! Interstitial sublattice generation " "has changed space group symmetry. Recommend " "reducing dist_tol and trying again..." ) return saturated_defect_struct
def unique_symmetry_operations_as_vectors_from_structure( structure, subset = None ): """ Uses pymatgen symmetry analysis to find the minimum complete set of symmetry operations for the space group of a structure. Args: structure (pymatgen Structure): structure to be analysed. subset (Optional [list]): list of atom indices to be used for generating the symmetry operations Returns: a list of lists, containing the symmetry operations as vector mappings. """ symmetry_analyzer = SpacegroupAnalyzer( structure ) print( "The spacegroup for structure is {}".format(symmetry_analyzer.get_spacegroup_symbol()) ) symmetry_operations = symmetry_analyzer.get_symmetry_operations() mappings = [] if subset: species_subset = [ spec for i,spec in enumerate(structure.species) if i in subset] frac_coords_subset = [ coord for i, coord in enumerate( structure.frac_coords ) if i in subset ] mapping_structure = Structure( structure.lattice, species_subset, frac_coords_subset ) else: mapping_structure = structure for symmop in symmetry_operations: new_structure = Structure( mapping_structure.lattice, mapping_structure.species, symmop.operate_multi( mapping_structure.frac_coords ) ) new_mapping = [ x+1 for x in list( coord_list_mapping_pbc( new_structure.frac_coords, mapping_structure.frac_coords ) ) ] if new_mapping not in mappings: mappings.append( new_mapping ) return mappings
def symm_reduce(self, coords_set, threshold=1e-6): """ Reduces the set of adsorbate sites by finding removing symmetrically equivalent duplicates Args: coords_set: coordinate set in cartesian coordinates threshold: tolerance for distance equivalence, used as input to in_coord_list_pbc for dupl. checking """ surf_sg = SpacegroupAnalyzer(self.slab, 0.1) symm_ops = surf_sg.get_symmetry_operations() unique_coords = [] # Convert to fractional coords_set = [self.slab.lattice.get_fractional_coords(coords) for coords in coords_set] for coords in coords_set: incoord = False for op in symm_ops: if in_coord_list_pbc(unique_coords, op.operate(coords), atol=threshold): incoord = True break if not incoord: unique_coords += [coords] # convert back to cartesian return [self.slab.lattice.get_cartesian_coords(coords) for coords in unique_coords]
def symmetry_reduce(tensors, structure, tol=1e-8, **kwargs): """ Function that converts a list of tensors corresponding to a structure and returns a dictionary consisting of unique tensor keys with symmop values corresponding to transformations that will result in derivative tensors from the original list Args: tensors (list of tensors): list of Tensor objects to test for symmetrically-equivalent duplicates structure (Structure): structure from which to get symmetry tol (float): tolerance for tensor equivalence kwargs: keyword arguments for the SpacegroupAnalyzer returns: dictionary consisting of unique tensors with symmetry operations corresponding to those which will reconstruct the remaining tensors as values """ sga = SpacegroupAnalyzer(structure, **kwargs) symmops = sga.get_symmetry_operations(cartesian=True) unique_tdict = {} for tensor in tensors: is_unique = True for unique_tensor, symmop in itertools.product(unique_tdict, symmops): if (np.abs(unique_tensor.transform(symmop) - tensor) < tol).all(): unique_tdict[unique_tensor].append(symmop) is_unique = False break if is_unique: unique_tdict[tensor] = [] return unique_tdict
def symmetry_reduce(tensors, structure, tol=1e-8, **kwargs): """ Function that converts a list of tensors corresponding to a structure and returns a dictionary consisting of unique tensor keys with symmop values corresponding to transformations that will result in derivative tensors from the original list Args: tensors (list of tensors): list of Tensor objects to test for symmetrically-equivalent duplicates structure (Structure): structure from which to get symmetry tol (float): tolerance for tensor equivalence kwargs: keyword arguments for the SpacegroupAnalyzer returns: dictionary consisting of unique tensors with symmetry operations corresponding to those which will reconstruct the remaining tensors as values """ sga = SpacegroupAnalyzer(structure, **kwargs) symmops = sga.get_symmetry_operations(cartesian=True) unique_mapping = TensorMapping([tensors[0]], [[]], tol=tol) for tensor in tensors[1:]: is_unique = True for unique_tensor, symmop in itertools.product(unique_mapping, symmops): if np.allclose(unique_tensor.transform(symmop), tensor, atol=tol): unique_mapping[unique_tensor].append(symmop) is_unique = False break if is_unique: unique_mapping[tensor] = [] return unique_mapping
def set_miller_family(self): """ get all miller indices for the given maximum index get the list of indices that correspond to the given family of indices """ recp_structure = Structure(self.recp_lattice, ["H"], [[0, 0, 0]]) analyzer = SpacegroupAnalyzer(recp_structure, symprec=0.001) symm_ops = analyzer.get_symmetry_operations() max_index = max(max(m) for m in self.hkl_family) r = list(range(-max_index, max_index + 1)) r.reverse() miller_indices = [] self.all_equiv_millers = [] self.all_surface_energies = [] for miller in itertools.product(r, r, r): if any([i != 0 for i in miller]): d = abs(reduce(gcd, miller)) miller_index = tuple([int(i / d) for i in miller]) for op in symm_ops: for i, u_miller in enumerate(self.hkl_family): if in_coord_list(u_miller, op.operate(miller_index)): self.all_equiv_millers.append(miller_index) self.all_surface_energies.append(self.surface_energies[i])
def __init__(self, rlxd_str, nd=0.01, ns=0.08, num_norm=4, num_shear=4, symmetry=False): """ constructs the deformed geometries of a structure. Generates m + n deformed structures according to the supplied parameters. Args: rlxd_str (structure): structure to undergo deformation, if fitting elastic tensor is desired, should be a geometry optimized structure nd (float): maximum perturbation applied to normal deformation ns (float): maximum perturbation applied to shear deformation m (int): number of deformation structures to generate for normal deformation, must be even n (int): number of deformation structures to generate for shear deformation, must be even """ if num_norm % 2 != 0: raise ValueError("Number of normal deformations (num_norm)" " must be even.") if num_shear % 2 != 0: raise ValueError("Number of shear deformations (num_shear)" " must be even.") norm_deformations = np.linspace(-nd, nd, num=num_norm + 1) norm_deformations = norm_deformations[norm_deformations.nonzero()] shear_deformations = np.linspace(-ns, ns, num=num_shear + 1) shear_deformations = shear_deformations[shear_deformations.nonzero()] self.undeformed_structure = rlxd_str self.deformations = [] self.def_structs = [] # Generate deformations for ind in [(0, 0), (1, 1), (2, 2)]: for amount in norm_deformations: defo = Deformation.from_index_amount(ind, amount) self.deformations.append(defo) for ind in [(0, 1), (0, 2), (1, 2)]: for amount in shear_deformations: defo = Deformation.from_index_amount(ind, amount) self.deformations.append(defo) # Perform symmetry reduction if specified if symmetry: sga = SpacegroupAnalyzer(self.undeformed_structure, symprec=0.1) symm_ops = sga.get_symmetry_operations(cartesian=True) self.deformations = symm_reduce(symm_ops, self.deformations) self.def_structs = [ defo.apply_to_structure(rlxd_str) for defo in self.deformations ]
def create_saturated_interstitial_structure( interstitial_def, dist_tol=0.1): """ this takes a Interstitial defect object and generates the sublattice for it based on the structure's space group. Useful for understanding multiplicity of an interstitial defect in thermodynamic analysis. NOTE: if large relaxation happens to interstitial or defect involves a complex then there may be additional degrees of freedom that need to be considered for the multiplicity. Args: dist_tol: changing distance tolerance of saturated structure, allowing for possibly overlapping sites but ensuring space group is maintained Returns: Structure object decorated with interstitial site equivalents """ sga = SpacegroupAnalyzer( interstitial_def.bulk_structure.copy()) sg_ops = sga.get_symmetry_operations( cartesian=True) # copy bulk structure to make saturated interstitial structure out of # artificially lower distance_tolerance to allow for distinct interstitials # with lower symmetry to be replicated - This is OK because one would never # actually use this structure for a practical calcualtion... saturated_defect_struct = interstitial_def.bulk_structure.copy() saturated_defect_struct.DISTANCE_TOLERANCE = dist_tol for sgo in sg_ops: new_interstit_coords = sgo.operate( interstitial_def.site.coords[:]) poss_new_site = PeriodicSite( interstitial_def.site.specie, new_interstit_coords, saturated_defect_struct.lattice, to_unit_cell=True, coords_are_cartesian=True) try: #will raise value error if site already exists in structure saturated_defect_struct.append( poss_new_site.specie, poss_new_site.coords, coords_are_cartesian=True, validate_proximity=True) except ValueError: pass # do final space group analysis to make sure symmetry not lowered by saturating defect structure saturated_sga = SpacegroupAnalyzer( saturated_defect_struct) if saturated_sga.get_space_group_number() != sga.get_space_group_number(): raise ValueError("Warning! Interstitial sublattice generation " "has changed space group symmetry. Recommend " "reducing dist_tol and trying again...") return saturated_defect_struct
def __init__(self, rlxd_str, nd=0.01, ns=0.08, num_norm=4, num_shear=4, symmetry=False): """ constructs the deformed geometries of a structure. Generates m + n deformed structures according to the supplied parameters. Args: rlxd_str (structure): structure to undergo deformation, if fitting elastic tensor is desired, should be a geometry optimized structure nd (float): maximum perturbation applied to normal deformation ns (float): maximum perturbation applied to shear deformation m (int): number of deformation structures to generate for normal deformation, must be even n (int): number of deformation structures to generate for shear deformation, must be even """ if num_norm % 2 != 0: raise ValueError("Number of normal deformations (num_norm)" " must be even.") if num_shear % 2 != 0: raise ValueError("Number of shear deformations (num_shear)" " must be even.") norm_deformations = np.linspace(-nd, nd, num=num_norm + 1) norm_deformations = norm_deformations[norm_deformations.nonzero()] shear_deformations = np.linspace(-ns, ns, num=num_shear + 1) shear_deformations = shear_deformations[shear_deformations.nonzero()] self.undeformed_structure = rlxd_str self.deformations = [] self.def_structs = [] # Generate deformations for ind in [(0, 0), (1, 1), (2, 2)]: for amount in norm_deformations: defo = Deformation.from_index_amount(ind, amount) self.deformations.append(defo) for ind in [(0, 1), (0, 2), (1, 2)]: for amount in shear_deformations: defo = Deformation.from_index_amount(ind, amount) self.deformations.append(defo) # Perform symmetry reduction if specified if symmetry: sga = SpacegroupAnalyzer(self.undeformed_structure, tol=0.1) symm_ops = sga.get_symmetry_operations(cartesian=True) self.deformations = symm_reduce(symm_ops, self.deformations) self.def_structs = [defo.apply_to_structure(rlxd_str) for defo in self.deformations]
def combo_symm_reduce(slab, coord_combos): surf_sg = SpacegroupAnalyzer(slab, 0.1) symm_ops = surf_sg.get_symmetry_operations() #get symmetry operations for the spacegroup of our slab test = [] for coord in list(coord_combos[0].values())[0]: test.append(np.array([1,1,1])) unique_combos = [{'test_case':test}] #create a generic first combination of coordinates based on the number of #coordinates in the combination submitted. necessary to initialize the search, #but this set should never be matched and is removed at the end of the cycle u_combos_lst = [] duplicate_lst = [] for combo in coord_combos: coords = list(combo.values())[0] coords = [slab.lattice.get_fractional_coords(coord) for coord in coords] #converts coordinates to fractional form for u_combo in unique_combos: match = False u_coords = list(u_combo.values())[0] u_coords = [slab.lattice.get_fractional_coords(u_coord) for u_coord in u_coords] #converts unique coordinates in memory to fractional form for op in symm_ops: coord_match = [] #iterates through symmetry operations for coord in coords: incoord = False if mg.util.coord.in_coord_list_pbc(u_coords, op.operate(coord), atol=1e-6): #tests if the coordinate is in the combination of unique coordinates incoord = True coord_match.append(incoord) if all(coord_match): #if all of the coordinates in a combination are already in the memory, #breaks the loop so that the repeated combination is not #added to the set of the unique coordinate combinations #print('suggests %s is symmetrically equivalent to %s' %(list(combo.keys())[0], list(u_combo.keys()))[0]) # u_combos_lst.append(list(u_combo.keys())[0]) # duplicate_lst.append(list(combo.keys())[0]) match = True break if match: break if not match: unique_combos.append(combo) unique_combos.pop(0) # combo_pairs = {'combo_pairs1':u_combos_lst, 'combo_pairs2':duplicate_lst} # pair_df = pd.DataFrame.from_dict(combo_pairs) # pair_df.to_csv(path_or_buf=os.getcwd()+'\\75ML_eq_pairs.csv') return unique_combos #, pair_df
def fit_to_structure(self, structure, symprec=0.1): """ Returns a tensor that is invariant with respect to symmetry operations corresponding to a structure Args: structure (Structure): structure from which to generate symmetry operations symprec (float): symmetry tolerance for the Spacegroup Analyzer used to generate the symmetry operations """ sga = SpacegroupAnalyzer(structure, symprec) symm_ops = sga.get_symmetry_operations(cartesian=True) return sum([self.transform(symm_op) for symm_op in symm_ops]) / len(symm_ops)
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 unique_symmetry_operations_as_vectors_from_structure( structure, verbose=False, subset=None, atol=1e-5 ): """ Uses `pymatgen`_ symmetry analysis to find the minimum complete set of symmetry operations for the space group of a structure. Args: structure (pymatgen ``Structure``): structure to be analysed. subset (Optional [list]): list of atom indices to be used for generating the symmetry operations. atol (Optional [float]): tolerance factor for the ``pymatgen`` `coordinate mapping`_ under each symmetry operation. Returns: (list[list]): a list of lists, containing the symmetry operations as vector mappings. .. _pymatgen: http://pymatgen.org .. _coordinate mapping: http://pymatgen.org/pymatgen.util.coord_utils.html#pymatgen.util.coord_utils.coord_list_mapping_pbc """ if isinstance( structure, Structure ): instantiate_structure = partial( Structure, lattice=structure.lattice, coords_are_cartesian=True ) coord_mapping = structure_cartesian_coordinates_mapping mapping_list = structure_mapping_list symmetry_analyzer = SpacegroupAnalyzer( structure ) if verbose: print( "The space group for this structure is {}".format( symmetry_analyzer.get_space_group_symbol()) ) elif isinstance( structure, Molecule ): instantiate_structure = Molecule coord_mapping = molecule_cartesian_coordinates_mapping mapping_list = molecule_mapping_list symmetry_analyzer = PointGroupAnalyzer( structure, tolerance=atol ) if verbose: print( "The point group for this structure is {}".format( symmetry_analyzer.get_pointgroup()) ) else: raise ValueError( 'structure argument should be a Structure or Molecule object' ) symmetry_operations = symmetry_analyzer.get_symmetry_operations() mappings = [] if subset: species_subset = [ spec for i,spec in enumerate( structure.species ) if i in subset ] cart_coords_subset = [ coord for i, coord in enumerate( structure.cart_coords ) if i in subset ] mapping_structure = instantiate_structure( species=species_subset, coords=cart_coords_subset ) else: mapping_structure = structure for symmop in symmetry_operations: cart_coords = coord_mapping( mapping_structure, symmop ) new_structure = instantiate_structure( species=mapping_structure.species, coords=cart_coords ) new_mapping = [ x+1 for x in list( mapping_list( new_structure, mapping_structure, atol ) ) ] if new_mapping not in mappings: mappings.append( new_mapping ) return mappings
def run_task(self, fw_spec): self.user_incar_settings.update({"NPAR": 2}) # Get kpoint density per vol vol = Poscar.from_file("POSCAR").structure.volume kppra_vol = self.kpoints_density / vol new_set = MPStaticSet.from_prev_calc( os.getcwd(), user_incar_settings=self.user_incar_settings, reciprocal_density=kppra_vol) new_set.write_input('.') structure = new_set.structure sga = SpacegroupAnalyzer(structure, 0.1) return FWAction(stored_data={ 'refined_structure': sga.get_refined_structure().as_dict(), 'conventional_standard_structure': sga.get_conventional_standard_structure().as_dict(), 'symmetry_dataset': sga.get_symmetry_dataset(), 'symmetry_operations': [x.as_dict() for x in sga.get_symmetry_operations()]})
def get_syms(self): """ Gets symmetries with Pymatgen""" from pymatgen.symmetry.analyzer import SpacegroupAnalyzer # Gets symmetries with pymatgen: symprec = 0.1 # symmetry tolerance for the Spacegroup Analyzer #used to generate the symmetry operations sga = SpacegroupAnalyzer(self.structure, symprec) # ([SymmOp]): List of symmetry operations. SymmOp = sga.get_symmetry_operations() nsym = len(SymmOp) # Symmetries directory: # dirname=path.dirname(path.abspath(__file__)) dirname = path.realpath(curdir) newdir = "symmetries" SYMdir = path.join(dirname, newdir) if not path.exists(SYMdir): mkdir(SYMdir) # symmetries/sym.d file: #self.symd_fname=SYMdir+"/sym.d" f = open(self.symd_fname, "w") f.write("%i\n" % (nsym)) for isym in range(0, nsym): symrel = np_array(SymmOp[isym].rotation_matrix) #rotations # Transpose all symmetry matrices symrel = np_linalg.inv(symrel.transpose()) symrel = np_array(symrel, np_int) #translation=SymmOp[isym].translation_vector f.write(" ".join(map(str, symrel[0][:])) + " ") f.write(" ".join(map(str, symrel[1][:])) + " ") f.write(" ".join(map(str, symrel[2][:])) + "\n") f.close() # Get lattice parameters lattice = self.structure.lattice rprim = lattice ang2bohr = 1.88972613 acell = np_ones(3) * ang2bohr # Write pvectors file # symmetries/pvectors file: # self.pvectors_fname=SYMdir+"/pvectors" f = open(self.pvectors_fname, "w") f.write(str(lattice) + "\n") f.write(" ".join(map(str, acell[:])) + "\n") f.close()
def sg_pg_compare(mpid='mp-989535'): stru = m.get_structure_by_material_id(mpid) spa = SpacegroupAnalyzer(stru) pg_name = spa.get_symmetry_dataset()['pointgroup'] pg_ops = PointGroup(pg_name).symmetry_ops sp_ops = [] sp_mats = [] pg_mats = [] for op in pg_ops: rotation = op.rotation_matrix pg_mats.append(jsanitize(rotation)) for op in spa.get_symmetry_operations(): rotation = op.rotation_matrix sp_mats.append(jsanitize(rotation)) sp_ops.append(SymmOp.from_rotation_and_translation( rotation, (0, 0, 0))) sp_ops = set(sp_ops) #pg_mats.sort();sp_mats.sort() return pg_mats, sp_mats
def is_structure_invertible(structure): ''' This function figures out whether or not an `pymatgen.Structure` object is invertible in the z-direction (i.e., is it symmetric about the x-y axis?). Arg: structure A `pymatgen.Structure` object. Returns A boolean indicating whether or not your `ase.Atoms` object is invertible. ''' # If any of the operations involve a transformation in the z-direction, # then the structure is invertible. sga = SpacegroupAnalyzer(structure, symprec=0.1) for operation in sga.get_symmetry_operations(): xform_matrix = operation.affine_matrix z_xform = xform_matrix[2, 2] if z_xform == -1: return True return False
def get_recp_symmetry_operation(structure, symprec=0.001): """ Find the symmetric operations of the reciprocal lattice, to be used for hkl transformations Args: structure (Structure): conventional unit cell symprec: default is 0.001 """ recp_lattice = structure.lattice.reciprocal_lattice_crystallographic # get symmetry operations from input conventional unit cell # Need to make sure recp lattice is big enough, otherwise symmetry # determination will fail. We set the overall volume to 1. recp_lattice = recp_lattice.scale(1) recp = Structure(recp_lattice, ["H"], [[0, 0, 0]]) # Creates a function that uses the symmetry operations in the # structure to find Miller indices that might give repetitive slabs analyzer = SpacegroupAnalyzer(recp, symprec=symprec) recp_symmops = analyzer.get_symmetry_operations() return recp_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 symmeterize(self, structure, symprec=0.1, tol=1e-3): """ Averages the piezo tensor based on the symmetries of the structure Args: structure (Structure): structure to check against symprec (float): Tolerance for symmetry finding, default good for as-made structures. Set to 0.1 for MP structures tol (float): tolerance for zero'ing out indicies. The average procedure produces very small numbers rather then 0. This tolerance is used to zero out those values to make the tensor less messy. """ sg = SpacegroupAnalyzer(structure, symprec) new_pt = PiezoTensor(self) for symm in sg.get_symmetry_operations(cartesian=True): new_pt = (new_pt + new_pt.transform(symm)) / 2 low_values_indices = np.abs(new_pt) < tol new_pt [low_values_indices] = 0 return new_pt
def symmeterize(self, structure, symprec=0.1, tol=1e-3): """ Averages the piezo tensor based on the symmetries of the structure Args: structure (Structure): structure to check against symprec (float): Tolerance for symmetry finding, default good for as-made structures. Set to 0.1 for MP structures tol (float): tolerance for zero'ing out indicies. The average procedure produces very small numbers rather then 0. This tolerance is used to zero out those values to make the tensor less messy. """ sg = SpacegroupAnalyzer(structure, symprec) new_pt = PiezoTensor(self) for symm in sg.get_symmetry_operations(cartesian=True): new_pt = (new_pt + new_pt.transform(symm)) / 2 low_values_indices = np.abs(new_pt) < tol new_pt[low_values_indices] = 0 return new_pt
def get_symmetrically_distinct_miller_indices(structure, max_index): """ Returns all symmetrically distinct indices below a certain max-index for a given structure. Analysis is based on the symmetry of the reciprocal lattice of the structure. Args: structure (Structure): input structure. max_index (int): The maximum index. For example, a max_index of 1 means that (100), (110), and (111) are returned for the cubic structure. All other indices are equivalent to one of these. """ recp_lattice = structure.lattice.reciprocal_lattice_crystallographic # Need to make sure recp lattice is big enough, otherwise symmetry # determination will fail. We set the overall volume to 1. recp_lattice = recp_lattice.scale(1) recp = Structure(recp_lattice, ["H"], [[0, 0, 0]]) # Creates a function that uses the symmetry operations in the # structure to find Miller indices that might give repetitive slabs analyzer = SpacegroupAnalyzer(recp, symprec=0.001) symm_ops = analyzer.get_symmetry_operations() unique_millers = [] def is_already_analyzed(miller_index): for op in symm_ops: if in_coord_list(unique_millers, op.operate(miller_index)): return True return False r = list(range(-max_index, max_index + 1)) r.reverse() for miller in itertools.product(r, r, r): if any([i != 0 for i in miller]): d = abs(reduce(gcd, miller)) miller = tuple([int(i / d) for i in miller]) if not is_already_analyzed(miller): unique_millers.append(miller) return unique_millers
def test_is_structure_invertible(): ''' Currently, we test this function only on slabs, because that's what we use it on mainly. You can test other things if you want. ''' # Get all of our test slabs and convert them to `pymatgen.Structure` ojbects slabs_folder = TEST_CASE_LOCATION + 'slabs/' for file_name in os.listdir(slabs_folder): atoms = ase.io.read(slabs_folder + file_name) structure = AseAtomsAdaptor.get_structure(atoms) # Check for invertibility manually expected_invertibility = False sga = SpacegroupAnalyzer(structure, symprec=0.1) for operation in sga.get_symmetry_operations(): xform_matrix = operation.affine_matrix z_xform = xform_matrix[2, 2] if z_xform == -1: expected_invertibility = True invertibility = is_structure_invertible(structure) assert invertibility == expected_invertibility
def _get_reciprocal_point_group_nonmagnetic(struct): """ Obtain the symmetry ops. in the reciprocal point group of an input structure. Returns: recip_point_group (list): List of symmetry operations as numpy arrays in the fractional reciprocal space basis. """ R = -1 * np.eye(3) sga = SpacegroupAnalyzer(struct) ops = sga.get_symmetry_operations() isomorphic_point_group = [op.rotation_matrix for op in ops] V = struct.lattice.matrix.T # fractional real space to cartesian real space # fractional reciprocal space to cartesian reciprocal space W = struct.lattice.reciprocal_lattice.matrix.T # fractional real space to fractional reciprocal space A = np.dot(np.linalg.inv(W), V) Ainv = np.linalg.inv(A) # convert to reciprocal primitive basis recip_point_group = [np.around(np.dot(A, np.dot(R, Ainv)), decimals=2)] for op in isomorphic_point_group: op = np.around(np.dot(A, np.dot(op, Ainv)), decimals=2) new = True new_coset = True for thing in recip_point_group: if (thing == op).all(): new = False if (thing == np.dot(R, op)).all(): new_coset = False if new: recip_point_group.append(op) if new_coset: recip_point_group.append(np.dot(R, op)) return recip_point_group
def num_equivalent_clusters(structure: Structure, inserted_atom_coords: Optional[list], removed_atom_indices: Optional[list], symprec: float = SYMMETRY_TOLERANCE, angle_tolerance: float = ANGLE_TOL ) -> Tuple[int, str]: """Calculate number of equivalent clusters in the structure. Args: structure (Structure): Supercell is assumed to big enough. inserted_atom_coords (list): removed_atom_indices (list): Needs to begin from 0. symprec (float): angle_tolerance (float): Angle tolerance in degree used for identifying the space group. Returns: Tuple of (num_equivalent_clusters (int), point_group (str)) """ inserted_atom_coords = inserted_atom_coords or [] removed_atom_indices = removed_atom_indices or [] sga = SpacegroupAnalyzer(structure, symprec, angle_tolerance) num_symmop = len(sga.get_symmetry_operations()) structure_with_cluster = structure.copy() for i in inserted_atom_coords: structure_with_cluster.append(DummySpecie(), i) structure_with_cluster.remove_sites(removed_atom_indices) sga_with_cluster = \ SpacegroupAnalyzer(structure_with_cluster, symprec, angle_tolerance) sym_dataset = sga_with_cluster.get_symmetry_dataset() point_group = sym_dataset["pointgroup"] return int(num_symmop / num_symmetry_operation(point_group)), point_group
def is_valid(self, structure, symprec=0.1, tol=1e-3): """ Checks the piezo tensor against the symmetries of the structure and determine if the piezoelectric tensor is valid for that structure Args: structure (Structure): structure to check against symprec (float): Tolerance for symmetry finding, default good for as-made structures. Set to 0.1 for MP structures tol (float): tolerance for equality """ sg = SpacegroupAnalyzer(structure, symprec) # Check every symmetry operation in the space group for symm in sg.get_symmetry_operations(cartesian=True): # does it produce the same tensor? diff = self.transform(symm) - self if not (np.abs(diff) < tol).all(): print(diff) return False return True
def get_recp_symmetry_operation(self, symprec: float = 0.01) -> List: """ Find the symmetric operations of the reciprocal lattice, to be used for hkl transformations Args: symprec: default is 0.001 """ recp_lattice = self.reciprocal_lattice_crystallographic # get symmetry operations from input conventional unit cell # Need to make sure recp lattice is big enough, otherwise symmetry # determination will fail. We set the overall volume to 1. recp_lattice = recp_lattice.scale(1) # need a localized import of structure to build a # pseudo empty lattice for SpacegroupAnalyzer from pymatgen import Structure from pymatgen.symmetry.analyzer import SpacegroupAnalyzer recp = Structure(recp_lattice, ["H"], [[0, 0, 0]]) # Creates a function that uses the symmetry operations in the # structure to find Miller indices that might give repetitive slabs analyzer = SpacegroupAnalyzer(recp, symprec=symprec) recp_symmops = analyzer.get_symmetry_operations() return recp_symmops
def get_distinct_rotations(structure, symprec=0.1, atol=1e-6): """ Get distinct rotations from structure spacegroup operations Args: structure (Structure): structure object to analyze and get corresponding rotations for symprec (float): symprec for SpacegroupAnalyzer atol (float): absolute tolerance for relative indices """ sga = SpacegroupAnalyzer(structure, symprec) symmops = sga.get_symmetry_operations(cartesian=True) rotations = [s.rotation_matrix for s in symmops] if len(rotations) == 1: return rotations unique_rotations = [np.array(rotations[0])] for rotation in rotations[1:]: if not any([ np.allclose(urot, rotation, atol=atol) for urot in unique_rotations ]): unique_rotations.append(rotation) return unique_rotations
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 __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 __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_spacegroup_symbol(), sf.get_spacegroup_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 process_elastic_calcs(opt_doc, defo_docs, add_derived=True, tol=0.002): """ Generates the list of calcs from deformation docs, along with 'derived stresses', i. e. stresses derived from symmop transformations of existing calcs from transformed strains resulting in an independent strain not in the input list Args: opt_doc (dict): document for optimization task defo_docs ([dict]) list of documents for deformation tasks add_derived (bool): flag for whether or not to add derived stress-strain pairs based on symmetry tol (float): tolerance for assigning equivalent stresses/strains Returns ([dict], [dict]): Two lists of summary documents corresponding to strains and stresses, one explicit and one derived """ structure = Structure.from_dict(opt_doc['output']['structure']) input_structure = Structure.from_dict(opt_doc['input']['structure']) # Process explicit calcs, store in dict keyed by strain explicit_calcs = TensorMapping() for doc in defo_docs: calc = { "type": "explicit", "input": doc["input"], "output": doc["output"], "task_id": doc["task_id"], "completed_at": doc["completed_at"] } deformed_structure = Structure.from_dict(doc['output']['structure']) defo = Deformation(calculate_deformation(structure, deformed_structure)) # Warning if deformation is not equivalent to stored deformation stored_defo = doc['transmuter']['transformation_params'][0]\ ['deformation'] if not np.allclose(defo, stored_defo, atol=1e-5): wmsg = "Inequivalent stored and calc. deformations." logger.debug(wmsg) calc["warnings"] = wmsg cauchy_stress = -0.1 * Stress(doc['output']['stress']) pk_stress = cauchy_stress.piola_kirchoff_2(defo) strain = defo.green_lagrange_strain calc.update({ "deformation": defo, "cauchy_stress": cauchy_stress, "strain": strain, "pk_stress": pk_stress }) if strain in explicit_calcs: existing_value = explicit_calcs[strain] if doc['completed_at'] > existing_value['completed_at']: explicit_calcs[strain] = calc else: explicit_calcs[strain] = calc if not add_derived: return explicit_calcs.values(), None # Determine all of the implicit calculations to include sga = SpacegroupAnalyzer(structure, symprec=0.1) symmops = sga.get_symmetry_operations(cartesian=True) derived_calcs_by_strain = TensorMapping(tol=0.002) for strain, calc in explicit_calcs.items(): # Generate all transformed strains task_id = calc['task_id'] tstrains = [(symmop, strain.transform(symmop)) for symmop in symmops] # Filter strains by those which are independent and new # For second order if len(explicit_calcs) < 30: tstrains = [(symmop, tstrain) for symmop, tstrain in tstrains if tstrain.get_deformation_matrix().is_independent(tol) and not tstrain in explicit_calcs] # For third order else: strain_states = get_default_strain_states(3) # Default stencil in atomate, this maybe shouldn't be hard-coded stencil = np.linspace(-0.075, 0.075, 7) valid_strains = [ Strain.from_voigt(s * np.array(strain_state)) for s, strain_state in product(stencil, strain_states) ] valid_strains = [v for v in valid_strains if not np.allclose(v, 0)] valid_strains = TensorMapping(valid_strains, [True] * len(valid_strains)) tstrains = [ (symmop, tstrain) for symmop, tstrain in tstrains if tstrain in valid_strains and not tstrain in explicit_calcs ] # Add surviving tensors to derived_strains dict for symmop, tstrain in tstrains: # curr_set = derived_calcs_by_strain[tstrain] if tstrain in derived_calcs_by_strain: curr_set = derived_calcs_by_strain[tstrain] curr_task_ids = [c[1] for c in curr_set] if task_id not in curr_task_ids: curr_set.append((symmop, calc['task_id'])) else: derived_calcs_by_strain[tstrain] = [(symmop, calc['task_id'])] # Process derived calcs explicit_calcs_by_id = {d['task_id']: d for d in explicit_calcs.values()} derived_calcs = [] for strain, calc_set in derived_calcs_by_strain.items(): symmops, task_ids = zip(*calc_set) task_strains = [ Strain(explicit_calcs_by_id[task_id]['strain']) for task_id in task_ids ] task_stresses = [ explicit_calcs_by_id[task_id]['cauchy_stress'] for task_id in task_ids ] derived_strains = [ tstrain.transform(symmop) for tstrain, symmop in zip(task_strains, symmops) ] for derived_strain in derived_strains: if not np.allclose(derived_strain, strain, atol=2e-3): logger.info("Issue with derived strains") raise ValueError("Issue with derived strains") derived_stresses = [ tstress.transform(sop) for sop, tstress in zip(symmops, task_stresses) ] input_docs = [{ "task_id": task_id, "strain": task_strain, "cauchy_stress": task_stress, "symmop": symmop } for task_id, task_strain, task_stress, symmop in zip( task_ids, task_strains, task_stresses, symmops)] calc = { "strain": strain, "cauchy_stress": Stress(np.average(derived_stresses, axis=0)), "deformation": strain.get_deformation_matrix(), "input_tasks": input_docs, "type": "derived" } calc['pk_stress'] = calc['cauchy_stress'].piola_kirchoff_2( calc['deformation']) derived_calcs.append(calc) return list(explicit_calcs.values()), derived_calcs
class SpacegroupAnalyzerTest(PymatgenTest): def setUp(self): p = Poscar.from_file(os.path.join(test_dir, 'POSCAR')) self.structure = p.structure self.sg = SpacegroupAnalyzer(self.structure, 0.001) self.disordered_structure = self.get_structure('Li10GeP2S12') self.disordered_sg = SpacegroupAnalyzer(self.disordered_structure, 0.001) s = p.structure.copy() site = s[0] del s[0] s.append(site.species_and_occu, site.frac_coords) self.sg3 = SpacegroupAnalyzer(s, 0.001) graphite = self.get_structure('Graphite') graphite.add_site_property("magmom", [0.1] * len(graphite)) self.sg4 = SpacegroupAnalyzer(graphite, 0.001) def test_get_space_symbol(self): self.assertEqual(self.sg.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.disordered_sg.get_spacegroup_symbol(), "P4_2/nmc") self.assertEqual(self.sg3.get_spacegroup_symbol(), "Pnma") self.assertEqual(self.sg4.get_spacegroup_symbol(), "R-3m") def test_get_space_number(self): self.assertEqual(self.sg.get_spacegroup_number(), 62) self.assertEqual(self.disordered_sg.get_spacegroup_number(), 137) self.assertEqual(self.sg4.get_spacegroup_number(), 166) def test_get_hall(self): self.assertEqual(self.sg.get_hall(), '-P 2ac 2n') self.assertEqual(self.disordered_sg.get_hall(), 'P 4n 2n -1n') def test_get_pointgroup(self): self.assertEqual(self.sg.get_point_group(), 'mmm') self.assertEqual(self.disordered_sg.get_point_group(), '4/mmm') def test_get_symmetry_dataset(self): ds = self.sg.get_symmetry_dataset() self.assertEqual(ds['international'], 'Pnma') def test_get_crystal_system(self): crystal_system = self.sg.get_crystal_system() self.assertEqual('orthorhombic', crystal_system) self.assertEqual('tetragonal', self.disordered_sg.get_crystal_system()) def test_get_symmetry_operations(self): fracsymmops = self.sg.get_symmetry_operations() symmops = self.sg.get_symmetry_operations(True) self.assertEqual(len(symmops), 8) latt = self.structure.lattice for fop, op in zip(fracsymmops, symmops): for site in self.structure: newfrac = fop.operate(site.frac_coords) newcart = op.operate(site.coords) self.assertTrue(np.allclose(latt.get_fractional_coords(newcart), newfrac)) found = False newsite = PeriodicSite(site.species_and_occu, newcart, latt, coords_are_cartesian=True) for testsite in self.structure: if newsite.is_periodic_image(testsite, 1e-3): found = True break self.assertTrue(found) def test_get_refined_structure(self): for a in self.sg.get_refined_structure().lattice.angles: self.assertEqual(a, 90) refined = self.disordered_sg.get_refined_structure() for a in refined.lattice.angles: self.assertEqual(a, 90) self.assertEqual(refined.lattice.a, refined.lattice.b) s = self.get_structure('Li2O') sg = SpacegroupAnalyzer(s, 0.001) self.assertEqual(sg.get_refined_structure().num_sites, 4 * s.num_sites) def test_get_symmetrized_structure(self): symm_struct = self.sg.get_symmetrized_structure() for a in symm_struct.lattice.angles: self.assertEqual(a, 90) self.assertEqual(len(symm_struct.equivalent_sites), 5) symm_struct = self.disordered_sg.get_symmetrized_structure() self.assertEqual(len(symm_struct.equivalent_sites), 8) self.assertEqual([len(i) for i in symm_struct.equivalent_sites], [16,4,8,4,2,8,8,8]) s1 = symm_struct.equivalent_sites[1][1] s2 = symm_struct[symm_struct.equivalent_indices[1][1]] self.assertEqual(s1, s2) self.assertEqual(self.sg4.get_symmetrized_structure()[0].magmom, 0.1) def test_find_primitive(self): """ F m -3 m Li2O testing of converting to primitive cell """ parser = CifParser(os.path.join(test_dir, 'Li2O.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure) primitive_structure = s.find_primitive() self.assertEqual(primitive_structure.formula, "Li2 O1") # This isn't what is expected. All the angles should be 60 self.assertAlmostEqual(primitive_structure.lattice.alpha, 60) self.assertAlmostEqual(primitive_structure.lattice.beta, 60) self.assertAlmostEqual(primitive_structure.lattice.gamma, 60) self.assertAlmostEqual(primitive_structure.lattice.volume, structure.lattice.volume / 4.0) def test_get_ir_reciprocal_mesh(self): grid=self.sg.get_ir_reciprocal_mesh() self.assertEqual(len(grid), 216) self.assertAlmostEquals(grid[1][0][0], 0.1) self.assertAlmostEquals(grid[1][0][1], 0.0) self.assertAlmostEquals(grid[1][0][2], 0.0) self.assertEqual(grid[1][1], 2) def test_get_conventional_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.b, 9.1980270633769461) self.assertAlmostEqual(conv.lattice.c, 9.1980270633769461) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.b, 5.0615106678044235) self.assertAlmostEqual(conv.lattice.c, 4.2327080177761687) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 2.9542233922299999) self.assertAlmostEqual(conv.lattice.b, 4.6330325651443296) self.assertAlmostEqual(conv.lattice.c, 5.373703587040775) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 4.1430033493799998) self.assertAlmostEqual(conv.lattice.b, 31.437979757624728) self.assertAlmostEqual(conv.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 117.53832420192903) self.assertAlmostEqual(conv.lattice.gamma, 90) self.assertAlmostEqual(conv.lattice.a, 14.033435583000625) self.assertAlmostEqual(conv.lattice.b, 3.96052850731) self.assertAlmostEqual(conv.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) conv = s.get_conventional_standard_structure() self.assertAlmostEqual(conv.lattice.alpha, 90) self.assertAlmostEqual(conv.lattice.beta, 90) self.assertAlmostEqual(conv.lattice.gamma, 120) self.assertAlmostEqual(conv.lattice.a, 3.699919902005897) self.assertAlmostEqual(conv.lattice.b, 3.699919902005897) self.assertAlmostEqual(conv.lattice.c, 6.9779585500000003) def test_get_primitive_standard_structure(self): parser = CifParser(os.path.join(test_dir, 'bcc_1927.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 109.47122063400001) self.assertAlmostEqual(prim.lattice.beta, 109.47122063400001) self.assertAlmostEqual(prim.lattice.gamma, 109.47122063400001) self.assertAlmostEqual(prim.lattice.a, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.b, 7.9657251015812145) self.assertAlmostEqual(prim.lattice.c, 7.9657251015812145) parser = CifParser(os.path.join(test_dir, 'btet_1915.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 105.015053349) self.assertAlmostEqual(prim.lattice.beta, 105.015053349) self.assertAlmostEqual(prim.lattice.gamma, 118.80658411899999) self.assertAlmostEqual(prim.lattice.a, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.b, 4.1579321075608791) self.assertAlmostEqual(prim.lattice.c, 4.1579321075608791) parser = CifParser(os.path.join(test_dir, 'orci_1010.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 134.78923546600001) self.assertAlmostEqual(prim.lattice.beta, 105.856239333) self.assertAlmostEqual(prim.lattice.gamma, 91.276341676000001) self.assertAlmostEqual(prim.lattice.a, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.b, 3.8428217771014852) self.assertAlmostEqual(prim.lattice.c, 3.8428217771014852) parser = CifParser(os.path.join(test_dir, 'orcc_1003.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 164.985257335) self.assertAlmostEqual(prim.lattice.a, 15.854897098324196) self.assertAlmostEqual(prim.lattice.b, 15.854897098324196) self.assertAlmostEqual(prim.lattice.c, 3.99648651) parser = CifParser(os.path.join(test_dir, 'monoc_1028.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 63.579155761999999) self.assertAlmostEqual(prim.lattice.beta, 116.42084423747779) self.assertAlmostEqual(prim.lattice.gamma, 148.47965136208569) self.assertAlmostEqual(prim.lattice.a, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.b, 7.2908007159612325) self.assertAlmostEqual(prim.lattice.c, 6.8743926325200002) parser = CifParser(os.path.join(test_dir, 'rhomb_1170.cif')) structure = parser.get_structures(False)[0] s = SpacegroupAnalyzer(structure, symprec=1e-2) prim = s.get_primitive_standard_structure() self.assertAlmostEqual(prim.lattice.alpha, 90) self.assertAlmostEqual(prim.lattice.beta, 90) self.assertAlmostEqual(prim.lattice.gamma, 120) self.assertAlmostEqual(prim.lattice.a, 3.699919902005897) self.assertAlmostEqual(prim.lattice.b, 3.699919902005897) self.assertAlmostEqual(prim.lattice.c, 6.9779585500000003)
def __init__(self, struct, find_spacegroup=False, symprec=None): """ Args: struct (Structure): structure to write find_spacegroup (bool): whether to try to determine the spacegroup symprec (float): If not none, finds the symmetry of the structure and writes the cif with symmetry information. Passes symprec to the SpacegroupAnalyzer """ format_str = "{:.8f}" block = OrderedDict() loops = [] latt = struct.lattice comp = struct.composition no_oxi_comp = comp.element_composition spacegroup = ("P 1", 1) if find_spacegroup: sf = SpacegroupAnalyzer(struct, 0.001) spacegroup = (sf.get_spacegroup_symbol(), sf.get_spacegroup_number()) 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 = no_oxi_comp.reduced_composition el = no_oxi_comp.elements[0] amt = comp[el] fu = int(amt / reduced_comp[Element(el.symbol)]) block["_cell_formula_units_Z"] = str(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) ops = [op.as_xyz_string() for op in sf.get_symmetry_operations()] 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: for group in sf.get_symmetrized_structure().equivalent_sites: site = group[0] for sp, occu in site.species_and_occu.items(): atom_site_type_symbol.append(sp.__str__()) atom_site_symmetry_multiplicity.append("%d" % len(group)) 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 __init__(self, struct, symprec=None, write_magmoms=False): """ A wrapper around CifFile to write CIF files from pymatgen structures. Args: struct (Structure): structure to write symprec (float): If not none, finds the symmetry of the structure and writes the cif with symmetry information. Passes symprec to the SpacegroupAnalyzer write_magmoms (bool): If True, will write magCIF file. Incompatible with symprec """ if write_magmoms and symprec: warnings.warn( "Magnetic symmetry cannot currently be detected by pymatgen.") 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) symmops = [] for op in sf.get_symmetry_operations(): v = op.translation_vector 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 = [] atom_site_moment_label = [] atom_site_moment_crystalaxis_x = [] atom_site_moment_crystalaxis_y = [] atom_site_moment_crystalaxis_z = [] count = 1 if symprec is None: for site in struct: for sp, occu in sorted(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__()) magmom = site.properties.get('magmom', Magmom(0)) moment = Magmom.get_moment_relative_to_crystal_axes( magmom, latt) if write_magmoms and abs(magmom) > 0: atom_site_moment_label.append("{}{}".format( sp.symbol, count)) atom_site_moment_crystalaxis_x.append(moment[0]) atom_site_moment_crystalaxis_y.append(moment[1]) atom_site_moment_crystalaxis_z.append(moment[2]) 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" ]) if write_magmoms: block["_atom_site_moment_label"] = atom_site_moment_label block[ "_atom_site_moment_crystalaxis_x"] = atom_site_moment_crystalaxis_x block[ "_atom_site_moment_crystalaxis_y"] = atom_site_moment_crystalaxis_y block[ "_atom_site_moment_crystalaxis_z"] = atom_site_moment_crystalaxis_z loops.append([ "_atom_site_moment_label", "_atom_site_moment_crystalaxis_x", "_atom_site_moment_crystalaxis_y", "_atom_site_moment_crystalaxis_z" ]) d = OrderedDict() d[comp.reduced_formula] = CifBlock(block, loops, comp.reduced_formula) self._cf = CifFile(d)