diff --git a/pyxtal/XRD.py b/pyxtal/XRD.py index 7b88306c..f69accbe 100644 --- a/pyxtal/XRD.py +++ b/pyxtal/XRD.py @@ -371,23 +371,11 @@ def get_unique_families(self, hkls, verbose=False): {hkl: multiplicity}: A dict with unique hkl and multiplicity. """ - # TODO: Definitely speed it up. - def is_perm(hkl1, hkl2): - h1 = np.abs(hkl1) - h2 = np.abs(hkl2) - return all(i == j for i, j in zip(sorted(h1), sorted(h2))) - unique = collections.defaultdict(list) - for hkl1 in hkls: - found = False - hkl1_tuple = tuple(hkl1) - for hkl2 in unique: - if is_perm(hkl1, hkl2): - found = True - unique[hkl2].append(hkl1_tuple) - break - if not found: - unique[hkl1_tuple].append(hkl1_tuple) + for hkl in hkls: + hkl_tuple = tuple(hkl) + key = tuple(sorted(abs(int(i)) for i in hkl_tuple)) + unique[key].append(hkl_tuple) pretty_unique = {} for v in unique.values(): diff --git a/pyxtal/io.py b/pyxtal/io.py index fe74ad84..49fe8978 100644 --- a/pyxtal/io.py +++ b/pyxtal/io.py @@ -153,13 +153,14 @@ def get_cif_str_for_pyxtal(struc, header: str = "", sym_num=None, style: str = " coords, species = coord0s, specie0s muls = [mul] * len(coords) else: - coords = None + coords_list = [] species = [] for id in range(sym_num): mol = site.get_mol_object(id) tmp = mol.cart_coords.dot(site.lattice.inv_matrix) - coords = tmp if coords is None else np.append(coords, tmp, axis=0) + coords_list.append(tmp) species.extend([s.value for s in mol.species]) + coords = np.concatenate(coords_list, axis=0) muls = [mul] * len(coords) # coords, species = site._get_coords_and_species(ids=sym_num) else: diff --git a/pyxtal/symmetry.py b/pyxtal/symmetry.py index 281586f1..d108cbec 100644 --- a/pyxtal/symmetry.py +++ b/pyxtal/symmetry.py @@ -4052,26 +4052,7 @@ def get_wyckoffs(num, organized=False, dim=3): Returns: a list of Wyckoff positions, each of which is a list of SymmOp's """ - if dim == 3: - df = SYMDATA.get_wyckoff_sg() - elif dim == 2: - df = SYMDATA.get_wyckoff_lg() - elif dim == 1: - df = SYMDATA.get_wyckoff_rg() - elif dim == 0: - df = SYMDATA.get_wyckoff_pg() - - # Convert the string from df into a list of wyckoff strings - wyckoff_strings = literal_eval(df["0"][num]) # Use literal_eval instead of eval - - wyckoffs = [] - for x in wyckoff_strings: - wyckoffs.append([]) - for y in x: - if dim == 0: - wyckoffs[-1].append(SymmOp(y)) - else: - wyckoffs[-1].append(SymmOp.from_xyz_str(y)) + wyckoffs = [list(wp) for wp in _get_wyckoffs_cached(num, dim)] if organized: wyckoffs_organized = [[]] # 2D Array of WP's organized by multiplicity old = len(wyckoffs[0]) @@ -4086,6 +4067,29 @@ def get_wyckoffs(num, organized=False, dim=3): # Return Wyckoff positions without organization return wyckoffs + +@functools.lru_cache(maxsize=None) +def _get_wyckoffs_cached(num, dim): + if dim == 3: + df = SYMDATA.get_wyckoff_sg() + elif dim == 2: + df = SYMDATA.get_wyckoff_lg() + elif dim == 1: + df = SYMDATA.get_wyckoff_rg() + elif dim == 0: + df = SYMDATA.get_wyckoff_pg() + else: + raise ValueError(f"Unsupported dimension: {dim}") + + wyckoff_strings = literal_eval(df["0"][num]) + wyckoffs = [] + for x in wyckoff_strings: + wyckoffs.append([]) + for y in x: + wyckoffs[-1].append(SymmOp(y) if dim == 0 else SymmOp.from_xyz_str(y)) + return tuple(tuple(wp) for wp in wyckoffs) + + def get_wyckoff_symmetry(num, dim=3): """ Returns a list of site symmetry for a given group. @@ -4101,6 +4105,11 @@ def get_wyckoff_symmetry(num, dim=3): a 3d list of SymmOp objects representing the site symmetry of each point in each Wyckoff position """ + return [[list(point) for point in wp] for wp in _get_wyckoff_symmetry_cached(num, dim)] + + +@functools.lru_cache(maxsize=None) +def _get_wyckoff_symmetry_cached(num, dim): if dim == 3: symmetry_df = SYMDATA.get_symmetry_sg() elif dim == 2: @@ -4109,21 +4118,18 @@ def get_wyckoff_symmetry(num, dim=3): symmetry_df = SYMDATA.get_symmetry_rg() elif dim == 0: symmetry_df = SYMDATA.get_symmetry_pg() + else: + raise ValueError(f"Unsupported dimension: {dim}") - symmetry_strings = eval(symmetry_df["0"][num]) - + symmetry_strings = literal_eval(symmetry_df["0"][num]) symmetry = [] - # Loop over Wyckoff positions for x in symmetry_strings: symmetry.append([]) - # Loop over points in WP for y in x: symmetry[-1].append([]) - # Loop over ops for z in y: - op = SymmOp(z) if dim == 0 else SymmOp.from_xyz_str(z) - symmetry[-1][-1].append(op) - return symmetry + symmetry[-1][-1].append(SymmOp(z) if dim == 0 else SymmOp.from_xyz_str(z)) + return tuple(tuple(tuple(point) for point in wp) for wp in symmetry) def get_generators(num, dim=3): @@ -4145,6 +4151,11 @@ def get_generators(num, dim=3): a 2d list of symmop objects [[wp0], [wp1], ... ] """ + return [list(wp) for wp in _get_generators_cached(num, dim)] + + +@functools.lru_cache(maxsize=None) +def _get_generators_cached(num, dim): if dim == 3: generators_df = SYMDATA.get_generator_sg() elif dim == 2: @@ -4153,18 +4164,16 @@ def get_generators(num, dim=3): generators_df = SYMDATA.get_generator_rg() elif dim == 0: generators_df = SYMDATA.get_generator_pg() + else: + raise ValueError(f"Unsupported dimension: {dim}") - generator_strings = eval(generators_df["0"][num]) - + generator_strings = literal_eval(generators_df["0"][num]) generators = [] - # Loop over Wyckoff positions for x in generator_strings: generators.append([]) - # Loop over ops for y in x: - op = SymmOp.from_xyz_str(y) if dim > 0 else SymmOp(y) - generators[-1].append(op) - return generators + generators[-1].append(SymmOp(y) if dim == 0 else SymmOp.from_xyz_str(y)) + return tuple(tuple(wp) for wp in generators) def site_symm(point, gen_pos, tol=1e-3, lattice=None, PBC=None):