structuretoolkit 0.0.35__tar.gz → 0.0.37__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/PKG-INFO +11 -14
  2. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/README.md +0 -1
  3. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/pyproject.toml +10 -11
  4. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/_version.py +2 -2
  5. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/distance.py +2 -4
  6. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/dscribe.py +7 -9
  7. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/neighbors.py +31 -32
  8. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/pyscal.py +11 -8
  9. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/snap.py +4 -5
  10. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/spatial.py +7 -7
  11. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/strain.py +4 -6
  12. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/symmetry.py +3 -4
  13. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/__init__.py +0 -2
  14. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/aimsgb.py +1 -2
  15. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/compound.py +5 -7
  16. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/sqs.py +17 -18
  17. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/surface.py +8 -10
  18. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/helper.py +2 -4
  19. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/phonopy.py +3 -3
  20. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/visualize.py +30 -30
  21. structuretoolkit-0.0.35/structuretoolkit/build/random.py +0 -107
  22. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/.gitignore +0 -0
  23. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/LICENSE +0 -0
  24. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/__init__.py +0 -0
  25. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/__init__.py +0 -0
  26. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/analyse/phonopy.py +0 -0
  27. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/build/mesh.py +0 -0
  28. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/__init__.py +0 -0
  29. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/error.py +0 -0
  30. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/pymatgen.py +0 -0
  31. {structuretoolkit-0.0.35 → structuretoolkit-0.0.37}/structuretoolkit/common/pyscal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structuretoolkit
3
- Version: 0.0.35
3
+ Version: 0.0.37
4
4
  Summary: build, analyse and visualise atomistic structures for materials science
5
5
  Project-URL: Homepage, https://github.com/pyiron/structuretoolkit
6
6
  Project-URL: Documentation, https://github.com/pyiron/structuretoolkit
@@ -41,37 +41,35 @@ Classifier: Development Status :: 5 - Production/Stable
41
41
  Classifier: Intended Audience :: Science/Research
42
42
  Classifier: License :: OSI Approved :: BSD License
43
43
  Classifier: Operating System :: OS Independent
44
- Classifier: Programming Language :: Python :: 3.9
45
44
  Classifier: Programming Language :: Python :: 3.10
46
45
  Classifier: Programming Language :: Python :: 3.11
47
46
  Classifier: Programming Language :: Python :: 3.12
47
+ Classifier: Programming Language :: Python :: 3.13
48
48
  Classifier: Topic :: Scientific/Engineering :: Physics
49
- Requires-Python: <3.13,>=3.9
49
+ Requires-Python: <3.14,>=3.10
50
50
  Requires-Dist: ase<=3.26.0,>=3.20.1
51
- Requires-Dist: numpy<=2.3.3,>=1.23.5
52
- Requires-Dist: scipy<=1.16.2,>=1.9.3
51
+ Requires-Dist: numpy<=2.3.4,>=1.23.5
52
+ Requires-Dist: scipy<=1.16.3,>=1.15.0
53
53
  Provides-Extra: clusters
54
- Requires-Dist: scikit-learn==1.6.1; extra == 'clusters'
54
+ Requires-Dist: scikit-learn==1.7.2; extra == 'clusters'
55
55
  Provides-Extra: dscribe
56
56
  Requires-Dist: dscribe==2.1.2; extra == 'dscribe'
57
57
  Provides-Extra: grainboundary
58
58
  Requires-Dist: aimsgb<=1.1.1,>=1.0.2; extra == 'grainboundary'
59
- Requires-Dist: pymatgen<=2025.3.10,>=2022.2.1; extra == 'grainboundary'
59
+ Requires-Dist: pymatgen<=2025.6.14,>=2022.2.1; extra == 'grainboundary'
60
60
  Provides-Extra: matplotlib
61
- Requires-Dist: matplotlib==3.10.3; extra == 'matplotlib'
61
+ Requires-Dist: matplotlib==3.10.7; extra == 'matplotlib'
62
62
  Provides-Extra: nglview
63
63
  Requires-Dist: nglview<=3.1.4,>=2.7.7; extra == 'nglview'
64
64
  Provides-Extra: phonopy
65
- Requires-Dist: phonopy<=2.43.2,>=2.16.2; extra == 'phonopy'
65
+ Requires-Dist: phonopy<=2.43.6,>=2.16.2; extra == 'phonopy'
66
66
  Requires-Dist: spglib<=2.6.0,>=1.16.5; extra == 'phonopy'
67
67
  Provides-Extra: plotly
68
- Requires-Dist: plotly<=6.3.0,>=4.14.3; extra == 'plotly'
68
+ Requires-Dist: plotly<=6.3.1,>=4.14.3; extra == 'plotly'
69
69
  Provides-Extra: pyscal
70
70
  Requires-Dist: pyscal3<=3.3.0,>=3.2.5; extra == 'pyscal'
71
- Provides-Extra: pyxtal
72
- Requires-Dist: pyxtal<=1.1.1,>=0.5.5; extra == 'pyxtal'
73
71
  Provides-Extra: surface
74
- Requires-Dist: pymatgen<=2025.3.10,>=2022.2.1; extra == 'surface'
72
+ Requires-Dist: pymatgen<=2025.6.14,>=2022.2.1; extra == 'surface'
75
73
  Requires-Dist: spglib<=2.6.0,>=1.16.5; extra == 'surface'
76
74
  Provides-Extra: symmetry
77
75
  Requires-Dist: spglib<=2.6.0,>=1.16.5; extra == 'symmetry'
@@ -127,7 +125,6 @@ stk.plot3d(structure)
127
125
  * `stk.build.high_index_surface()`
128
126
  * `stk.build.get_high_index_surface_info()`
129
127
  * `stk.build.sqs_structures()`
130
- * `stk.build.pyxtal()`
131
128
  * `stk.build.B2()`
132
129
  * `stk.build.C14()`
133
130
  * `stk.build.C15()`
@@ -48,7 +48,6 @@ stk.plot3d(structure)
48
48
  * `stk.build.high_index_surface()`
49
49
  * `stk.build.get_high_index_surface_info()`
50
50
  * `stk.build.sqs_structures()`
51
- * `stk.build.pyxtal()`
52
51
  * `stk.build.B2()`
53
52
  * `stk.build.C14()`
54
53
  * `stk.build.C15()`
