@mat3ra/made 2026.6.15-0 → 2026.6.29-0

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 (57) hide show
  1. package/.pre-commit-config.yaml +1 -1
  2. package/.prettierignore +1 -0
  3. package/AGENTS.md +133 -0
  4. package/MANIFEST.in +1 -0
  5. package/data/constants.json +13 -0
  6. package/data/reciprocal_paths.json +111 -0
  7. package/dist/js/data/reciprocal_paths.json +111 -0
  8. package/dist/js/lattice/reciprocal/paths.js +8 -113
  9. package/dist/js/made.d.ts +1 -1
  10. package/dist/js/made.js +1 -1
  11. package/package.json +1 -1
  12. package/pyproject.toml +1 -1
  13. package/src/js/constants/molecule.ts +1 -1
  14. package/src/js/lattice/reciprocal/paths.ts +10 -118
  15. package/src/js/made.ts +1 -1
  16. package/src/py/mat3ra/made/data_helper.py +5 -0
  17. package/src/py/mat3ra/made/lattice.py +34 -0
  18. package/src/py/mat3ra/made/material.py +1 -2
  19. package/src/py/mat3ra/made/reciprocal/__init__.py +5 -0
  20. package/src/py/mat3ra/made/reciprocal/lattice_reciprocal.py +147 -0
  21. package/src/py/mat3ra/made/reciprocal/paths.py +30 -0
  22. package/src/py/mat3ra/made/reciprocal/symmetry_points.py +385 -0
  23. package/src/py/mat3ra/made/tools/analyze/fingerprint/__init__.py +1 -1
  24. package/src/py/mat3ra/made/tools/analyze/fingerprint/layers/layered_fingerprint_along_axis.py +2 -3
  25. package/src/py/mat3ra/made/tools/analyze/interface/__init__.py +1 -1
  26. package/src/py/mat3ra/made/tools/analyze/lattice/analyzer.py +7 -5
  27. package/src/py/mat3ra/made/tools/analyze/lattice_planes.py +10 -9
  28. package/src/py/mat3ra/made/tools/analyze/lattice_swap_analyzer.py +3 -3
  29. package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/helpers.py +0 -1
  30. package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/utils.py +10 -11
  31. package/src/py/mat3ra/made/tools/build_components/operations/core/combinations/stack/builder.py +26 -13
  32. package/src/py/mat3ra/made/tools/build_components/operations/core/modifications/perturb/helpers.py +2 -2
  33. package/src/py/mat3ra/made/tools/build_components/operations/core/modifications/strain/builder.py +1 -1
  34. package/src/py/mat3ra/made/tools/convert/__init__.py +8 -11
  35. package/src/py/mat3ra/made/tools/convert/utils.py +9 -5
  36. package/src/py/mat3ra/made/tools/operations/reusable/unary.py +2 -2
  37. package/tests/js/parsers/poscar.ts +9 -9
  38. package/tests/py/unit/fixtures/thrid_party/ase_atoms.py +0 -1
  39. package/tests/py/unit/test_data_files.py +68 -0
  40. package/tests/py/unit/test_lattice.py +3 -4
  41. package/tests/py/unit/test_lattice_type_extended.py +53 -0
  42. package/tests/py/unit/test_reciprocal_lattice.py +86 -0
  43. package/tests/py/unit/test_reciprocal_paths.py +64 -0
  44. package/tests/py/unit/test_symmetry_points.py +151 -0
  45. package/tests/py/unit/test_tools_analyze.py +7 -1
  46. package/tests/py/unit/test_tools_analyze_basis.py +1 -1
  47. package/tests/py/unit/test_tools_analyze_lattice.py +9 -4
  48. package/tests/py/unit/test_tools_analyze_swap.py +15 -9
  49. package/tests/py/unit/test_tools_build_defect/test_point_defect.py +24 -3
  50. package/tests/py/unit/test_tools_build_interface.py +1 -2
  51. package/tests/py/unit/test_tools_build_interface_zsl.py +72 -4
  52. package/tests/py/unit/test_tools_build_metadata.py +0 -1
  53. package/tests/py/unit/test_tools_calculate.py +1 -0
  54. package/tests/py/unit/test_tools_convert.py +1 -4
  55. package/tests/py/unit/utils.py +29 -0
  56. package/constants.json +0 -13
  57. package/src/py/mat3ra/made/constants.py +0 -11
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Default k-point paths according to:
3
+ * [AFLOW](https://arxiv.org/abs/1004.2974) methodology.
4
+ * Paths are split in parts for clarity.
5
+ *
6
+ * Raw segment data is stored in data/reciprocal_paths.json (shared with Python).
7
+ */
8
+ import points from "../../data/reciprocal_paths.json";
9
+
1
10
  interface KPointStep {
2
11
  point: string;
3
12
  steps: number;
@@ -7,123 +16,6 @@ interface PathsType {
7
16
  [key: string]: KPointStep[];
8
17
  }
9
18
 
10
- /**
11
- * Default kpoing paths according to:
12
- * [AFLOW](https://arxiv.org/abs/1004.2974) methodology.
13
- * Paths are split in parts for clarity.
14
- */
15
- const points: Record<string, string[][]> = {
16
- CUB: [
17
- ["Г", "X", "M", "Г", "R", "X"],
18
- ["M", "R"],
19
- ],
20
- BCC: [
21
- ["Г", "H", "N", "Г", "P", "H"],
22
- ["P", "N"],
23
- ],
24
- FCC: [
25
- ["Г", "X", "W", "K", "Г", "L", "U", "W", "L"],
26
- ["U", "X"],
27
- ],
28
- TET: [
29
- ["Г", "X", "M", "Г", "Z", "R", "A", "Z"],
30
- ["X", "R"],
31
- ["M", "A"],
32
- ],
33
- "BCT-1": [
34
- ["Г", "X", "M", "Г", "Z", "P", "N", "Z1", "M"],
35
- ["X", "P"],
36
- ],
37
- "BCT-2": [
38
- ["Г", "X", "Y", "∑", "Г", "Z", "∑1", "N", "P", "Y1", "Z"],
39
- ["X", "P"],
40
- ],
41
- ORC: [
42
- ["Г", "X", "S", "Y", "Г", "Z", "U", "R", "T", "Z"],
43
- ["Y", "T"],
44
- ["U", "X"],
45
- ["S", "R"],
46
- ],
47
- "ORCF-1": [
48
- ["Г", "Y", "T", "Z", "Г", "X", "A1", "Y"],
49
- ["T", "X1"],
50
- ["X", "A", "Z"],
51
- ["L", "Г"],
52
- ],
53
- "ORCF-2": [
54
- ["Г", "Y", "C", "D", "X", "Г", "Z", "D1", "H", "C"],
55
- ["C1", "Z"],
56
- ["X", "H1"],
57
- ["H", "Y"],
58
- ["L", "Г"],
59
- ],
60
- "ORCF-3": [
61
- ["Г", "Y", "T", "Z", "Г", "X", "A1", "Y"],
62
- ["X", "A", "Z"],
63
- ["L", "Г"],
64
- ],
65
- ORCI: [
66
- ["Г", "X", "L", "T", "W", "R", "X1", "Z", "Г", "Y", "S", "W"],
67
- ["L1", "Y"],
68
- ["Y1", "Z"],
69
- ],
70
- ORCC: [
71
- ["Г", "X", "S", "R", "A", "Z", "Г", "Y", "X1", "A1", "T", "Y"],
72
- ["Z", "T"],
73
- ],
74
- HEX: [
75
- ["Г", "M", "K", "Г", "A", "L", "H", "A"],
76
- ["L", "M"],
77
- ["K", "H"],
78
- ],
79
- "RHL-1": [
80
- ["Г", "L", "B1"],
81
- ["B", "Z", "Г", "X"],
82
- ["Q", "F", "P1", "Z"],
83
- ["L", "P"],
84
- ],
85
- "RHL-2": [["Г", "P", "Z", "Q", "Г", "F", "P1", "Q1", "L", "Z"]],
86
- MCL: [
87
- ["Г", "Y", "H", "C", "E", "M1", "A", "X", "H1"],
88
- ["M", "D", "Z"],
89
- ["Y", "D"],
90
- ],
91
- "MCLC-1": [
92
- ["Г", "Y", "Г", "L", "I"],
93
- ["I1", "Z", "F1"],
94
- ["Y", "X1"],
95
- ["X", "Г", "N"],
96
- ["M", "Г"],
97
- ],
98
- "MCLC-2": [
99
- ["Г", "Y", "F", "L", "I"],
100
- ["I1", "Z", "F1"],
101
- ["N", "Г", "M"],
102
- ],
103
- "MCLC-3": [
104
- ["Г", "Y", "F", "H", "Z", "I", "F1"],
105
- ["H1", "Y1", "X", "Г", "N"],
106
- ["M", "Г"],
107
- ],
108
- "MCLC-4": [
109
- ["Г", "Y", "F", "H", "Z", "I"],
110
- ["H1", "Y1", "X", "Г", "N"],
111
- ["M", "Г"],
112
- ],
113
- "MCLC-5": [
114
- ["Г", "Y", "F", "L", "I"],
115
- ["I1", "Z", "H", "F1"],
116
- ["H1", "Y1", "X", "Г", "N"],
117
- ["M", "Г"],
118
- ],
119
- TRI: [
120
- ["X", "Г", "Y"],
121
- ["L", "Г", "Z"],
122
- ["N", "Г", "M"],
123
- ["R", "Г"],
124
- ],
125
- };
126
-
127
19
  // Export a processed version of the paths
128
20
  export const paths: PathsType = (() => {
129
21
  const result: PathsType = {};
@@ -132,7 +24,7 @@ export const paths: PathsType = (() => {
132
24
  // Flatten arrays of path segments into a single array
133
25
  const flattenedPath: string[] = pathSegments.reduce(
134
26
  (acc, segment) => [...acc, ...segment],
135
- [],
27
+ [] as string[],
136
28
  );
137
29
 
138
30
  // Convert to KPointStep array
package/src/js/made.ts CHANGED
@@ -3,9 +3,9 @@ import { Cell } from "./cell/cell";
3
3
  import { ATOMIC_COORD_UNITS, coefficients, tolerance, units } from "./constants";
4
4
  import { AtomicConstraints } from "./constraints/constraints";
5
5
  import {
6
- Lattice,
7
6
  defaultNonPeriodicMinimumLatticeSize,
8
7
  diatomicLatticePaddingFactor,
8
+ Lattice,
9
9
  molecularLatticePaddingFactor,
10
10
  } from "./lattice/lattice";
11
11
  import { DEFAULT_LATTICE_UNITS, LATTICE_TYPE_CONFIGS } from "./lattice/lattice_types";
@@ -0,0 +1,5 @@
1
+ """Resolve the shared data/ directory at the repository root."""
2
+
3
+ from pathlib import Path
4
+
5
+ DATA_DIR = Path(__file__).parent / "data"
@@ -142,6 +142,40 @@ class Lattice(RoundNumericValuesMixin, LatticeSchemaVectorless, InMemoryEntityPy
142
142
  max_norm = max(norms)
143
143
  return [round(float(value / max_norm), 3) for value in norms]
144
144
 
145
+ @property
146
+ def type_extended(self) -> str:
147
+ """Get the extended lattice type classification.
148
+
149
+ For lattice types with subtypes (BCT, ORCF, RHL, MCLC, TRI),
150
+ returns the specific subtype based on lattice parameters.
151
+ For other types, returns the base type string.
152
+ """
153
+ a, b, c = self.a, self.b, self.c
154
+ alpha, beta, gamma = self.alpha, self.beta, self.gamma
155
+ cos_alpha = math.cos(math.radians(alpha))
156
+ lattice_type = self.type.value if hasattr(self.type, "value") else str(self.type)
157
+
158
+ if lattice_type == "BCT":
159
+ return "BCT-1" if c < a else "BCT-2"
160
+ elif lattice_type == "ORCF":
161
+ if 1 / (a * a) >= 1 / (b * b) + 1 / (c * c):
162
+ return "ORCF-1"
163
+ return "ORCF-2"
164
+ elif lattice_type == "RHL":
165
+ return "RHL-1" if cos_alpha > 0 else "RHL-2"
166
+ elif lattice_type == "MCLC":
167
+ if gamma >= 90:
168
+ return "MCLC-1"
169
+ if (b / c) * cos_alpha + (b * b / (a * a)) * (1 - cos_alpha * cos_alpha) <= 1:
170
+ return "MCLC-3"
171
+ return "MCLC-5"
172
+ elif lattice_type == "TRI":
173
+ if alpha > 90 and beta > 90 and gamma >= 90:
174
+ return "TRI_1a"
175
+ return "TRI_1b"
176
+ else:
177
+ return lattice_type
178
+
145
179
  def get_hash_string(self, is_scaled: bool = False) -> str:
146
180
  """Mirrors JS Lattice.getHashString(isScaled). Rounds to HASH_TOLERANCE decimal places."""
147
181
  scale = self.a if is_scaled else 1
@@ -55,7 +55,7 @@ defaultMaterialConfig = {
55
55
  # TODO: replace `-Pydantic` with actual class in the next PR
56
56
  class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemoryEntityPydantic):
57
57
  model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True)
58
-
58
+
59
59
  __default_config__ = defaultMaterialConfig
60
60
  __schema__ = MaterialSchema
61
61
 
@@ -132,4 +132,3 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
132
132
  @property
133
133
  def scaled_hash(self) -> str:
134
134
  return self.calculate_hash(is_scaled=True)
135
-
@@ -0,0 +1,5 @@
1
+ """Reciprocal lattice module with symmetry points and k-point path utilities."""
2
+
3
+ from .lattice_reciprocal import ReciprocalLattice
4
+ from .paths import RECIPROCAL_PATHS
5
+ from .symmetry_points import get_symmetry_points
@@ -0,0 +1,147 @@
1
+ """
2
+ Reciprocal lattice class with k-point path and symmetry point utilities.
3
+
4
+ Extends the base Lattice class with reciprocal-space functionality
5
+ for Brillouin zone analysis.
6
+ """
7
+
8
+ from typing import List, Optional
9
+
10
+ import numpy as np
11
+
12
+ from ..lattice import Lattice
13
+ from .paths import RECIPROCAL_PATHS
14
+ from .symmetry_points import get_symmetry_points
15
+
16
+
17
+ class ReciprocalLattice(Lattice):
18
+ """Lattice with reciprocal-space utilities.
19
+
20
+ Provides methods for working with reciprocal lattice vectors,
21
+ high-symmetry points, k-point paths, and k-grid dimensions.
22
+ """
23
+
24
+ def cartesian_coordinates(self, point: List[float]) -> List[float]:
25
+ """Convert a point in crystal (fractional) coordinates to Cartesian coordinates.
26
+
27
+ The Cartesian coordinates are in units of 2π/a.
28
+
29
+ Args:
30
+ point: A 3D point in fractional reciprocal-space coordinates.
31
+
32
+ Returns:
33
+ The point in Cartesian coordinates.
34
+ """
35
+ return np.dot(point, self.reciprocal_vectors).tolist()
36
+
37
+ @property
38
+ def symmetry_points(self) -> List[dict]:
39
+ """Get the list of high-symmetry points for the current lattice.
40
+
41
+ Returns:
42
+ List of dicts with 'point' (str) and 'coordinates' ([float, float, float]).
43
+ """
44
+ return get_symmetry_points(self)
45
+
46
+ @property
47
+ def default_kpoint_path(self) -> List[dict]:
48
+ """Get the default path in reciprocal space for the current lattice.
49
+
50
+ Looks up the path by extended type first (e.g., 'BCT-1'), falling back
51
+ to the base type (e.g., 'BCT').
52
+
53
+ Returns:
54
+ List of dicts with 'point' (str) and 'steps' (int).
55
+ """
56
+ path = RECIPROCAL_PATHS.get(self.type_extended)
57
+ if path is None:
58
+ lattice_type = self.type.value if hasattr(self.type, "value") else str(self.type)
59
+ path = RECIPROCAL_PATHS.get(lattice_type, [])
60
+ return path
61
+
62
+ def extract_kpoint_path(self, data_points: Optional[List[List[float]]] = None) -> List[dict]:
63
+ """Find and label high-symmetry points in a list of raw k-point coordinates.
64
+
65
+ Scans through the provided data points and identifies any that match
66
+ known symmetry points (within tolerance).
67
+
68
+ Args:
69
+ data_points: List of 3D k-point coordinates.
70
+
71
+ Returns:
72
+ List of dicts with 'point' (str), 'steps' (int), and
73
+ 'coordinates' ([float, float, float]) for each matched symmetry point.
74
+ """
75
+ if data_points is None:
76
+ data_points = []
77
+
78
+ kpoint_path: List[dict] = []
79
+ symm_points = self.symmetry_points
80
+
81
+ for index, point in enumerate(data_points):
82
+ for sp in symm_points:
83
+ if np.allclose(sp["coordinates"], point, atol=1e-4):
84
+ kpoint_path.append(
85
+ {
86
+ "point": sp["point"],
87
+ "steps": index,
88
+ "coordinates": sp["coordinates"],
89
+ }
90
+ )
91
+ break
92
+
93
+ return kpoint_path
94
+
95
+ def calculate_dimension(self, n_points: int, index: int) -> int:
96
+ """Calculate k-grid dimension for one reciprocal direction.
97
+
98
+ Args:
99
+ n_points: Total number of k-points.
100
+ index: Index of the reciprocal vector direction (0, 1, or 2).
101
+
102
+ Returns:
103
+ Grid dimension in the specified direction.
104
+ """
105
+ norms = self.reciprocal_vector_norms
106
+ others = [i for i in range(3) if i != index]
107
+ j, k = others[0], others[1]
108
+ n = np.cbrt((n_points * norms[index] ** 2) / (norms[j] * norms[k]))
109
+ return max(1, int(np.ceil(n)))
110
+
111
+ def get_dimensions_from_points_count(self, n_kpoints: int) -> List[int]:
112
+ """Calculate 3D grid dimensions from total number of k-points.
113
+
114
+ Args:
115
+ n_kpoints: Total number of k-points.
116
+
117
+ Returns:
118
+ List of three grid dimensions [n1, n2, n3].
119
+ """
120
+ return [self.calculate_dimension(n_kpoints, i) for i in range(3)]
121
+
122
+ def get_dimensions_from_spacing(self, spacing: float) -> List[int]:
123
+ """Calculate grid dimensions from k-point spacing.
124
+
125
+ The spacing is in Cartesian (2π/a) units.
126
+
127
+ Args:
128
+ spacing: Maximum spacing between k-points.
129
+
130
+ Returns:
131
+ List of three grid dimensions [n1, n2, n3].
132
+ """
133
+ norms = self.reciprocal_vector_norms
134
+ return [max(1, int(np.ceil(round(norm / spacing, 4)))) for norm in norms]
135
+
136
+ def get_spacing_from_dimensions(self, dimensions: List[int]) -> float:
137
+ """Calculate average k-point spacing from grid dimensions.
138
+
139
+ Args:
140
+ dimensions: List of three grid dimensions [n1, n2, n3].
141
+
142
+ Returns:
143
+ Average spacing in Cartesian (2π/a) units.
144
+ """
145
+ norms = self.reciprocal_vector_norms
146
+ spacings = [norms[i] / max(1, dimensions[i]) for i in range(3)]
147
+ return float(np.mean(spacings))
@@ -0,0 +1,30 @@
1
+ """
2
+ Default k-point paths according to AFLOW methodology.
3
+
4
+ Reference: https://arxiv.org/abs/1004.2974
5
+ Paths are split in parts for clarity.
6
+
7
+ Raw segment data is stored in reciprocal_paths.json (shared with JS/TS).
8
+ """
9
+
10
+ import json
11
+ from typing import Dict, List
12
+
13
+ from mat3ra.made.data_helper import DATA_DIR
14
+
15
+ _JSON_FILE = DATA_DIR / "reciprocal_paths.json"
16
+ with open(_JSON_FILE) as f:
17
+ _POINTS: Dict[str, List[List[str]]] = json.load(f)
18
+
19
+
20
+ def _build_reciprocal_paths() -> Dict[str, List[dict]]:
21
+ """Flatten path segments and assign default step count to each point."""
22
+ result: Dict[str, List[dict]] = {}
23
+ for key, path_segments in _POINTS.items():
24
+ flattened = [point for segment in path_segments for point in segment]
25
+ # TODO: calculate number of steps based on distance in k-space
26
+ result[key] = [{"point": point, "steps": 10} for point in flattened]
27
+ return result
28
+
29
+
30
+ RECIPROCAL_PATHS: Dict[str, List[dict]] = _build_reciprocal_paths()