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 subgroup_by_splitter(self, splitter, eps=0.05): lat1 = np.dot(splitter.R[:3, :3].T, self.lattice.matrix) multiples = np.linalg.det(splitter.R[:3, :3]) split_sites = [] for i, site in enumerate(self.atom_sites): pos = site.position for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]): pos0 = apply_ops(pos, ops1)[0] pos0 -= np.floor(pos0) pos0 += eps * (np.random.sample(3) - 0.5) wp, _ = Wyckoff_position.from_symops(ops2, group=splitter.H.number, permutation=False) split_sites.append(atom_site(wp, pos0, site.specie)) new_struc = deepcopy(self) new_struc.group = splitter.H lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type) new_struc.lattice = lattice.mutate(degree=0.01, frozen=True) new_struc.atom_sites = split_sites new_struc.numIons = [ int(multiples * numIon) for numIon in self.numIons ] new_struc.source = 'Wyckoff Split' return new_struc
def make_supergroup(self, solutions, once=False, show_detail=True): """ create supergroup structures based on the list of solutions Args: solutions: list of tuples (splitter, mapping, disp) show_detail (bool): print out the detail Returns: list of pyxtal structures """ G_strucs = [] if len(solutions) > 0: if once: disps = np.array([sol[-1] for sol in solutions]) ID = np.argmin(disps) solutions = [solutions[ID]] for solution in solutions: (sp, mapping, disp, mae) = solution lat1 = np.dot(np.linalg.inv(sp.R[:3,:3]).T, self.struc.lattice.matrix) lattice = Lattice.from_matrix(lat1, ltype=sp.G.lattice_type) details = self.symmetrize(sp, mapping, disp) coords_G1, coords_G2, coords_H1, elements = details G_struc = self.struc.copy() G_struc.group = sp.G G_sites = [] for i, wp in enumerate(sp.wp1_lists): pos = coords_G1[i] pos -= np.floor(pos) pos1 = sym.search_matched_position(sp.G, wp, pos) if pos1 is not None: site = atom_site(wp, pos1, sp.elements[i]) G_sites.append(site) else: print(">>>>>>>>>>>>>>") print(self.struc.group.number) print(pos) print(wp) print(">>>>>>>>>>>>>>") raise RuntimeError("cannot assign the right wp") G_struc.atom_sites = G_sites G_struc.source = 'supergroup {:6.3f}'.format(mae) G_struc.lattice = lattice G_struc.numIons *= round(np.abs(np.linalg.det(sp.R[:3,:3]))) G_struc._get_formula() G_struc.disp = mae if new_structure(G_struc, G_strucs): G_strucs.append(G_struc) if show_detail: G = sp.G.number self.print_detail(G, coords_H1, coords_G2, elements, disp) print(G_struc) return G_strucs
def read_cif(filename): """ read the cif, mainly for pyxtal cif output Be cautious in using it to read other cif files Args: filename: path of the structure file Return: pyxtal structure """ species = [] coords = [] with open(filename, 'r') as f: lines = f.readlines() for i, line in enumerate(lines): if line.startswith('_symmetry_Int_Tables_number'): sg = int(line.split()[-1]) elif line.startswith('_cell_length_a'): a = float(lines[i].split()[-1]) b = float(lines[i+1].split()[-1]) c = float(lines[i+2].split()[-1]) alpha = float(lines[i+3].split()[-1]) beta = float(lines[i+4].split()[-1]) gamma = float(lines[i+5].split()[-1]) elif line.startswith('_symmetry_cell_setting'): lat_type = line.split()[-1] elif line.startswith('_symmetry_space_group_name_H-M '): symbol = line.split()[-1] if eval(symbol) in ["Pn", "P21/n", "C2/n"]: diag = True else: diag = False elif line.find('_atom_site') >= 0: s = i while True: s += 1 if lines[s].find('_atom_site') >= 0: pass elif len(lines[s].split()) <= 3: break else: tmp = lines[s].split() pos = [float(tmp[-4]), float(tmp[-3]), float(tmp[-2])] species.append(tmp[0]) coords.append(pos) break wp0 = Group(sg)[0] lattice = Lattice.from_para(a, b, c, alpha, beta, gamma, lat_type) sites = [] for specie, coord in zip(species, coords): pt, wp, _ = WP_merge(coord, lattice.matrix, wp0, tol=0.1) sites.append(atom_site(wp, pt, specie, diag)) return lattice, sites
def optimize_lattice(self, iterations=3): """ optimize the lattice if the cell has a bad inclination angles """ #if self.molecular: count = 0 for i in range(iterations): lattice, trans, opt = self.lattice.optimize() #print(self.lattice, "->", lattice) if opt: if self.molecular: sites = self.mol_sites else: sites = self.atom_sites for j, site in enumerate(sites): count += 1 pos_abs = np.dot(site.position, self.lattice.matrix) pos_frac = pos_abs.dot(lattice.inv_matrix) pos_frac -= np.floor(pos_frac) if self.molecular: site.lattice = lattice # for P21/c, Pc, C2/c, check if opt the inclination angle ops = site.wp.ops.copy() diag = False if self.group.number in [7, 14, 15]: for k, op in enumerate(ops): vec = op.translation_vector.dot(trans) #print(vec) vec -= np.floor(vec) op1 = op.from_rotation_and_translation( op.rotation_matrix, vec) ops[k] = op1 wp, perm = Wyckoff_position.from_symops( ops, self.group.number) if not isinstance(perm, list): diag = True else: diag = False pos_frac = pos_frac[perm] sites[j] = atom_site(wp, pos_frac, site.specie, diag) #print(sites[j].wp) #site.update() self.lattice = lattice self.diag = diag else: break
def make_supergroup(self, solutions, show_detail=True): """ create supergroup structures based on the list of solutions Args: solutions: list of tuples (splitter, mapping, disp) Returns: list of pyxtal structures """ G_strucs = [] for solution in solutions: (sp, mapping, disp, mae) = solution G = sp.G.number #print(disp) details = self.symmetrize(sp, mapping, disp) coords_G1, coords_G2, coords_H1, elements, mults = details G_struc = self.struc.copy() G_struc.group = sp.G G_sites = [] for i, wp in enumerate(sp.wp1_lists): site = atom_site(wp, coords_G1[i], sp.elements[i]) G_sites.append(site) G_struc.atom_sites = G_sites G_struc.source = 'supergroup {:6.3f}'.format(mae) lat1 = np.dot(sp.inv_R[:3, :3].T, self.struc.lattice.matrix) lattice = Lattice.from_matrix(lat1, ltype=sp.G.lattice_type) #lattice.reset_matrix() #make it has a regular shape #G_struc.lattice = self.struc.lattice.supergroup(sp.G.lattice_type) G_struc.lattice = lattice G_struc.numIons *= round(np.abs(np.linalg.det(sp.R[:3, :3]))) G_struc._get_formula() if new_structure(G_struc, G_strucs): G_strucs.append(G_struc) if show_detail: details = self.symmetrize(sp, mapping, disp) _, coords_G2, coords_H1, elements, mults = details self.print_detail(G, coords_H1, coords_G2, elements, mults, disp) print(G_struc) #print(sp.R) return G_strucs
def _from_pymatgen(self, struc, tol=1e-3, a_tol=5.0): """ Load structure from Pymatgen should not be used directly """ from pyxtal.util import get_symmetrized_pmg #import pymatgen.analysis.structure_matcher as sm self.valid = True try: sym_struc, number = get_symmetrized_pmg(struc, tol, a_tol) #print(sym_struc) #import sys; sys.exit() except TypeError: print("Failed to load the Pymatgen structure") # print(struc) # 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) matrix, ltype = sym_struc.lattice.matrix, self.group.lattice_type self.lattice = Lattice.from_matrix(matrix, ltype=ltype) 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 pos1 = search_matched_position(self.group, wp, pos) if pos1 is not None: atom_sites.append(atom_site(wp, pos1, specie)) else: break if len(atom_sites) != len(sym_struc.equivalent_sites): raise RuntimeError("Cannot extract the right mapping from spglib") else: self.atom_sites = atom_sites
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_alternative(self, wyc_set, index): """ get alternative structure representations Args: tran: affine matrix index: the list of transformed wps Returns: a new pyxtal structure after transformation """ new_struc = self.copy() # xyz_string like 'x+1/4,y+1/4,z+1/4' xyz_string = wyc_set['Coset Representative'][index] op = get_inverse(SymmOp.from_xyz_string(xyz_string)) #op = SymmOp.from_xyz_string(xyz_string) ids = [] for i, site in enumerate(new_struc.atom_sites): id = len(self.group) - site.wp.index - 1 letter = wyc_set['Transformed WP'][index].split()[id] ids.append(letters.index(letter)) wp = Wyckoff_position.from_group_and_index(self.group.number, letter) pos = op.operate(site.position) pos1 = search_matched_position(self.group, wp, pos) if pos1 is not None: new_struc.atom_sites[i] = atom_site(wp, pos1, site.specie) else: print(pos) print(wp) raise RuntimeError("Cannot find the right pos") # switch lattice R = op.affine_matrix[:3,:3] #rotation matrix = np.dot(R, self.lattice.matrix) new_struc.lattice = Lattice.from_matrix(matrix, ltype=self.group.lattice_type) new_struc.source = "Alt. Wyckoff Set: " + xyz_string return new_struc, ids
def _generate_ion_wyckoffs(self, numIon, specie, cell_matrix, wyks): """ generates a set of wyckoff positions to accomodate a given number of ions Args: numIon: Number of ions to accomodate specie: Type of species being placed on wyckoff site cell_matrix: Matrix of lattice vectors wyks: current wyckoff sites Returns: Sucess: wyckoff_sites_tmp: list of wyckoff sites for valid sites Failue: None """ numIon_added = 0 tol = self.tol_matrix.get_tol(specie, specie) tol_matrix = self.tol_matrix wyckoff_sites_tmp = [] # Now we start to add the specie to the wyckoff position sites_list = deepcopy(self.sites[specie]) # the list of Wyckoff site if sites_list is not None: wyckoff_attempts = max(len(sites_list) * 2, 10) else: # the minimum numattempts is to put all atoms to the general WPs min_wyckoffs = int(numIon / len(self.group.wyckoffs_organized[0][0])) wyckoff_attempts = max(2 * min_wyckoffs, 10) cycle = 0 while cycle < wyckoff_attempts: # Choose a random WP for given multiplicity: 2a, 2b if sites_list is not None: site = sites_list[0] else: # Selecting the merging site = None wp = choose_wyckoff(self.group, numIon - numIon_added, site, self.dim) if wp is not False: # Generate a list of coords from ops mult = wp.multiplicity # remember the original multiplicity pt = self.lattice.generate_point() # Merge coordinates if the atoms are close pt, wp, _ = WP_merge(pt, cell_matrix, wp, tol) # For pure planar structure if self.dim == 2 and self.thickness is not None and self.thickness < 0.1: pt[-1] = 0.5 # If site the pre-assigned, do not accept merge if wp is not False: if site is not None and mult != wp.multiplicity: cycle += 1 continue # Use a Wyckoff_site object for the current site new_site = atom_site(wp, pt, specie) # Check current WP against existing WP's passed_wp_check = True for ws in wyckoff_sites_tmp + wyks: if not new_site.check_with_ws2(ws, cell_matrix, tol_matrix): passed_wp_check = False if passed_wp_check: if sites_list is not None: sites_list.pop(0) wyckoff_sites_tmp.append(new_site) numIon_added += new_site.multiplicity # Check if enough atoms have been added if numIon_added == numIon: return wyckoff_sites_tmp cycle += 1 self.numattempts += 1 return None
def subgroup_by_splitter(self, splitter, eps=0.05): """ transform the crystal to subgroup symmetry from a splitter object """ lat1 = np.dot(splitter.R[:3, :3].T, self.lattice.matrix) multiples = np.linalg.det(splitter.R[:3, :3]) new_struc = deepcopy(self) new_struc.group = splitter.H lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type) lattice = lattice.mutate(degree=eps, frozen=True) h = splitter.H.number split_sites = [] if self.molecular: # below only works when the cell does not change for i, site in enumerate(self.mol_sites): pos = site.position mol = site.molecule ori = site.orientation coord0 = mol.mol.cart_coords.dot(ori.matrix.T) wp1 = site.wp ori.reset_matrix(np.eye(3)) for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]): #reset molecule coord1 = np.dot(coord0, ops1[0].affine_matrix[:3, :3].T) _mol = mol.copy() _mol.reset_positions(coord1) pos0 = apply_ops(pos, ops1)[0] pos0 -= np.floor(pos0) pos0 += eps * (np.random.sample(3) - 0.5) wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False) split_sites.append(mol_site(_mol, pos0, ori, wp, lattice)) new_struc.mol_sites = split_sites new_struc.numMols = [ int(multiples * numMol) for numMol in self.numMols ] else: for i, site in enumerate(self.atom_sites): pos = site.position for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]): pos0 = apply_ops(pos, ops1)[0] pos0 -= np.floor(pos0) pos0 += eps * (np.random.sample(3) - 0.5) wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False) split_sites.append(atom_site(wp, pos0, site.specie)) new_struc.atom_sites = split_sites new_struc.numIons = [ int(multiples * numIon) for numIon in self.numIons ] new_struc.lattice = lattice new_struc.source = 'Wyckoff Split' return new_struc
def _subgroup_by_splitter(self, splitter, eps=0.05, mut_lat=True): """ transform the crystal to subgroup symmetry from a splitter object Args: splitter: wyckoff splitter object eps (float): maximum atomic displacement in Angstrom mut_lat (bool): whether or not mutate the lattice """ lat1 = np.dot(splitter.R[:3,:3].T, self.lattice.matrix) multiples = np.linalg.det(splitter.R[:3,:3]) new_struc = self.copy() new_struc.group = splitter.H lattice = Lattice.from_matrix(lat1, ltype=new_struc.group.lattice_type) if mut_lat: lattice=lattice.mutate(degree=eps, frozen=True) h = splitter.H.number split_sites = [] if self.molecular: # below only works when the cell does not change for i, site in enumerate(self.mol_sites): pos = site.position mol = site.molecule ori = site.orientation coord0 = mol.mol.cart_coords.dot(ori.matrix.T) coord0 = np.dot(coord0, splitter.R[:3,:3]) wp1 = site.wp ori.reset_matrix(np.eye(3)) id = 0 for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]): #reset molecule rot = wp1.generators_m[id].affine_matrix[:3,:3].T coord1 = np.dot(coord0, rot) _mol = mol.copy() _mol.reset_positions(coord1) pos0 = apply_ops(pos, ops1)[0] pos0 -= np.floor(pos0) dis = (np.random.sample(3) - 0.5).dot(self.lattice.matrix) dis /= np.linalg.norm(dis) pos0 += eps*dis*(np.random.random()-0.5) wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False) if h in [7, 14] and self.group.number == 31: diag = True else: diag = self.diag split_sites.append(mol_site(_mol, pos0, ori, wp, lattice, diag)) id += wp.multiplicity new_struc.mol_sites = split_sites new_struc.numMols = [int(multiples*numMol) for numMol in self.numMols] else: for i, site in enumerate(self.atom_sites): pos = site.position for ops1, ops2 in zip(splitter.G2_orbits[i], splitter.H_orbits[i]): pos0 = apply_ops(pos, ops1)[0] pos0 -= np.floor(pos0) dis = (np.random.sample(3) - 0.5).dot(self.lattice.matrix) dis /= np.linalg.norm(dis) pos0 += np.dot(eps*dis*(np.random.random()-0.5), self.lattice.inv_matrix) wp, _ = Wyckoff_position.from_symops(ops2, h, permutation=False) split_sites.append(atom_site(wp, pos0, site.specie)) new_struc.atom_sites = split_sites new_struc.numIons = [int(multiples*numIon) for numIon in self.numIons] new_struc.lattice = lattice new_struc.source = 'subgroup' return new_struc