def subgroup(self, H=None, eps=0.05, idx=None, once=False): """ generate a structure with lower symmetry Args: H: space group number (int) eps: pertubation term (float) idx: list once: generate only one structure, otherwise output all Returns: a list of pyxtal structures with lower symmetries """ #randomly choose a subgroup from the available list Hs = self.group.get_max_t_subgroup()['subgroup'] if idx is None: idx = range(len(Hs)) else: for id in idx: if id >= len(Hs): raise ValueError( "The idx exceeds the number of possible splits") if H is not None: idx = [id for id in idx if Hs[idx] == H] if len(idx) == 0: raise ValueError("The space group H is incompatible with idx") sites = [ str(site.wp.multiplicity) + site.wp.letter for site in self.atom_sites ] valid_splitters = [] bad_splitters = [] for id in idx: splitter = wyckoff_split(G=self.group.number, wp1=sites, idx=id) if splitter.valid_split: valid_splitters.append(splitter) else: bad_splitters.append(splitter) if len(valid_splitters) == 0: # do one more step new_strucs = [] for splitter in bad_splitters: trail_struc = self.subgroup_by_splitter(splitter) new_strucs.append(trail_struc.subgroup(once=True)) return new_strucs else: if once: return self.subgroup_by_splitter(choice(valid_splitters), eps=eps) else: new_strucs = [] for splitter in valid_splitters: new_strucs.append( self.subgroup_by_splitter(splitter, eps=eps)) return new_strucs
def get_displacement(self, G, split_id, solution, d_tol): """ For a given solution, search for the possbile supergroup structure Args: G: group object split_id (int): integer solution (list): e.g., [['2d'], ['6h'], ['2c', '6h', '12i']] d_tol (float): tolerance Returns: mae: mean absolute atomic displcement disp: overall cell translation """ sites_G = [] elements = [] muls = [] for i, e in enumerate(self.elements): sites_G.extend(solution[i]) elements.extend([e]*len(solution[i])) muls.extend([int(sol[:-1]) for sol in solution[i]]) # resort the sites_G by multiplicity ids = np.argsort(np.array(muls)) elements = [elements[id] for id in ids] sites_G = [sites_G[id] for id in ids] #print(G, self.struc.group.number, sites_G) splitter = wyckoff_split(G, split_id, sites_G, self.group_type, elements) mappings = find_mapping(self.struc.atom_sites, splitter) dists = [] disps = [] masks = [] if len(mappings) > 0: for mapping in mappings: dist, disp, mask = self.symmetrize_dist(splitter, mapping, None, None, d_tol) dists.append(dist) disps.append(disp) masks.append(mask) dists = np.array(dists) mae = np.min(dists) id = np.argmin(dists) disp = disps[id] mask = masks[id] if 0.2 < mae < d_tol: # optimize disp further if mask is None or len(mask)<3: def fun(disp, mapping, splitter, mask): return self.symmetrize_dist(splitter, mapping, disp, mask)[0] res = minimize(fun, disps[id], args=(mappings[id], splitter, mask), method='Nelder-Mead', options={'maxiter': 10}) if res.fun < mae: mae = res.fun disp = res.x return mae, disp, mappings[id], splitter else: print("bug in findding the mappings", solution) print(splitter.G.number, '->', splitter.H.number) return 1000, None, None, None
def get_displacement(self, G, split_id, solution, d_tol): """ For a given solution, search for the possbile supergroup structure Args: G: supergroup number split_id: integer solution: e.g., [['2d'], ['6h'], ['2c', '6h', '12i']] d_tol: Returns: mae: mean absolute atomic displcement disp: overall cell translation """ sites_G = [] elements = [] muls = [] for i, e in enumerate(self.elements): sites_G.extend(solution[i]) elements.extend([e] * len(solution[i])) muls.extend([int(sol[:-1]) for sol in solution[i]]) # resort the sites_G by multiplicity ids = np.argsort(np.array(muls)) elements = [elements[id] for id in ids] sites_G = [sites_G[id] for id in ids] #print(G, self.struc.group.number, sites_G) splitter = wyckoff_split(G, split_id, sites_G, self.group_type, elements) mappings = self.find_mapping(splitter) dists = [] disps = [] for mapping in mappings: #disp = None #np.array([0.0, 0.0, 0.222222]) dist, disp, mask = self.symmetrize_dist(splitter, mapping, None, None, d_tol) dists.append(dist) disps.append(disp) dists = np.array(dists) mae = np.min(dists) id = np.argmin(dists) disp = disps[id] if (mae > 0.2) and (mae < d_tol): # optimize further def fun(disp, mapping, splitter, mask): return self.symmetrize_dist(splitter, mapping, disp, mask)[0] res = minimize(fun, disps[id], args=(mappings[id], splitter, mask), method='Nelder-Mead', options={'maxiter': 20}) if res.fun < mae: mae = res.fun disp = res.x return mae, disp, mappings[id], splitter
def subgroup(self, H=None, eps=0.05, idx=None, once=False, group_type='t', max_index=4): """ generate a structure with lower symmetry Args: H: space group number (int) eps: pertubation term (float) idx: list once: generate only one structure, otherwise output all Returns: a list of pyxtal structures with lower symmetries """ #randomly choose a subgroup from the available list if group_type == 't': dicts = self.group.get_max_t_subgroup() #['subgroup'] else: dicts = self.group.get_max_k_subgroup() #['subgroup'] Hs = dicts['subgroup'] indices = dicts['index'] if idx is None: idx = [i for i, id in enumerate(indices) if id <= max_index] #idx = range(len(Hs)) else: for id in idx: if id >= len(Hs): raise ValueError( "The idx exceeds the number of possible splits") if H is not None: idx = [id for id in idx if Hs[id] == H] if len(idx) == 0: raise RuntimeError("No subgroup to perform the split") if self.molecular: struc_sites = self.mol_sites else: struc_sites = self.atom_sites sites = [ str(site.wp.multiplicity) + site.wp.letter for site in struc_sites ] valid_splitters = [] bad_splitters = [] for id in idx: splitter = wyckoff_split(G=self.group.number, wp1=sites, idx=id, group_type=group_type) if splitter.valid_split: valid_splitters.append(splitter) else: bad_splitters.append(splitter) if len(valid_splitters) == 0: # do one more step new_strucs = [] for splitter in bad_splitters: trail_struc = self.subgroup_by_splitter(splitter) new_strucs.append( trail_struc.subgroup(once=True, group_type=group_type)) return new_strucs else: if once: return self.subgroup_by_splitter(choice(valid_splitters), eps=eps) else: new_strucs = [] for splitter in valid_splitters: new_strucs.append( self.subgroup_by_splitter(splitter, eps=eps)) return new_strucs
def subgroup_once(self, eps=0.1, H=None, permutations=None, group_type='t', max_cell=4, mut_lat=True): """ generate a structure with lower symmetry (for atomic crystals only) Args: permutations: e.g., {"Si": "C"} H: space group number (int) idx: list group_type: `t` or `k` max_cell: maximum cell reconstruction (float) Returns: a list of pyxtal structures with lower symmetries """ idx, sites, t_types, k_types = self._get_subgroup_ids(H, group_type, None, max_cell) # Try 100 times to see if a valid split can be found count = 0 while count < 100: id = choice(idx) gtype = (t_types+k_types)[id] if gtype == 'k': id -= len(t_types) #print(self.group.number, sites, id, gtype) splitter = wyckoff_split(G=self.group.number, wp1=sites, idx=id, group_type=gtype) if not splitter.error: if permutations is not None: if len(splitter.H_orbits) == 1: if len(splitter.H_orbits[0]) > 1: return self._apply_substitution(splitter, permutations) else: #print("try to find the next subgroup") trail_struc = self._subgroup_by_splitter(splitter, eps=eps, mut_lat=mut_lat) multiple = sum(trail_struc.numIons)/sum(self.numIons) max_cell = max([1, max_cell/multiple]) ans = trail_struc.subgroup_once(eps, H, permutations, group_type, max_cell) if ans.group.number > 1: return ans else: return self._apply_substitution(splitter, permutations) else: if splitter.valid_split: special = False if self.molecular: for i in range(len(self.mol_sites)): for ops in splitter.H_orbits[i]: if len(ops) < len(splitter.H[0]): special = True break if not special: return self._subgroup_by_splitter(splitter, eps=eps, mut_lat=mut_lat) else: #print("try to find the next subgroup") trail_struc = self._subgroup_by_splitter(splitter, eps=eps, mut_lat=mut_lat) multiple = sum(trail_struc.numIons)/sum(self.numIons) max_cell = max([1, max_cell/multiple]) ans = trail_struc.subgroup_once(eps, H, None, group_type, max_cell) if ans.group.number > 1: return ans count += 1 raise RuntimeError("Cannot find the splitter")
def subgroup(self, permutations=None, H=None, eps=0.05, idx=None, group_type='t', max_cell=4): """ generate a structure with lower symmetry Args: permutations: e.g., {"Si": "C"} H: space group number (int) eps: pertubation term (float) idx: list group_type: `t`, `k` or `t+k` max_cell: maximum cell reconstruction (float) Returns: a list of pyxtal structures with lower symmetries """ #randomly choose a subgroup from the available list idx, sites, t_types, k_types = self._get_subgroup_ids(H, group_type, idx, max_cell) valid_splitters = [] bad_splitters = [] for id in idx: gtype = (t_types+k_types)[id] if gtype == 'k': id -= len(t_types) splitter = wyckoff_split(G=self.group.number, wp1=sites, idx=id, group_type=gtype) if not splitter.error: if permutations is None: if splitter.valid_split: special = False if self.molecular: for i in range(len(self.mol_sites)): for ops in splitter.H_orbits[i]: if len(ops) < len(splitter.H[0]): special = True break if not special: valid_splitters.append(splitter) else: bad_splitters.append(splitter) else: bad_splitters.append(splitter) else: # apply permuation if len(splitter.H_orbits) == 1: if len(splitter.H_orbits[0]) > 1: valid_splitters.append(splitter) else: bad_splitters.append(splitter) else: valid_splitters.append(splitter) if len(valid_splitters) == 0: #print("try do one more step") new_strucs = [] for splitter in bad_splitters: trail_struc = self._subgroup_by_splitter(splitter, eps=eps) new_strucs.extend(trail_struc.subgroup(permutations, group_type=group_type)) return new_strucs else: #print(len(valid_splitters), "valid_splitters are present") new_strucs = [] for splitter in valid_splitters: if permutations is None: new_struc = self._subgroup_by_splitter(splitter, eps=eps) else: new_struc = self._apply_substitution(splitter, permutations) new_strucs.append(new_struc) return new_strucs