diff --git a/.ci_support/environment-old.yml b/.ci_support/environment-old.yml index f74a7d193..4510cbd1d 100644 --- a/.ci_support/environment-old.yml +++ b/.ci_support/environment-old.yml @@ -10,10 +10,9 @@ dependencies: - plotly =4.14.3 - pymatgen =2022.2.1 - pyscal3 =3.2.5 -- pyxtal =0.5.5 - scikit-learn =1.2.1 - scipy =1.9.3 - spglib =1.16.5 - sqsgenerator =0.2 - hatchling =1.27.0 -- hatch-vcs =0.4.0 \ No newline at end of file +- hatch-vcs =0.4.0 diff --git a/.ci_support/environment.yml b/.ci_support/environment.yml index 1d36f6610..e8a5c40dd 100644 --- a/.ci_support/environment.yml +++ b/.ci_support/environment.yml @@ -14,7 +14,6 @@ dependencies: - plotly =6.3.1 - pymatgen =2025.6.14 - pyscal3 =3.3.0 -- pyxtal =1.1.1 - scikit-learn =1.7.2 - scipy =1.16.2 - spglib =2.6.0 diff --git a/README.md b/README.md index 1a2bf951e..a1b0821ce 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ stk.plot3d(structure) * `stk.build.high_index_surface()` * `stk.build.get_high_index_surface_info()` * `stk.build.sqs_structures()` -* `stk.build.pyxtal()` * `stk.build.B2()` * `stk.build.C14()` * `stk.build.C15()` diff --git a/pyproject.toml b/pyproject.toml index 9626a6101..a9ea1b994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ phonopy = [ "phonopy==2.43.2", "spglib==2.6.0", ] -pyxtal = ["pyxtal==1.1.1"] [tool.ruff] exclude = [".ci_support", "tests", "setup.py", "_version.py"] diff --git a/structuretoolkit/build/__init__.py b/structuretoolkit/build/__init__.py index 3b506f304..ab11d72b8 100644 --- a/structuretoolkit/build/__init__.py +++ b/structuretoolkit/build/__init__.py @@ -1,7 +1,6 @@ from structuretoolkit.build.aimsgb import get_grainboundary_info, grainboundary from structuretoolkit.build.compound import B2, C14, C15, C36, D03 from structuretoolkit.build.mesh import create_mesh -from structuretoolkit.build.random import pyxtal from structuretoolkit.build.sqs import sqs_structures from structuretoolkit.build.surface import ( get_high_index_surface_info, @@ -17,7 +16,6 @@ "C36", "D03", "create_mesh", - "pyxtal", "sqs_structures", "get_high_index_surface_info", "high_index_surface", diff --git a/structuretoolkit/build/random.py b/structuretoolkit/build/random.py deleted file mode 100644 index 435d12023..000000000 --- a/structuretoolkit/build/random.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department -# Distributed under the terms of "New BSD License", see the LICENSE file. - -import warnings -from typing import Union - -try: - from tqdm.auto import tqdm -except ImportError: - - def tqdm(x): - return x - - -from ase import Atoms - -from structuretoolkit.common.helper import center_coordinates_in_unit_cell - - -def pyxtal( - group: Union[int, list[int]], - species: tuple[str], - num_ions: tuple[int], - dim=3, - repeat=1, - allow_exceptions=True, - **kwargs, -) -> Union[Atoms, list[dict]]: - """ - Generate random crystal structures with PyXtal. - - `group` must be between 1 and the largest possible value for the given dimensionality: - dim=3 => 1 - 230 (space groups) - dim=2 => 1 - 80 (layer groups) - dim=1 => 1 - 75 (rod groups) - dim=0 => 1 - 58 (point groups) - - When `group` is passed as a list of integers or `repeat>1`, generate multiple structures and return them in a list - of dicts containing the keys `atoms`, `symmetry` and `repeat` for the ASE structure, the symmetry group - number and which iteration it is, respectively. - - Args: - group (list of int, or int): the symmetry group to generate or a list of them - species (tuple of str): which species to include, defines the stoichiometry together with `num_ions` - num_ions (tuple of int): how many of each species to include, defines the stoichiometry together with `species` - dim (int): dimensionality of the symmetry group, 0 is point groups, 1 is rod groups, 2 is layer groups and 3 is space groups - repeat (int): how many random structures to generate - allow_exceptions (bool): when generating multiple structures, silence errors when the requested stoichiometry and symmetry group are incompatible - **kwargs: passed to `pyxtal.pyxtal` function verbatim - - Returns: - :class:`~.Atoms`: the generated structure, if repeat==1 and only one symmetry group is requested - list of dict of all generated structures, if repeat>1 or multiple symmetry groups are requested - - Raises: - ValueError: if `species` and `num_ions` are not of the same length - ValueError: if stoichiometry and symmetry group are incompatible and allow_exceptions==False or only one structure is requested - """ - from pyxtal import pyxtal as _pyxtal - from pyxtal.msg import Comp_CompatibilityError - - if len(species) != len(num_ions): - raise ValueError( - "species and num_ions must be of same length, " - f"not {species} and {num_ions}!" - ) - stoich = "".join(f"{s}{n}" for s, n in zip(species, num_ions)) - - def generate(group): - s = _pyxtal() - try: - s.from_random( - dim=dim, group=group, species=species, numIons=num_ions, **kwargs - ) - except Comp_CompatibilityError: - if not allow_exceptions: - raise ValueError( - f"Symmetry group {group} incompatible with stoichiometry {stoich}!" - ) from None - else: - return None - s = s.to_ase() - s = center_coordinates_in_unit_cell(structure=s) - return s - - # return a single structure - if repeat == 1 and isinstance(group, int): - allow_exceptions = False - return generate(group) - else: - structures = [] - if isinstance(group, int): - group = [group] - failed_groups = [] - for g in tqdm(group, desc="Spacegroups"): - for i in range(repeat): - s = generate(g) - if s is None: - failed_groups.append(g) - continue - structures.append({"atoms": s, "symmetry": g, "repeat": i}) - if len(failed_groups) > 0: - warnings.warn( - f"Groups [{', '.join(map(str, failed_groups))}] could not be generated with stoichiometry {stoich}!", - stacklevel=2, - ) - return structures diff --git a/tests/test_pyxtal.py b/tests/test_pyxtal.py deleted file mode 100644 index 1c066a9a2..000000000 --- a/tests/test_pyxtal.py +++ /dev/null @@ -1,77 +0,0 @@ -from unittest import TestCase, skipIf - -from ase import Atoms - -import structuretoolkit as stk - -try: - import pyxtal - - skip_pyxtal_test = False -except ImportError: - skip_pyxtal_test = True - - -@skipIf(skip_pyxtal_test, "pyxtal is not installed, so the pyxtal tests are skipped.") -class TestPyxtal(TestCase): - def test_args_raised(self): - """pyxtal should raise appropriate errors when called with wrong arguments""" - - with self.assertRaises( - ValueError, msg="No error raised when num_ions and species do not match!" - ): - stk.build.pyxtal(1, species=["Fe"], num_ions=[1, 2]) - - with self.assertRaises( - ValueError, msg="No error raised when num_ions and species do not match!" - ): - stk.build.pyxtal(1, species=["Fe", "Cr"], num_ions=[1]) - - try: - with self.assertWarnsRegex( - UserWarning, - "Groups \[193, 194\] could not be generated with stoichiometry Mg1!", - msg="No warning is raised even though allow_exceptions=True was passed!\n", - ): - stk.build.pyxtal( - [193, 194], ["Mg"], num_ions=[1], allow_exceptions=True - ) - except ValueError: - self.fail("Error raised even though allow_exceptions=True was passed!") - - with self.assertRaises( - ValueError, - msg="No error raised even though allow_exceptions=False was passed!", - ): - stk.build.pyxtal(194, ["Mg"], num_ions=[1], allow_exceptions=False) - - def test_return_value(self): - """pyxtal should either return Atoms or list of dict, depending on arguments""" - - self.assertIsInstance( - stk.build.pyxtal(1, species=["Fe"], num_ions=[1]), - Atoms, - "returned not an Atoms with scalar arguments", - ) - self.assertIsInstance( - stk.build.pyxtal([1, 2], species=["Fe"], num_ions=[1]), - list, - "returned not a StructureStorage with multiple groups", - ) - self.assertIsInstance( - stk.build.pyxtal(1, species=["Fe"], num_ions=[1], repeat=5), - list, - "returned not a StructureStorage with repeat given", - ) - self.assertEqual( - len(stk.build.pyxtal(1, species=["Fe"], num_ions=[1], repeat=5)), - 5, - "returned number of structures did not match given repeat", - ) - self.assertTrue( - all( - isinstance(d, dict) - for d in stk.build.pyxtal(1, species=["Fe"], num_ions=[1], repeat=5) - ), - "returned list should contain only dicts", - )