def from_pymatgen(self, structure): """ Load the seed structure from Pymatgen/ASE/POSCAR/CIFs """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as sga try: # needs to do it twice in order to get the conventional cell s = sga(structure) structure = s.get_refined_structure() s = sga(structure) sym_struc = s.get_symmetrized_structure() number = s.get_space_group_number() except: print("Failed to load the Pymatgen structure") self.valid = False if self.valid: d = sym_struc.composition.as_dict() species = [key for key in d.keys()] numIons = [] for ele in species: numIons.append(int(d[ele])) self.numIons = numIons self.species = species self.group = Group(number) atom_sites = [] for i, site in enumerate(sym_struc.equivalent_sites): pos = site[0].frac_coords wp = Wyckoff_position.from_group_and_index( number, sym_struc.wyckoff_symbols[i]) specie = site[0].specie.number atom_sites.append(atom_site(wp, pos, specie)) self.atom_sites = atom_sites self.lattice = Lattice.from_matrix(sym_struc.lattice.matrix, ltype=self.group.lattice_type)
def plotBZ(): from pymatgen.electronic_structure.plotter import \ plot_brillouin_zone_from_kpath as pltBZ st = bands.structure st_sym = sga(st) prim = sga(st).get_primitive_standard_structure( international_monoclinic=False) t = '{0} - {1} ({2})'.format(st_sym.get_lattice_type().capitalize(), st_sym.get_space_group_symbol(), st_sym.get_space_group_number()) pltBZ(HighSymmKpath(prim), title=t)
def get_site_symmetries(struc, precision=0.1): """ Get all the point group operations centered on each atomic site in the form [[point operations of site index 1]...[[point operations of site index N]]] Args: struc: Pymatgen structure precision (float): tolerance to find symmetry operaitons Return: list of lists of point operations for each atomic site """ pointops = [] # Point symmetries of each atom for site1 in range(len(struc.sites)): tempstruc = struc.copy() # Place the origin of the cell at each atomic site pointops.append([]) for site2 in range(len(struc.sites)): tempstruc.replace( site2, tempstruc.sites[site2].specie, tempstruc.frac_coords[site2] - struc.frac_coords[site1], ) sgastruc = sga(tempstruc, symprec=precision) ops = sgastruc.get_symmetry_operations(cartesian=True) for site2, op in enumerate(ops): if all(op.translation_vector == [0, 0, 0]): pointops[site1].append(op) return pointops
def get_BEC_operations(self, eigtol=1e-05, opstol=1e-03): """ Returns the symmetry operations which maps the tensors belonging to equivalent sites onto each other in the form [site index 1, site index 2, [Symmops mapping from site index 1 to site index 2]] Args: eigtol (float): tolerance for determining if two sites are related by symmetry opstol (float): tolerance for determining if a symmetry operation relates two sites Return: list of symmetry operations mapping equivalent sites and the indexes of those sites. """ bec = self.bec struc = self.structure ops = sga(struc).get_symmetry_operations(cartesian=True) uniquepointops = [] for op in ops: uniquepointops.append(op) for atom in range(len(self.pointops)): for op in self.pointops[atom]: if op not in uniquepointops: uniquepointops.append(op) passed = [] relations = [] for site in range(len(bec)): unique = 1 eig1, vecs1 = np.linalg.eig(bec[site]) index = np.argsort(eig1) neweig = np.real([eig1[index[0]], eig1[index[1]], eig1[index[2]]]) for index in range(len(passed)): if np.allclose(neweig, passed[index][1], atol=eigtol): relations.append([site, index]) unique = 0 passed.append([site, passed[index][0], neweig]) break if unique == 1: relations.append([site, site]) passed.append([site, neweig]) BEC_operations = [] for atom in range(len(relations)): BEC_operations.append(relations[atom]) BEC_operations[atom].append([]) for op in uniquepointops: new = op.transform_tensor(self.bec[relations[atom][1]]) # Check the matrix it references if np.allclose(new, self.bec[relations[atom][0]], atol=opstol): BEC_operations[atom][2].append(op) self.BEC_operations = BEC_operations
def get_rand_BEC(self, max_charge=1): """ Generate a random born effective charge tensor which obeys a structure's symmetry and the acoustic sum rule Args: max_charge (float): maximum born effective charge value Return: np.array Born effective charge tensor """ struc = self.structure symstruc = sga(struc) symstruc = symstruc.get_symmetrized_structure() l = len(struc) BEC = np.zeros((l, 3, 3)) for atom, ops in enumerate(self.BEC_operations): if ops[0] == ops[1]: temp_tensor = Tensor(np.random.rand(3, 3) - 0.5) temp_tensor = sum(temp_tensor.transform(symm_op) for symm_op in self.pointops[atom]) / len( self.pointops[atom] ) BEC[atom] = temp_tensor else: tempfcm = np.zeros([3, 3]) for op in ops[2]: tempfcm += op.transform_tensor(BEC[self.BEC_operations[atom][1]]) BEC[ops[0]] = tempfcm if len(ops[2]) != 0: BEC[ops[0]] = BEC[ops[0]] / len(ops[2]) # Enforce Acoustic Sum disp_charge = np.einsum("ijk->jk", BEC) / l add = np.zeros([l, 3, 3]) for atom, ops in enumerate(self.BEC_operations): if ops[0] == ops[1]: temp_tensor = Tensor(disp_charge) temp_tensor = sum(temp_tensor.transform(symm_op) for symm_op in self.pointops[atom]) / len( self.pointops[atom] ) add[ops[0]] = temp_tensor else: temp_tensor = np.zeros([3, 3]) for op in ops[2]: temp_tensor += op.transform_tensor(add[self.BEC_operations[atom][1]]) add[ops[0]] = temp_tensor if len(ops) != 0: add[ops[0]] = add[ops[0]] / len(ops[2]) BEC = BEC - add return BEC * max_charge
def symmetrize_cell(struc, mode='C'): """ symmetrize structure from pymatgen, and return the struc in conventional/primitive setting Args: struc: ase type mode: output conventional or primitive cell """ P_struc = ase2pymatgen(struc) finder = sga(P_struc, symprec=0.06) if mode == 'C': P_struc = finder.get_conventional_standard_structure() else: P_struc = finder.get_primitive_standard_structure() return pymatgen2ase(P_struc)
def _from_pymatgen(self, struc, tol=1e-3): """ Load structure from Pymatgen should not be used directly """ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer as sga from pyxtal.util import symmetrize #import pymatgen.analysis.structure_matcher as sm self.valid = True try: # needs to do it twice in order to get the conventional cell pmg = symmetrize(struc, tol) s = sga(pmg, symprec=tol) sym_struc = s.get_symmetrized_structure() number = s.get_space_group_number() #print(sym_struc) except: print("Failed to load the Pymatgen structure") self.valid = False if self.valid: d = sym_struc.composition.as_dict() species = [key for key in d.keys()] numIons = [] for ele in species: numIons.append(int(d[ele])) self.numIons = numIons self.species = species self.group = Group(number) atom_sites = [] for i, site in enumerate(sym_struc.equivalent_sites): pos = site[0].frac_coords wp = Wyckoff_position.from_group_and_index( number, sym_struc.wyckoff_symbols[i]) specie = site[0].specie.number atom_sites.append(atom_site(wp, pos, specie, search=True)) self.atom_sites = atom_sites matrix, ltype = sym_struc.lattice.matrix, self.group.lattice_type self.lattice = Lattice.from_matrix(matrix, ltype=ltype)
def get_symmetrized_pmg(pmg, tol=1e-3, a_tol=5.0): """ Symmetrized Pymatgen structure A slight modification to ensure that the structure adopts the standard setting used in interational crystallography table Args: pmg: input pymatgen structure tol: symmetry tolerance """ pmg = symmetrize(pmg, tol, a_tol=a_tol) s = sga(pmg, symprec=tol, angle_tolerance=a_tol) hn = Group(s.get_space_group_number()).hall_number # make sure that the coordinates are in standard setting if hn != s._space_group_data["hall_number"]: s._space_group_data = get_symmetry_dataset(s._cell, tol, angle_tolerance=a_tol, hall_number=hn) return s.get_symmetrized_structure(), s.get_space_group_number()
def get_IST_operations(self, opstol=1e-03): """ Returns the symmetry operations which maps the tensors belonging to equivalent sites onto each other in the form [site index 1, site index 2, [Symmops mapping from site index 1 to site index 2]] Args: opstol (float): tolerance for determining if a symmetry operation relates two sites Return: list of symmetry operations mapping equivalent sites and the indexes of those sites. """ struc = self.structure ops = sga(struc).get_symmetry_operations(cartesian=True) uniquepointops = [] for op in ops: uniquepointops.append(op) for ops in self.pointops: for op in ops: if op not in uniquepointops: uniquepointops.append(op) IST_operations = [] for atom in range(len(self.ist)): # pylint: disable=C0200 IST_operations.append([]) for j in range(0, atom): for op in uniquepointops: new = op.transform_tensor(self.ist[j]) # Check the matrix it references if np.allclose(new, self.ist[atom], atol=opstol): IST_operations[atom].append([j, op]) self.IST_operations = IST_operations
def get_FCM_operations(self, eigtol=1e-05, opstol=1e-05): """ Returns the symmetry operations which maps the tensors belonging to equivalent sites onto each other in the form [site index 1a, site index 1b, site index 2a, site index 2b, [Symmops mapping from site index 1a, 1b to site index 2a, 2b]] Args: eigtol (float): tolerance for determining if two sites are related by symmetry opstol (float): tolerance for determining if a symmetry operation relates two sites Return: list of symmetry operations mapping equivalent sites and the indexes of those sites. """ struc = self.structure ops = sga(struc).get_symmetry_operations(cartesian=True) uniquepointops = [] for op in ops: uniquepointops.append(op) for ops in self.pointops: for op in ops: if op not in uniquepointops: uniquepointops.append(op) passed = [] relations = [] for atom1 in range(len(self.fcm)): # pylint: disable=C0200 for atom2 in range(atom1, len(self.fcm)): unique = 1 eig1, vecs1 = np.linalg.eig(self.fcm[atom1][atom2]) index = np.argsort(eig1) neweig = np.real( [eig1[index[0]], eig1[index[1]], eig1[index[2]]]) for entry, p in enumerate(passed): if np.allclose(neweig, p[2], atol=eigtol): relations.append([atom1, atom2, p[0], p[1]]) unique = 0 break if unique == 1: relations.append([atom1, atom2, atom2, atom1]) passed.append([atom1, atom2, np.real(neweig)]) FCM_operations = [] for entry, r in enumerate(relations): FCM_operations.append(r) FCM_operations[entry].append([]) good = 0 for op in uniquepointops: new = op.transform_tensor(self.fcm[r[2]][r[3]]) if np.allclose(new, self.fcm[r[0]][r[1]], atol=opstol): FCM_operations[entry][4].append(op) good = 1 if r[0] == r[3] and r[1] == r[2]: good = 1 if r[0] == r[2] and r[1] == r[3]: good = 1 if good == 0: FCM_operations[entry] = [ r[0], r[1], r[3], r[2], ] FCM_operations[entry].append([]) for op in uniquepointops: new = op.transform_tensor(self.fcm[r[2]][r[3]]) if np.allclose( new.T, self.fcm[r[0]][r[1]], atol=opstol, ): FCM_operations[entry][4].append(op) self.FCM_operations = FCM_operations return FCM_operations