def symm_check(self, ucell, wulff_vertices): """ # Checks if the point group of the Wulff shape matches # the point group of its conventional unit cell Args: ucell (string): Unit cell that the Wulff shape is based on. wulff_vertices (list): List of all vertices on the Wulff shape. Use wulff.wulff_pt_list to obtain the list (see wulff_generator.py). return (bool) """ space_group_analyzer = SpacegroupAnalyzer(ucell) symm_ops = space_group_analyzer.get_point_group_operations( cartesian=True) for point in wulff_vertices: for op in symm_ops: symm_point = op.operate(point) if in_coord_list(wulff_vertices, symm_point): continue else: return False return True
def symmetrize(self, structure): tensor = self._reduced_tensor if self._is_real_space: real_lattice = self._lattice else: real_lattice = self._lattice.reciprocal_lattice # I guess this is the reason why tensor.symmetrize (omega) is so slow! from pymatgen.symmetry.analyzer import SpacegroupAnalyzer real_finder = SpacegroupAnalyzer(structure) real_symmops = real_finder.get_point_group_operations(cartesian=True) cartesian_tensor = self.cartesian_tensor sym_tensor = np.zeros((3, 3)) my_tensor = cartesian_tensor for real_sym in real_symmops: mat = real_sym.rotation_matrix prod_sym = np.dot(np.transpose(mat), np.dot(cartesian_tensor, mat)) sym_tensor = sym_tensor + prod_sym sym_tensor = sym_tensor / len(real_symmops) self._reduced_tensor = _from_cart_to_red(sym_tensor, self._lattice)
def get_sym_eq_kpoints(self, kpoint, cartesian=False, tol=1e-2): """ Returns a list of unique symmetrically equivalent k-points. Args: kpoint (1x3 array): coordinate of the k-point cartesian (bool): kpoint is in cartesian or fractional coordinates tol (float): tolerance below which coordinates are considered equal Returns: ([1x3 array] or None): if structure is not available returns None """ if not self.structure: return None sg = SpacegroupAnalyzer(self.structure) symmops = sg.get_point_group_operations(cartesian=cartesian) points = np.dot(kpoint, [m.rotation_matrix for m in symmops]) rm_list = [] # identify and remove duplicates from the list of equivalent k-points: for i in range(len(points) - 1): for j in range(i + 1, len(points)): if np.allclose(pbc_diff(points[i], points[j]), [0, 0, 0], tol): rm_list.append(i) break return np.delete(points, rm_list, axis=0)
def symm_check(self, ucell, wulff_vertices): """ # Checks if the point group of the Wulff shape matches # the point group of its conventional unit cell Args: ucell (string): Unit cell that the Wulff shape is based on. wulff_vertices (list): List of all vertices on the Wulff shape. Use wulff.wulff_pt_list to obtain the list (see wulff_generator.py). return (bool) """ space_group_analyzer = SpacegroupAnalyzer(ucell) symm_ops = space_group_analyzer.get_point_group_operations( cartesian=True) for point in wulff_vertices: for op in symm_ops: symm_point = op.operate(point) if in_coord_list(wulff_vertices, symm_point): continue else: return False return True
def symmetrize(self, structure): tensor = self._reduced_tensor if self._is_real_space: real_lattice = self._lattice else: real_lattice = self._lattice.reciprocal_lattice # I guess this is the reason why tensor.symmetrize (omega) is so slow! real_finder = SpacegroupAnalyzer(structure) real_symmops = real_finder.get_point_group_operations(cartesian=True) cartesian_tensor = self.cartesian_tensor sym_tensor = np.zeros((3,3)) my_tensor = cartesian_tensor for real_sym in real_symmops: mat = real_sym.rotation_matrix prod_sym = np.dot(np.transpose(mat),np.dot(cartesian_tensor,mat)) sym_tensor = sym_tensor + prod_sym sym_tensor = sym_tensor/len(real_symmops) self._reduced_tensor = from_cart_to_red(sym_tensor,self._lattice)
def get_sym_eq_kpoints(self, kpoint, cartesian=False, tol=1e-2): """ Returns a list of unique symmetrically equivalent k-points. Args: kpoint (1x3 array): coordinate of the k-point cartesian (bool): kpoint is in cartesian or fractional coordinates tol (float): tolerance below which coordinates are considered equal Returns: ([1x3 array] or None): if structure is not available returns None """ if not self.structure: return None sg = SpacegroupAnalyzer(self.structure) symmops = sg.get_point_group_operations(cartesian=cartesian) points = np.dot(kpoint, [m.rotation_matrix for m in symmops]) rm_list = [] # identify and remove duplicates from the list of equivalent k-points: for i in range(len(points) - 1): for j in range(i + 1, len(points)): if np.allclose(pbc_diff(points[i], points[j]), [0, 0, 0], tol): rm_list.append(i) break return np.delete(points, rm_list, axis=0)
def reconstruct_bz_kpoints(self): """ Reconstruct all the kpoints in the 1st Brillouin zone from the irreducible points chosen by VASP. :return: """ neighbors = set_up_neighbors(self._out.lattice) kpts_bz = [] for k in self.kpoints: kpts_bz.append(return_to_brillouin(k, self._out.lattice, neighbors=neighbors, cartesian=False)) all_kpts = np.array(kpts_bz[0]) all_lu_energies = np.array([self.lu_energies[0], ]) all_ho_energies = np.array([self.ho_energies[0], ]) sg = SpacegroupAnalyzer(self._out.structures[-1]) symmops = sg.get_point_group_operations(cartesian=False) # Reconstruct all the kpoints with the corresponding energies for k, lu_energy, ho_energy in zip(kpts_bz[1:], self.lu_energies[1:], self.ho_energies[1:]): # Get all symmetry equivalent kpoints of the kpoint in the IBZ sym_kpoints = np.dot(k, [m.rotation_matrix for m in symmops]) add_list = [sym_kpoints[0], ] for point in list(sym_kpoints[1:]): if not any(np.linalg.norm(x - point) < 1e-6 for x in add_list): add_list.append(point) # Add the list of kpoints to the total array all_kpts = np.vstack([all_kpts, add_list]) # Also add the energy value of the corresponding kpoint #sympoints # times all_lu_energies = np.vstack( [all_lu_energies, np.ones([len(add_list), 1]) * lu_energy] ) all_ho_energies = np.vstack( [all_ho_energies, np.ones([len(add_list), 1]) * ho_energy] ) self._all_kpoints = all_kpts self._all_lu_energies = all_lu_energies self._all_ho_energies = all_ho_energies
def get_sym_eq_kpoints(struct, kpoint, cartesian=False, tol=1e-2): if not struct: return None sg = SpacegroupAnalyzer(struct) symmops = sg.get_point_group_operations(cartesian=cartesian) points = np.dot(kpoint, [m.rotation_matrix for m in symmops]) rm_list = [] # identify and remove duplicates from the list of equivalent k-points: for i in range(len(points) - 1): for j in range(i + 1, len(points)): if np.allclose(pbc_diff(points[i], points[j]), [0, 0, 0], tol): rm_list.append(i) break return np.delete(points, rm_list, axis=0)
def enumerate_ads_config(self, adsorbate, loading, site='all', symmetry_tol=0.01): vor, mesh = self.Voronoi_tessalate() vcoords = vor.vertices base_coords = [a.position for a in self.bulk_ase] repeat_unit_cell = self.bulk_ase.get_cell().T inv_ruc = np.linalg.inv(repeat_unit_cell) corrected_vcoords = [] used = [] for coord in vcoords: if np.linalg.norm(coord) < 1e3: fcoord = np.dot(inv_ruc, coord) zero_threshold_indices = abs(fcoord) < 1e-6 fcoord[zero_threshold_indices] = 0.0 one_threshold_indices = abs(fcoord - 1.0) < 1e-6 fcoord[one_threshold_indices] = 0.0 advance = True for u in used: if np.allclose(fcoord, u): advance = False break if advance: corrected_vcoords.append(np.dot(repeat_unit_cell, fcoord)) used.append(fcoord) vtypes = [] for vert in corrected_vcoords: used = [] spectrum = [] for atom_coord in base_coords: fvert = np.dot(inv_ruc, vert) fatom_coord = np.dot(inv_ruc, atom_coord) fdist_vec, sym = PBC3DF_sym(fvert, fatom_coord) dist = np.linalg.norm(np.dot(repeat_unit_cell, fdist_vec)) sym_atom_coord = np.dot(repeat_unit_cell, fatom_coord - sym) spectrum.append((dist,sym_atom_coord)) spectrum.sort(key = lambda x: x[0]) min_dist = spectrum[0][0] spectrum = [s[1] for s in spectrum if s[0] - min_dist < symmetry_tol] + [vert] spectrum_atoms = Atoms() for s in spectrum: spectrum_atoms.append(Atom('H', s)) spectrum_atoms.set_cell(repeat_unit_cell.T) spectrum_struct = AseAtomsAdaptor.get_structure(spectrum_atoms) sga = SpacegroupAnalyzer(spectrum_struct, symprec=symmetry_tol) pgs = sga.get_point_group_symbol() symmetry_order = len(sga.get_point_group_operations()) try: symmetry_type = symmetry_order_dict[symmetry_order] except KeyError: symmetry_type = 'unknown' + str(symmetry_order) vtypes.append((symmetry_type, vert)) if site in ('all', 'single_site'): max_loading = float(len(vtypes)) else: max_loading = float(len([ty for ty in vtypes if ty[0] == site])) all_combinations = [s for s in all_subsets(vtypes) if len(s) > 0] if isfloat(loading): fractional_loadings = [abs(len(s)/max_loading - loading) for s in all_combinations] closest = min(set(fractional_loadings)) all_combinations = [all_combinations[i] for i in range(len(all_combinations)) if fractional_loadings[i] == closest] if site == 'single_site': site_dict = dict((k,[]) for k in set(ty[0] for ty in vtypes)) for entry in vtypes: ty, coord = entry site_dict[ty].append(coord) elif site == 'all': site_dict = {'all':[]} for entry in vtypes: site_dict['all'].append(entry) else: site_dict = {site:[]} for entry in vtypes: ty, coord = entry if ty == site: site_dict[ty].append(coord) ads_dict = {} atype_counter = 0 fingerprints = dict((len(s), dict((k,[]) for k in range(1,231))) for s in all_combinations) sg_counts = dict((k,0) for k in range(1,231)) for subset in all_combinations: types = [s[0] for s in subset] type_string = '' for ty in set(types): type_string += ty + '_' if len(set(types)) > 1 and site == 'single_site': continue elif site not in ('all', 'single_site'): if len(set(types)) > 1: continue else: if list(set(types))[0] != site: continue coords = [s[1] for s in subset] ld = len(subset) fp_dict = fingerprints[ld] fractional_loading = ld/max_loading adsorbate_combinations = itertools.product(adsorbate, repeat=ld) for ads_comb in adsorbate_combinations: adsonly_positions = [] bulk_coords = [(sl.symbol, sl.position) for sl in self.bulk_ase] for s,a in zip(subset, ads_comb): ads, shift_ind = a ty, site_coord = s elems = [] positions = [] for atom in ads: elems.append(atom.symbol) positions.append(atom.position) elems = np.asarray(elems) positions = np.asarray(positions) positions -= positions[shift_ind] positions += site_coord for e,c in zip(elems, positions): bulk_coords.append((e, c)) adsonly_positions.append(c) advance, index, sgs, sgn, dists, atoms, formula = redundancy_check(bulk_coords, adsonly_positions, fp_dict, repeat_unit_cell, symmetry_tol) if advance: sg_counts[sgn] += 1 fp_dict[sgn].append(dists) ads_dict[formula + '_' + str(int(fractional_loading * 1000)) + '_' + type_string + str(sgn) + '_' + index] = atoms self.adsorbate_configuration_dict = ads_dict
def __init__( self, uuid, structure, # pristine unit cell sc_size="1 1 1", signicant_figures=4, muon_threshold=1e-2, if_pristine=False, if_with_distortions=True # reduced_sites = False, # reduced_distance = 10.0 ): """ Parameters ----------- uuid : int The PK for relax structure structure : Structure object Pymatgen Structure Object sc_size : str Supercell size in a, b and c direction eg "3 3 3" signicant_figures : int Round positios to significant figures. Default=4 muon_threshold : float Absolute threshold for checking distance. if_pristine : bool If True, to generate symmetry pristine structure for muon equivalent sites. Default=False if_with_distortions : bool If True, to generate structure with distortions for each muon equivalent sites. Default=True Returns ------- """ # """ # reduced_sites: Bool # If True, (planning for something). # Default=False # reduced_distance : float # planning for something """ # self.relax_uuid = uuid self.structure = structure self.sc_size = sc_size self.signicant_figures = signicant_figures self.muon_threshold = muon_threshold self.if_pristine = if_pristine self.if_with_distortions = if_with_distortions # This parameters for future # self.reduced_sites = reduced_sites # self.reduced_distance = reduced_distance if sc_size is None or sc_size == "": self.sc_size = "1 1 1" else: self.sc_size = sc_size self.pristine_structure_supercell = self.structure.copy() self.pristine_structure_supercell.make_supercell( [int(x) for x in self.sc_size.split()]) # Check if pritine structure is compatible to supercell size if len(self.pristine_structure_supercell) != self.n_atoms - 1: raise SiteDistortions('Invalid inputs Structure or supercell size') SA = SpacegroupAnalyzer(self.pristine_structure_supercell, symprec=self.muon_threshold) self._SA = SA self._SG = SA.get_space_group_operations() self._PG = SA.get_point_group_operations() self._SGO = SpacegroupOperations( SA.get_space_group_number(), SA.get_space_group_symbol(), SA.get_symmetry_operations(cartesian=False))
def __init__(self, lattice, miller_list, e_surf_list, color_set='PuBu', grid_off=True, axis_off=True, show_area=False, alpha=1, off_color='red', symprec=0.00001): """ Args: lattice: Lattice object of the conventional unit cell miller_list ([(hkl), ...]: list of hkl or hkil for hcp e_surf_list ([float]): list of corresponding surface energies color_set: default is 'PuBu' grid_off (bool): default is True axis_off (bool): default is Ture show_area (bool): default is False alpha (float): chosen from 0 to 1 (float), default is 1 off_color: color_legend for off_wulff planes on show_area legend symprec (float): for recp_operation, default is 0.01 """ latt = lattice.scale(1) structure = Structure(latt, ["H"], [[0, 0, 0]]) # 1. store input args: # store plot settings: self.alpha = alpha self.color_set = color_set self.color_ind = list(range(len(miller_list))) self.grid_off = grid_off self.axis_off = axis_off self.show_area = show_area self.off_color = off_color self.input_miller_fig = [hkl_tuple_to_str(x) for x in miller_list] # store input data self.structure = structure self.input_miller = [list(x) for x in miller_list] self.input_hkl = [[x[0], x[1], x[-1]] for x in miller_list] self.input_e_surf = list(e_surf_list) self.latt = latt self.recp = structure.lattice.reciprocal_lattice_crystallographic self.recp_symmops = get_recp_symmetry_operation(structure, symprec) sga = SpacegroupAnalyzer(structure, symprec) self.cart_symmops = sga.get_point_group_operations(cartesian=True) # 2. get all the data for wulff construction # get all the surface normal from get_all_miller_e() normal_e_m = self.get_all_miller_e() # [normal, e_surf, normal_pt, dual_pt, color_plane, m_ind_orig, miller] logger.debug(len(normal_e_m)) self.normal_e_m = normal_e_m # 3. consider the dual condition dual_pts = [x[3] for x in normal_e_m] dual_convex = ConvexHull(dual_pts) dual_cv_simp = dual_convex.simplices # simplices (ndarray of ints, shape (nfacet, ndim)) # list of [i, j, k] , ndim = 3 # i, j, k: ind for normal_e_m # recalculate the dual of dual, get the wulff shape. # conner <-> surface # get cross point from the simplices of the dual convex hull wulff_pt_list = [self.get_cross_pt_dual_simp(dual_simp) for dual_simp in dual_cv_simp] wulff_convex = ConvexHull(wulff_pt_list) wulff_cv_simp = wulff_convex.simplices logger.debug(", ".join([str(len(x)) for x in wulff_cv_simp])) # store simplices and convex self.dual_cv_simp = dual_cv_simp self.wulff_pt_list = wulff_pt_list self.wulff_cv_simp = wulff_cv_simp self.wulff_convex = wulff_convex # 4. get wulff info # return (simpx_info, plane_wulff_info, on_wulff, surface_area) wulff_info = self.get_simpx_plane() self.simpx_info = wulff_info[0] # need to update the color # plan_wulff_info: [normal, e_surf, pts, simpx, # color_plane, m_ind_orig, miller] self.plane_wulff_info = wulff_info[1] self.on_wulff = wulff_info[2] self.color_area = wulff_info[3] # 5. assign color for on_wulff plane # return (color_list, color_proxy, color_proxy_on_wulff, # miller_on_wulff, e_surf_on_wulff_list) color_info = self.get_colors() self.color_list = color_info[0] self.color_proxy = color_info[1] self.color_proxy_on_wulff = color_info[2] self.miller_on_wulff = color_info[3] self.e_surf_on_wulff = color_info[4] miller_area = [] for m, in_mill_fig in enumerate(self.input_miller_fig): miller_area.append( in_mill_fig + ' : ' + str(round(self.color_area[m], 4))) self.miller_area = miller_area