@@ -11,22 +11,22 @@ authors = [
11
11
  readme = "README.md"
12
12
  license = { file = "LICENSE" }
13
13
  keywords = ["pyiron"]
14
- requires-python = ">=3.9, <3.13"
14
+ requires-python = ">=3.10, <3.14"
15
15
  classifiers = [
16
16
  "Development Status :: 5 - Production/Stable",
17
17
  "Topic :: Scientific/Engineering :: Physics",
18
18
  "License :: OSI Approved :: BSD License",
19
19
  "Intended Audience :: Science/Research",
20
20
  "Operating System :: OS Independent",
21
- "Programming Language :: Python :: 3.9",
22
21
  "Programming Language :: Python :: 3.10",
23
22
  "Programming Language :: Python :: 3.11",
24
23
  "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
25
  ]
26
26
  dependencies = [
27
27
  "ase>=3.20.1,<=3.26.0",
28
- "numpy>=1.23.5,<=2.3.3",
29
- "scipy>=1.9.3,<=1.16.2",
28
+ "numpy>=1.23.5,<=2.3.4",
29
+ "scipy>=1.15.0,<=1.16.3",
30
30
  ]
31
31
  dynamic = ["version"]
32
32
 
@@ -39,23 +39,22 @@ Repository = "https://github.com/pyiron/structuretoolkit"
39
39
  dscribe = ["dscribe==2.1.2"]
40
40
  grainboundary = [
41
41
  "aimsgb>=1.0.2,<=1.1.1",
42
- "pymatgen>=2022.2.1,<=2025.3.10",
42
+ "pymatgen>=2022.2.1,<=2025.6.14",
43
43
  ]
44
44
  pyscal = ["pyscal3>=3.2.5,<=3.3.0"]
45
45
  nglview = ["nglview>=2.7.7,<=3.1.4"]
46
- matplotlib = ["matplotlib==3.10.3"]
47
- plotly = ["plotly>=4.14.3,<=6.3.0"]
48
- clusters = ["scikit-learn==1.6.1"]
46
+ matplotlib = ["matplotlib==3.10.7"]
47
+ plotly = ["plotly>=4.14.3,<=6.3.1"]
48
+ clusters = ["scikit-learn==1.7.2"]
49
49
  symmetry = ["spglib>=1.16.5,<=2.6.0"]
50
50
  surface = [
51
51
  "spglib>=1.16.5,<=2.6.0",
52
- "pymatgen>=2022.2.1,<=2025.3.10",
52
+ "pymatgen>=2022.2.1,<=2025.6.14",
53
53
  ]
54
54
  phonopy = [
55
- "phonopy>=2.16.2,<=2.43.2",
55
+ "phonopy>=2.16.2,<=2.43.6",
56
56
  "spglib>=1.16.5,<=2.6.0",
57
57
  ]
58
- pyxtal = ["pyxtal>=0.5.5,<=1.1.1"]
59
58
 
60
59
  [tool.ruff]
61
60
  exclude = [".ci_support", "tests", "setup.py", "_version.py"]
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.35'
32
- __version_tuple__ = version_tuple = (0, 0, 35)
31
+ __version__ = version = '0.0.37'
32
+ __version_tuple__ = version_tuple = (0, 0, 37)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1,13 +1,11 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
 
6
4
 
7
5
  def get_distances_array(
8
6
  structure: Atoms,
9
- p1: Optional[np.ndarray] = None,
10
- p2: Optional[np.ndarray] = None,
7
+ p1: np.ndarray | None = None,
8
+ p2: np.ndarray | None = None,
11
9
  mic: bool = True,
12
10
  vectors: bool = False,
13
11
  ) -> np.ndarray:
@@ -1,24 +1,22 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
 
6
4
 
7
5
  def soap_descriptor_per_atom(
8
6
  structure: Atoms,
9
- r_cut: Optional[float] = None,
10
- n_max: Optional[int] = None,
11
- l_max: Optional[int] = None,
12
- sigma: Optional[float] = 1.0,
7
+ r_cut: float | None = None,
8
+ n_max: int | None = None,
9
+ l_max: int | None = None,
10
+ sigma: float | None = 1.0,
13
11
  rbf: str = "gto",
14
- weighting: Optional[np.ndarray] = None,
12
+ weighting: np.ndarray | None = None,
15
13
  average: str = "off",
16
14
  compression: dict = None,
17
- species: Optional[list] = None,
15
+ species: list | None = None,
18
16
  periodic: bool = True,
19
17
  sparse: bool = False,
20
18
  dtype: str = "float64",
21
- centers: Optional[np.ndarray] = None,
19
+ centers: np.ndarray | None = None,
22
20
  n_jobs: int = 1,
23
21
  only_physical_cores: bool = False,
24
22
  verbose: bool = False,
@@ -3,14 +3,13 @@
3
3
 
4
4
  import itertools
5
5
  import warnings
6
- from typing import Optional, Union
7
6
 
8
7
  import numpy as np
9
8
  from ase.atoms import Atoms
10
9
  from scipy.sparse import coo_matrix
11
10
  from scipy.spatial import cKDTree
12
11
  from scipy.spatial.transform import Rotation
13
- from scipy.special import gamma, sph_harm
12
+ from scipy.special import gamma, sph_harm_y
14
13
 
15
14
  from structuretoolkit.common.helper import (
16
15
  get_average_of_unique_labels,
@@ -149,8 +148,8 @@ class Tree:
149
148
  def _reshape(
150
149
  self,
151
150
  value: np.ndarray,
152
- key: Optional[str] = None,
153
- ref_vector: Optional[np.ndarray] = None,
151
+ key: str | None = None,
152
+ ref_vector: np.ndarray | None = None,
154
153
  ) -> np.ndarray:
155
154
  """
156
155
  Reshape the given value based on the specified key and reference vector.
@@ -258,7 +257,7 @@ class Tree:
258
257
  + " with the correct norm_order value"
259
258
  )
260
259
 
261
- def _get_max_length(self, ref_vector: Optional[np.ndarray] = None) -> int:
260
+ def _get_max_length(self, ref_vector: np.ndarray | None = None) -> int:
262
261
  """
263
262
  Get the maximum length of the reference vector.
264
263
 
@@ -279,7 +278,7 @@ class Tree:
279
278
  return max(len(dd[dd < np.inf]) for dd in ref_vector)
280
279
 
281
280
  def _contract(
282
- self, value: np.ndarray, ref_vector: Optional[np.ndarray] = None
281
+ self, value: np.ndarray, ref_vector: np.ndarray | None = None
283
282
  ) -> np.ndarray:
284
283
  """
285
284
  Contract the given value based on the specified reference vector.
@@ -295,7 +294,7 @@ class Tree:
295
294
  return value
296
295
  return [
297
296
  vv[: np.sum(dist < np.inf)]
298
- for vv, dist in zip(value, self.filled.distances)
297
+ for vv, dist in zip(value, self.filled.distances, strict=True)
299
298
  ]
300
299
 
301
300
  def _allow_ragged_to_mode(self, new_bool: bool) -> str:
@@ -362,7 +361,7 @@ class Tree:
362
361
  def _get_distances_and_indices(
363
362
  self,
364
363
  positions: np.ndarray,
365
- num_neighbors: Optional[int] = None,
364
+ num_neighbors: int | None = None,
366
365
  cutoff_radius: float = np.inf,
367
366
  width_buffer: float = 1.2,
368
367
  ) -> tuple[np.ndarray, np.ndarray]:
@@ -431,10 +430,10 @@ class Tree:
431
430
  def _get_vectors(
432
431
  self,
433
432
  positions: np.ndarray,
434
- num_neighbors: Optional[int] = None,
433
+ num_neighbors: int | None = None,
435
434
  cutoff_radius: float = np.inf,
436
- distances: Optional[np.ndarray] = None,
437
- indices: Optional[np.ndarray] = None,
435
+ distances: np.ndarray | None = None,
436
+ indices: np.ndarray | None = None,
438
437
  width_buffer: float = 1.2,
439
438
  ) -> np.ndarray:
440
439
  """
@@ -470,7 +469,7 @@ class Tree:
470
469
 
471
470
  def _estimate_num_neighbors(
472
471
  self,
473
- num_neighbors: Optional[int] = None,
472
+ num_neighbors: int | None = None,
474
473
  cutoff_radius: float = np.inf,
475
474
  width_buffer: float = 1.2,
476
475
  ) -> int:
@@ -515,7 +514,7 @@ class Tree:
515
514
 
516
515
  def _estimate_width(
517
516
  self,
518
- num_neighbors: Optional[int] = None,
517
+ num_neighbors: int | None = None,
519
518
  cutoff_radius: float = np.inf,
520
519
  width_buffer: float = 1.2,
521
520
  ) -> float:
@@ -551,7 +550,7 @@ class Tree:
551
550
  def get_neighborhood(
552
551
  self,
553
552
  positions: np.ndarray,
554
- num_neighbors: Optional[int] = None,
553
+ num_neighbors: int | None = None,
555
554
  cutoff_radius: float = np.inf,
556
555
  width_buffer: float = 1.2,
557
556
  ) -> "Tree":
@@ -647,7 +646,7 @@ class Tree:
647
646
  l: np.ndarray,
648
647
  m: np.ndarray,
649
648
  cutoff_radius: float = np.inf,
650
- rotation: Optional[np.ndarray] = None,
649
+ rotation: np.ndarray | None = None,
651
650
  ) -> np.ndarray:
652
651
  """
653
652
  Args:
@@ -668,7 +667,7 @@ class Tree:
668
667
  The angles are calculated based on `self.vecs`, where the azimuthal angle is defined on the
669
668
  xy-plane and the polar angle is along the z-axis.
670
669
 
671
- See more on: scipy.special.sph_harm
670
+ See more on: scipy.special.sph_harm_y
672
671
 
673
672
  """
674
673
  vecs = self.filled.vecs
@@ -685,7 +684,7 @@ class Tree:
685
684
  phi[within_cutoff] = np.arctan2(
686
685
  np.linalg.norm(vecs[within_cutoff, :2], axis=-1), vecs[within_cutoff, 2]
687
686
  )
688
- return np.sum(sph_harm(m, l, theta, phi) * within_cutoff, axis=-1) / np.sum(
687
+ return np.sum(sph_harm_y(l, m, phi, theta) * within_cutoff, axis=-1) / np.sum(
689
688
  within_cutoff, axis=-1
690
689
  )
691
690
 
@@ -868,8 +867,8 @@ class Neighbors(Tree):
868
867
 
869
868
  def get_local_shells(
870
869
  self,
871
- mode: Optional[str] = None,
872
- tolerance: Optional[int] = None,
870
+ mode: str | None = None,
871
+ tolerance: int | None = None,
873
872
  cluster_by_distances: bool = False,
874
873
  cluster_by_vecs: bool = False,
875
874
  ) -> np.ndarray:
@@ -947,8 +946,8 @@ class Neighbors(Tree):
947
946
 
948
947
  def get_global_shells(
949
948
  self,
950
- mode: Optional[str] = None,
951
- tolerance: Optional[int] = None,
949
+ mode: str | None = None,
950
+ tolerance: int | None = None,
952
951
  cluster_by_distances: bool = False,
953
952
  cluster_by_vecs: bool = False,
954
953
  ) -> np.ndarray:
@@ -1008,7 +1007,7 @@ class Neighbors(Tree):
1008
1007
 
1009
1008
  def get_shell_matrix(
1010
1009
  self,
1011
- chemical_pair: Optional[list[str]] = None,
1010
+ chemical_pair: list[str] | None = None,
1012
1011
  cluster_by_distances: bool = False,
1013
1012
  cluster_by_vecs: bool = False,
1014
1013
  ):
@@ -1107,8 +1106,8 @@ class Neighbors(Tree):
1107
1106
 
1108
1107
  def cluster_by_vecs(
1109
1108
  self,
1110
- distance_threshold: Optional[float] = None,
1111
- n_clusters: Optional[int] = None,
1109
+ distance_threshold: float | None = None,
1110
+ n_clusters: int | None = None,
1112
1111
  linkage: str = "complete",
1113
1112
  metric: str = "euclidean",
1114
1113
  ):
@@ -1152,8 +1151,8 @@ class Neighbors(Tree):
1152
1151
 
1153
1152
  def cluster_by_distances(
1154
1153
  self,
1155
- distance_threshold: Optional[float] = None,
1156
- n_clusters: Optional[int] = None,
1154
+ distance_threshold: float | None = None,
1155
+ n_clusters: int | None = None,
1157
1156
  linkage: str = "complete",
1158
1157
  metric: str = "euclidean",
1159
1158
  use_vecs: bool = False,
@@ -1225,7 +1224,7 @@ class Neighbors(Tree):
1225
1224
 
1226
1225
  def cluster_analysis(
1227
1226
  self, id_list: list, return_cluster_sizes: bool = False
1228
- ) -> Union[dict[int, list[int]], tuple[dict[int, list[int]], list[int]]]:
1227
+ ) -> dict[int, list[int]] | tuple[dict[int, list[int]], list[int]]:
1229
1228
  """
1230
1229
  Perform cluster analysis on a list of atom IDs.
1231
1230
 
@@ -1283,7 +1282,7 @@ class Neighbors(Tree):
1283
1282
  def get_bonds(
1284
1283
  self,
1285
1284
  radius: float = np.inf,
1286
- max_shells: Optional[int] = None,
1285
+ max_shells: int | None = None,
1287
1286
  prec: float = 0.1,
1288
1287
  ) -> list[dict[str, list[list[int]]]]:
1289
1288
  """
@@ -1322,7 +1321,7 @@ class Neighbors(Tree):
1322
1321
  el_list = self._ref_structure.get_chemical_symbols()
1323
1322
 
1324
1323
  ind_shell = []
1325
- for d, i in zip(dist, ind):
1324
+ for d, i in zip(dist, ind, strict=True):
1326
1325
  id_list = get_cluster(d[d < radius], i[d < radius])
1327
1326
  ia_shells_dict = {}
1328
1327
  for i_shell_list in id_list:
@@ -1361,7 +1360,7 @@ def get_neighbors(
1361
1360
  structure: Atoms,
1362
1361
  num_neighbors: int = 12,
1363
1362
  tolerance: int = 2,
1364
- id_list: Optional[list] = None,
1363
+ id_list: list | None = None,
1365
1364
  cutoff_radius: float = np.inf,
1366
1365
  width_buffer: float = 1.2,
1367
1366
  mode: str = "filled",
@@ -1400,12 +1399,12 @@ def _get_neighbors(
1400
1399
  structure: Atoms,
1401
1400
  num_neighbors: int = 12,
1402
1401
  tolerance: int = 2,
1403
- id_list: Optional[list] = None,
1402
+ id_list: list | None = None,
1404
1403
  cutoff_radius: float = np.inf,
1405
1404
  width_buffer: float = 1.2,
1406
1405
  get_tree: bool = False,
1407
1406
  norm_order: int = 2,
1408
- ) -> Union[Neighbors, Tree]:
1407
+ ) -> Neighbors | Tree:
1409
1408
  """
1410
1409
  Get the neighbors of atoms in a structure.
1411
1410
 
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
2
2
  # Distributed under the terms of "New BSD License", see the LICENSE file.
3
3
 
4
- from typing import Any, Optional, Union
4
+ from typing import Any
5
5
 
6
6
  import numpy as np
7
7
  from ase.atoms import Atoms
@@ -24,10 +24,10 @@ def get_steinhardt_parameters(
24
24
  structure: Atoms,
25
25
  neighbor_method: str = "cutoff",
26
26
  cutoff: float = 0.0,
27
- n_clusters: Optional[int] = 2,
28
- q: Optional[tuple] = None,
27
+ n_clusters: int | None = 2,
28
+ q: tuple | None = None,
29
29
  averaged: bool = False,
30
- ) -> Union[tuple[np.ndarray], tuple[np.ndarray, np.ndarray]]:
30
+ ) -> tuple[np.ndarray] | tuple[np.ndarray, np.ndarray]:
31
31
  """
32
32
  Calculate Steinhardts parameters
33
33
 
@@ -55,7 +55,7 @@ def get_steinhardt_parameters(
55
55
 
56
56
  cl = cluster.KMeans(n_clusters=n_clusters)
57
57
 
58
- ind = cl.fit(list(zip(*sysq))).labels_
58
+ ind = cl.fit(list(zip(*sysq, strict=True))).labels_
59
59
  return sysq, ind
60
60
  else:
61
61
  return sysq
@@ -80,7 +80,7 @@ def get_centro_symmetry_descriptors(
80
80
 
81
81
  def get_diamond_structure_descriptors(
82
82
  structure: Atoms, mode: str = "total", ovito_compatibility: bool = False
83
- ) -> Union[dict[str, int], np.ndarray]:
83
+ ) -> dict[str, int] | np.ndarray:
84
84
  """
85
85
  Analyse diamond structure
86
86
 
@@ -197,7 +197,10 @@ def get_adaptive_cna_descriptors(
197
197
  if not ovito_compatibility:
198
198
  return cna
199
199
  else:
200
- return {o: cna[p] for o, p in zip(ovito_parameter, pyscal_parameter)}
200
+ return {
201
+ o: cna[p]
202
+ for o, p in zip(ovito_parameter, pyscal_parameter, strict=True)
203
+ }
201
204
  else:
202
205
  cnalist = np.array(sys.atoms.structure)
203
206
  if mode == "numeric":
@@ -241,7 +244,7 @@ def find_solids(
241
244
  q: int = 6,
242
245
  right: bool = True,
243
246
  return_sys: bool = False,
244
- ) -> Union[int, Any]:
247
+ ) -> int | Any:
245
248
  """
246
249
  Get the number of solids or the corresponding pyscal system.
247
250
  Calls necessary pyscal methods as described in https://pyscal.org/en/latest/methods/03_solidliquid.html.
@@ -1,5 +1,4 @@
1
1
  from ctypes import POINTER, c_double, c_int, cast
2
- from typing import Optional, Union
3
2
 
4
3
  import numpy as np
5
4
  from ase.atoms import Atoms
@@ -70,7 +69,7 @@ def get_snap_descriptors_per_atom(
70
69
  rmin0: float = 0.0,
71
70
  bzeroflag: bool = False,
72
71
  quadraticflag: bool = False,
73
- weights: Optional[Union[list, np.ndarray]] = None,
72
+ weights: list | np.ndarray | None = None,
74
73
  cutoff: float = 10.0,
75
74
  ) -> np.ndarray:
76
75
  """
@@ -121,7 +120,7 @@ def get_snap_descriptor_derivatives(
121
120
  rmin0: float = 0.0,
122
121
  bzeroflag: bool = False,
123
122
  quadraticflag: bool = False,
124
- weights: Optional[Union[list, np.ndarray]] = None,
123
+ weights: list | np.ndarray | None = None,
125
124
  cutoff: float = 10.0,
126
125
  ):
127
126
  """
@@ -525,7 +524,7 @@ def _set_computes_snap(lmp, bispec_options: dict):
525
524
  kw_substrings = [f"{k} {v}" for k, v in kw_options.items()]
526
525
  kwargs = " ".join(kw_substrings)
527
526
 
528
- for _op, base in zip(("b", "db", "vb"), (base_b, base_db, base_vb)):
527
+ for _op, base in zip(("b", "db", "vb"), (base_b, base_db, base_vb), strict=True):
529
528
  command = f"{base} {radelem} {wj} {kwargs}"
530
529
  lmp.command(command)
531
530
 
@@ -659,7 +658,7 @@ def _get_default_parameters(
659
658
  rmin0: float = 0.0,
660
659
  bzeroflag: bool = False,
661
660
  quadraticflag: bool = False,
662
- weights: Optional[Union[list, np.ndarray]] = None,
661
+ weights: list | np.ndarray | None = None,
663
662
  cutoff: float = 10.0,
664
663
  ):
665
664
  """
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
2
2
  # Distributed under the terms of "New BSD License", see the LICENSE file.
3
3
 
4
- from typing import Callable, Optional
4
+ from collections.abc import Callable
5
5
 
6
6
  import numpy as np
7
7
  from ase.atoms import Atoms
@@ -225,7 +225,7 @@ class Interstitials:
225
225
  l_values: np.ndarray = np.arange(2, 13),
226
226
  q_eps: float = 0.3,
227
227
  var_ratio: float = 5.0,
228
- min_samples: Optional[int] = None,
228
+ min_samples: int | None = None,
229
229
  neigh_args: dict = None,
230
230
  **kwargs,
231
231
  ):
@@ -294,7 +294,7 @@ class Interstitials:
294
294
  self.structure = structure
295
295
 
296
296
  def run_workflow(
297
- self, positions: Optional[np.ndarray] = None, steps: int = -1
297
+ self, positions: np.ndarray | None = None, steps: int = -1
298
298
  ) -> np.ndarray:
299
299
  """
300
300
  Run the workflow to obtain the interstitial positions.
@@ -407,7 +407,7 @@ def get_interstitials(
407
407
  l_values: np.ndarray = np.arange(2, 13),
408
408
  q_eps: float = 0.3,
409
409
  var_ratio: float = 5.0,
410
- min_samples: Optional[int] = None,
410
+ min_samples: int | None = None,
411
411
  neigh_args: dict = None,
412
412
  **kwargs,
413
413
  ) -> Interstitials:
@@ -457,7 +457,7 @@ get_interstitials.__doc__ = (
457
457
  def get_layers(
458
458
  structure: Atoms,
459
459
  distance_threshold: float = 0.01,
460
- id_list: Optional[list[int]] = None,
460
+ id_list: list[int] | None = None,
461
461
  wrap_atoms: bool = True,
462
462
  planes: np.ndarray = None,
463
463
  cluster_method: str = None,
@@ -690,9 +690,9 @@ def get_delaunay_neighbors(structure: Atoms, width_buffer: float = 10.0) -> np.n
690
690
 
691
691
  def get_cluster_positions(
692
692
  structure: Atoms,
693
- positions: Optional[np.ndarray] = None,
693
+ positions: np.ndarray | None = None,
694
694
  eps: float = 1.0,
695
- buffer_width: Optional[float] = None,
695
+ buffer_width: float | None = None,
696
696
  return_labels: bool = False,
697
697
  ) -> np.ndarray:
698
698
  """
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
  from scipy.spatial.transform import Rotation
@@ -30,7 +28,7 @@ class Strain:
30
28
  self,
31
29
  structure: Atoms,
32
30
  ref_structure: Atoms,
33
- num_neighbors: Optional[int] = None,
31
+ num_neighbors: int | None = None,
34
32
  only_bulk_type: bool = False,
35
33
  ):
36
34
  """
@@ -85,7 +83,7 @@ class Strain:
85
83
  )
86
84
 
87
85
  def _get_perpendicular_unit_vectors(
88
- self, vec: np.ndarray, vec_axis: Optional[np.ndarray] = None
86
+ self, vec: np.ndarray, vec_axis: np.ndarray | None = None
89
87
  ) -> np.ndarray:
90
88
  """
91
89
  Get the perpendicular unit vectors of the given vectors.
@@ -146,7 +144,7 @@ class Strain:
146
144
  self,
147
145
  vec_before: np.ndarray,
148
146
  vec_after: np.ndarray,
149
- vec_axis: Optional[np.ndarray] = None,
147
+ vec_axis: np.ndarray | None = None,
150
148
  ) -> np.ndarray:
151
149
  """
152
150
  Calculate the rotation matrix that transforms the `vec_before` vectors to the `vec_after` vectors.
@@ -319,7 +317,7 @@ class Strain:
319
317
  def get_strain(
320
318
  structure: Atoms,
321
319
  ref_structure: Atoms,
322
- num_neighbors: Optional[int] = None,
320
+ num_neighbors: int | None = None,
323
321
  only_bulk_type: bool = False,
324
322
  return_object: bool = False,
325
323
  ):
@@ -4,7 +4,6 @@
4
4
  import ast
5
5
  import dataclasses
6
6
  import string
7
- from typing import Optional
8
7
 
9
8
  import numpy as np
10
9
  import spglib
@@ -263,7 +262,7 @@ class Symmetry(dict):
263
262
  ) / len(self.rotations)
264
263
 
265
264
  def _get_spglib_cell(
266
- self, use_elements: Optional[bool] = None, use_magmoms: Optional[bool] = None
265
+ self, use_elements: bool | None = None, use_magmoms: bool | None = None
267
266
  ) -> tuple:
268
267
  """
269
268
  Get the cell information in the format required by spglib.
@@ -380,8 +379,8 @@ class Symmetry(dict):
380
379
  def get_primitive_cell(
381
380
  self,
382
381
  standardize: bool = False,
383
- use_elements: Optional[bool] = None,
384
- use_magmoms: Optional[bool] = None,
382
+ use_elements: bool | None = None,
383
+ use_magmoms: bool | None = None,
385
384
  ) -> Atoms:
386
385
  """
387
386
  Get primitive cell of a given structure.
@@ -1,7 +1,6 @@
1
1
  from structuretoolkit.build.aimsgb import get_grainboundary_info, grainboundary
2
2
  from structuretoolkit.build.compound import B2, C14, C15, C36, D03
3
3
  from structuretoolkit.build.mesh import create_mesh
4
- from structuretoolkit.build.random import pyxtal
5
4
  from structuretoolkit.build.sqs import sqs_structures
6
5
  from structuretoolkit.build.surface import (
7
6
  get_high_index_surface_info,
@@ -17,7 +16,6 @@ __all__ = [
17
16
  "C36",
18
17
  "D03",
19
18
  "create_mesh",
20
- "pyxtal",
21
19
  "sqs_structures",
22
20
  "get_high_index_surface_info",
23
21
  "high_index_surface",
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
2
2
  # Distributed under the terms of "New BSD License", see the LICENSE file.
3
3
 
4
- from typing import Optional
5
4
  from warnings import warn
6
5
 
7
6
  import numpy as np
@@ -59,7 +58,7 @@ def grainboundary(
59
58
  delete_layer: str = "0b0t0b0t",
60
59
  tol: float = 0.25,
61
60
  to_primitive: bool = False,
62
- add_if_dist: Optional[float] = None,
61
+ add_if_dist: float | None = None,
63
62
  ):
64
63
  """
65
64
  Generate a grain boundary structure based on aimsgb.
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
  from ase.build import bulk
@@ -8,7 +6,7 @@ from ase.spacegroup import crystal
8
6
  from structuretoolkit.analyse.neighbors import get_neighbors
9
7
 
10
8
 
11
- def B2(element_a: str, element_b: str, a: Optional[float] = None):
9
+ def B2(element_a: str, element_b: str, a: float | None = None):
12
10
  """
13
11
  Builds a cubic $AB$ B2 structure of interpenetrating simple cubic lattices.
14
12
 
@@ -33,7 +31,7 @@ def B2(element_a: str, element_b: str, a: Optional[float] = None):
33
31
  def C14(
34
32
  element_a: str,
35
33
  element_b: str,
36
- a: Optional[float] = None,
34
+ a: float | None = None,
37
35
  c_over_a: float = 1.626,
38
36
  x1: float = 0.1697,
39
37
  z1: float = 0.5629,
@@ -83,7 +81,7 @@ def C14(
83
81
  return s
84
82
 
85
83
 
86
- def C15(element_a: str, element_b: str, a: Optional[float] = None) -> Atoms:
84
+ def C15(element_a: str, element_b: str, a: float | None = None) -> Atoms:
87
85
  """
88
86
  Builds a cubic $A B_2$ C15 Laves phase cell.
89
87
 
@@ -125,7 +123,7 @@ def C15(element_a: str, element_b: str, a: Optional[float] = None) -> Atoms:
125
123
  def C36(
126
124
  element_a: str,
127
125
  element_b: str,
128
- a: Optional[float] = None,
126
+ a: float | None = None,
129
127
  c_over_a: float = 3.252,
130
128
  x1: float = 0.16429,
131
129
  z1: float = 0.09400,
@@ -186,7 +184,7 @@ def C36(
186
184
  return s
187
185
 
188
186
 
189
- def D03(element_a: str, element_b: str, a: Optional[float] = None) -> Atoms:
187
+ def D03(element_a: str, element_b: str, a: float | None = None) -> Atoms:
190
188
  """
191
189
  Builds a cubic $A B_3$ D03 cubic cell.
192
190
 
@@ -3,7 +3,6 @@ import random
3
3
  import warnings
4
4
  from collections.abc import Iterable
5
5
  from multiprocessing import cpu_count
6
- from typing import Optional, Union
7
6
 
8
7
  import numpy as np
9
8
  from ase.atoms import Atoms
@@ -127,7 +126,7 @@ def remap_sro(species: Iterable[str], array: np.ndarray) -> dict[str, list]:
127
126
 
128
127
 
129
128
  def remap_sqs_results(
130
- result: dict[str, Union[Atoms, np.ndarray]],
129
+ result: dict[str, Atoms | np.ndarray],
131
130
  ) -> tuple[Atoms, dict[str, list]]:
132
131
  """
133
132
  Remap the results of SQS optimization.
@@ -155,28 +154,28 @@ def transpose(it: Iterable[Iterable]) -> Iterable[tuple]:
155
154
  Iterable[tuple]: The transposed iterable.
156
155
 
157
156
  """
158
- return zip(*it)
157
+ return zip(*it, strict=True)
159
158
 
160
159
 
161
160
  def sqs_structures(
162
161
  structure: Atoms,
163
- mole_fractions: dict[str, Union[float, int]],
164
- weights: Optional[dict[int, float]] = None,
165
- objective: Union[float, np.ndarray] = 0.0,
166
- iterations: Union[float, int] = 1e6,
162
+ mole_fractions: dict[str, float | int],
163
+ weights: dict[int, float] | None = None,
164
+ objective: float | np.ndarray = 0.0,
165
+ iterations: float | int = 1e6,
167
166
  output_structures: int = 10,
168
167
  mode: str = "random",
169
- num_threads: Optional[int] = None,
170
- prefactors: Optional[Union[float, np.ndarray]] = None,
171
- pair_weights: Optional[np.ndarray] = None,
172
- rtol: Optional[float] = None,
173
- atol: Optional[float] = None,
174
- which: Optional[Iterable[int]] = None,
175
- shell_distances: Optional[Iterable[int]] = None,
176
- minimal: Optional[bool] = True,
177
- similar: Optional[bool] = True,
178
- return_statistics: Optional[bool] = False,
179
- ) -> Union[Atoms, tuple[Atoms, dict[str, list], int, float]]:
168
+ num_threads: int | None = None,
169
+ prefactors: float | np.ndarray | None = None,
170
+ pair_weights: np.ndarray | None = None,
171
+ rtol: float | None = None,
172
+ atol: float | None = None,
173
+ which: Iterable[int] | None = None,
174
+ shell_distances: Iterable[int] | None = None,
175
+ minimal: bool | None = True,
176
+ similar: bool | None = True,
177
+ return_statistics: bool | None = False,
178
+ ) -> Atoms | tuple[Atoms, dict[str, list], int, float]:
180
179
  """
181
180
  Generate SQS structures.
182
181
 
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
  from ase.build import bulk, surface
@@ -12,10 +10,10 @@ def get_high_index_surface_info(
12
10
  element: str,
13
11
  crystal_structure: str,
14
12
  lattice_constant: float,
15
- terrace_orientation: Optional[list] = None,
16
- step_orientation: Optional[list] = None,
17
- kink_orientation: Optional[list] = None,
18
- step_down_vector: Optional[list] = None,
13
+ terrace_orientation: list | None = None,
14
+ step_orientation: list | None = None,
15
+ kink_orientation: list | None = None,
16
+ step_down_vector: list | None = None,
19
17
  length_step: int = 3,
20
18
  length_terrace: int = 3,
21
19
  length_kink: int = 1,
@@ -99,10 +97,10 @@ def high_index_surface(
99
97
  element: str,
100
98
  crystal_structure: str,
101
99
  lattice_constant: float,
102
- terrace_orientation: Optional[list] = None,
103
- step_orientation: Optional[list] = None,
104
- kink_orientation: Optional[list] = None,
105
- step_down_vector: Optional[list] = None,
100
+ terrace_orientation: list | None = None,
101
+ step_orientation: list | None = None,
102
+ kink_orientation: list | None = None,
103
+ step_down_vector: list | None = None,
106
104
  length_step: int = 3,
107
105
  length_terrace: int = 3,
108
106
  length_kink: int = 1,
@@ -1,5 +1,3 @@
1
- from typing import Optional, Union
2
-
3
1
  import numpy as np
4
2
  from ase.atoms import Atoms
5
3
  from scipy.sparse import coo_matrix
@@ -24,7 +22,7 @@ def get_extended_positions(
24
22
  width: float,
25
23
  return_indices: bool = False,
26
24
  norm_order: int = 2,
27
- positions: Optional[np.ndarray] = None,
25
+ positions: np.ndarray | None = None,
28
26
  ):
29
27
  """
30
28
  Get all atoms in the boundary around the supercell which have a distance
@@ -276,7 +274,7 @@ def apply_strain(
276
274
  return structure_copy
277
275
 
278
276
 
279
- def get_cell(cell: Union[Atoms, list, tuple, np.ndarray, float]):
277
+ def get_cell(cell: Atoms | list | tuple | np.ndarray | float):
280
278
  """
281
279
  Get cell of an ase structure, or convert a float or a (3,)-array into a
282
280
  orthogonal cell.
@@ -11,9 +11,9 @@ def phonopy_to_atoms(ph_atoms) -> Atoms:
11
11
 
12
12
  """
13
13
  return Atoms(
14
- symbols=list(ph_atoms.get_chemical_symbols()),
15
- positions=list(ph_atoms.get_positions()),
16
- cell=list(ph_atoms.get_cell()),
14
+ symbols=list(ph_atoms.symbols),
15
+ positions=list(ph_atoms.positions),
16
+ cell=list(ph_atoms.cell),
17
17
  pbc=True,
18
18
  )
19
19
 
@@ -2,7 +2,7 @@
2
2
  # Distributed under the terms of "New BSD License", see the LICENSE file.
3
3
 
4
4
  import warnings
5
- from typing import Any, Optional
5
+ from typing import Any
6
6
 
7
7
  import numpy as np
8
8
  from ase.atoms import Atoms
@@ -30,21 +30,21 @@ def plot3d(
30
30
  camera: str = "orthographic",
31
31
  spacefill: bool = True,
32
32
  particle_size: float = 1.0,
33
- select_atoms: Optional[np.ndarray] = None,
33
+ select_atoms: np.ndarray | None = None,
34
34
  background: str = "white",
35
- color_scheme: Optional[str] = None,
36
- colors: Optional[np.ndarray] = None,
37
- scalar_field: Optional[np.ndarray] = None,
38
- scalar_start: Optional[float] = None,
39
- scalar_end: Optional[float] = None,
40
- scalar_cmap: Optional[Any] = None,
41
- vector_field: Optional[np.ndarray] = None,
42
- vector_color: Optional[np.ndarray] = None,
35
+ color_scheme: str | None = None,
36
+ colors: np.ndarray | None = None,
37
+ scalar_field: np.ndarray | None = None,
38
+ scalar_start: float | None = None,
39
+ scalar_end: float | None = None,
40
+ scalar_cmap: Any | None = None,
41
+ vector_field: np.ndarray | None = None,
42
+ vector_color: np.ndarray | None = None,
43
43
  magnetic_moments: bool = False,
44
44
  view_plane: np.ndarray = np.array([0, 0, 1]),
45
45
  distance_from_camera: float = 1.0,
46
46
  opacity: float = 1.0,
47
- height: Optional[float] = None,
47
+ height: float | None = None,
48
48
  ):
49
49
  """
50
50
  Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database"
@@ -195,7 +195,7 @@ def _draw_box_plotly(fig: Any, structure: Atoms, px: Any, go: Any) -> Any:
195
195
  cell = get_cell(structure)
196
196
  data = fig.data
197
197
  for lines in _get_box_skeleton(cell):
198
- fig = px.line_3d(**dict(zip(["x", "y", "z"], lines.T)))
198
+ fig = px.line_3d(**dict(zip(["x", "y", "z"], lines.T, strict=True)))
199
199
  fig.update_traces(line_color="#000000")
200
200
  data = fig.data + data
201
201
  return go.Figure(data=data)
@@ -204,14 +204,14 @@ def _draw_box_plotly(fig: Any, structure: Atoms, px: Any, go: Any) -> Any:
204
204
  def _plot3d_plotly(
205
205
  structure: Atoms,
206
206
  show_cell: bool = True,
207
- scalar_field: Optional[np.ndarray] = None,
208
- select_atoms: Optional[np.ndarray] = None,
207
+ scalar_field: np.ndarray | None = None,
208
+ select_atoms: np.ndarray | None = None,
209
209
  particle_size: float = 1.0,
210
210
  camera: str = "orthographic",
211
211
  view_plane: np.ndarray = np.array([1, 1, 1]),
212
212
  distance_from_camera: float = 1.0,
213
213
  opacity: float = 1.0,
214
- height: Optional[float] = None,
214
+ height: float | None = None,
215
215
  ):
216
216
  """
217
217
  Make a 3D plot of the atomic structure.
@@ -284,16 +284,16 @@ def _plot3d(
284
284
  camera: str = "orthographic",
285
285
  spacefill: bool = True,
286
286
  particle_size: float = 1.0,
287
- select_atoms: Optional[np.ndarray] = None,
287
+ select_atoms: np.ndarray | None = None,
288
288
  background: str = "white",
289
- color_scheme: Optional[str] = None,
290
- colors: Optional[np.ndarray] = None,
291
- scalar_field: Optional[np.ndarray] = None,
292
- scalar_start: Optional[float] = None,
293
- scalar_end: Optional[float] = None,
294
- scalar_cmap: Optional[Any] = None,
295
- vector_field: Optional[np.ndarray] = None,
296
- vector_color: Optional[np.ndarray] = None,
289
+ color_scheme: str | None = None,
290
+ colors: np.ndarray | None = None,
291
+ scalar_field: np.ndarray | None = None,
292
+ scalar_start: float | None = None,
293
+ scalar_end: float | None = None,
294
+ scalar_cmap: Any | None = None,
295
+ vector_field: np.ndarray | None = None,
296
+ vector_color: np.ndarray | None = None,
297
297
  magnetic_moments: bool = False,
298
298
  view_plane: np.ndarray = np.array([0, 0, 1]),
299
299
  distance_from_camera: float = 1.0,
@@ -462,7 +462,7 @@ def _plot3d(
462
462
  vector_color = np.ones((len(structure), 3)) * vector_color
463
463
 
464
464
  if vector_field is not None:
465
- for arr, pos, col in zip(vector_field, positions, vector_color):
465
+ for arr, pos, col in zip(vector_field, positions, vector_color, strict=True):
466
466
  view.shape.add_arrow(list(pos), list(pos + arr), list(col), 0.2)
467
467
 
468
468
  if show_axes: # Add axes
@@ -576,8 +576,8 @@ def _ngl_write_atom(
576
576
  x: float,
577
577
  y: float,
578
578
  z: float,
579
- group: Optional[str] = None,
580
- num2: Optional[int] = None,
579
+ group: str | None = None,
580
+ num2: int | None = None,
581
581
  occupancy: float = 1.0,
582
582
  temperature_factor: float = 0.0,
583
583
  ) -> str:
@@ -689,7 +689,7 @@ def _add_colorscheme_spacefill(
689
689
  Returns:
690
690
  (nglview.NGLWidget): The modified widget.
691
691
  """
692
- for elem, num in set(zip(elements, atomic_numbers)):
692
+ for elem, num in set(zip(elements, atomic_numbers, strict=True)):
693
693
  view.add_spacefill(
694
694
  selection="#" + elem,
695
695
  radius_type="vdw",
@@ -726,8 +726,8 @@ def _add_custom_color_spacefill(
726
726
 
727
727
  def _scalars_to_hex_colors(
728
728
  scalar_field: np.ndarray,
729
- start: Optional[float] = None,
730
- end: Optional[float] = None,
729
+ start: float | None = None,
730
+ end: float | None = None,
731
731
  cmap=None,
732
732
  ):
733
733
  """
@@ -1,107 +0,0 @@
1
- # Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
2
- # Distributed under the terms of "New BSD License", see the LICENSE file.
3
-
4
- import warnings
5
- from typing import Union
6
-
7
- try:
8
- from tqdm.auto import tqdm
9
- except ImportError:
10
-
11
- def tqdm(x):
12
- return x
13
-
14
-
15
- from ase import Atoms
16
-
17
- from structuretoolkit.common.helper import center_coordinates_in_unit_cell
18
-
19
-
20
- def pyxtal(
21
- group: Union[int, list[int]],
22
- species: tuple[str],
23
- num_ions: tuple[int],
24
- dim=3,
25
- repeat=1,
26
- allow_exceptions=True,
27
- **kwargs,
28
- ) -> Union[Atoms, list[dict]]:
29
- """
30
- Generate random crystal structures with PyXtal.
31
-
32
- `group` must be between 1 and the largest possible value for the given dimensionality:
33
- dim=3 => 1 - 230 (space groups)
34
- dim=2 => 1 - 80 (layer groups)
35
- dim=1 => 1 - 75 (rod groups)
36
- dim=0 => 1 - 58 (point groups)
37
-
38
- When `group` is passed as a list of integers or `repeat>1`, generate multiple structures and return them in a list
39
- of dicts containing the keys `atoms`, `symmetry` and `repeat` for the ASE structure, the symmetry group
40
- number and which iteration it is, respectively.
41
-
42
- Args:
43
- group (list of int, or int): the symmetry group to generate or a list of them
44
- species (tuple of str): which species to include, defines the stoichiometry together with `num_ions`
45
- num_ions (tuple of int): how many of each species to include, defines the stoichiometry together with `species`
46
- dim (int): dimensionality of the symmetry group, 0 is point groups, 1 is rod groups, 2 is layer groups and 3 is space groups
47
- repeat (int): how many random structures to generate
48
- allow_exceptions (bool): when generating multiple structures, silence errors when the requested stoichiometry and symmetry group are incompatible
49
- **kwargs: passed to `pyxtal.pyxtal` function verbatim
50
-
51
- Returns:
52
- :class:`~.Atoms`: the generated structure, if repeat==1 and only one symmetry group is requested
53
- list of dict of all generated structures, if repeat>1 or multiple symmetry groups are requested
54
-
55
- Raises:
56
- ValueError: if `species` and `num_ions` are not of the same length
57
- ValueError: if stoichiometry and symmetry group are incompatible and allow_exceptions==False or only one structure is requested
58
- """
59
- from pyxtal import pyxtal as _pyxtal
60
- from pyxtal.msg import Comp_CompatibilityError
61
-
62
- if len(species) != len(num_ions):
63
- raise ValueError(
64
- "species and num_ions must be of same length, "
65
- f"not {species} and {num_ions}!"
66
- )
67
- stoich = "".join(f"{s}{n}" for s, n in zip(species, num_ions))
68
-
69
- def generate(group):
70
- s = _pyxtal()
71
- try:
72
- s.from_random(
73
- dim=dim, group=group, species=species, numIons=num_ions, **kwargs
74
- )
75
- except Comp_CompatibilityError:
76
- if not allow_exceptions:
77
- raise ValueError(
78
- f"Symmetry group {group} incompatible with stoichiometry {stoich}!"
79
- ) from None
80
- else:
81
- return None
82
- s = s.to_ase()
83
- s = center_coordinates_in_unit_cell(structure=s)
84
- return s
85
-
86
- # return a single structure
87
- if repeat == 1 and isinstance(group, int):
88
- allow_exceptions = False
89
- return generate(group)
90
- else:
91
- structures = []
92
- if isinstance(group, int):
93
- group = [group]
94
- failed_groups = []
95
- for g in tqdm(group, desc="Spacegroups"):
96
- for i in range(repeat):
97
- s = generate(g)
98
- if s is None:
99
- failed_groups.append(g)
100
- continue
101
- structures.append({"atoms": s, "symmetry": g, "repeat": i})
102
- if len(failed_groups) > 0:
103
- warnings.warn(
104
- f"Groups [{', '.join(map(str, failed_groups))}] could not be generated with stoichiometry {stoich}!",
105
- stacklevel=2,
106
- )
107
- return structures