@mat3ra/made 2026.7.13-0 → 2026.7.21-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.
- package/data/reciprocal_paths.json +38 -38
- package/dist/js/data/reciprocal_paths.json +38 -38
- package/dist/js/lattice/reciprocal/symmetry_points.js +1 -1
- package/package.json +1 -1
- package/src/js/lattice/reciprocal/symmetry_points.ts +1 -1
- package/src/py/mat3ra/made/reciprocal/symmetry_points.py +1 -1
- package/src/py/mat3ra/made/tools/analyze/build_metadata_analyzer.py +2 -0
- package/src/py/mat3ra/made/tools/analyze/build_metadata_analyzer_factory.py +31 -0
- package/src/py/mat3ra/made/tools/analyze/interface_material.py +45 -0
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/interface/__init__.py +5 -1
- package/src/py/mat3ra/made/tools/build_components/metadata/material_build_metadata.py +5 -0
- package/src/py/mat3ra/made/tools/calculate/__init__.py +10 -14
- package/tests/js/lattice/lattice_reciprocal.ts +2 -2
- package/tests/py/unit/test_primitive_cell.py +1 -1
- package/tests/py/unit/test_reciprocal_lattice.py +4 -4
- package/tests/py/unit/test_reciprocal_paths.py +3 -3
- package/tests/py/unit/test_symmetry_points.py +7 -7
- package/tests/py/unit/test_tools_analyze_interface_material.py +63 -0
- package/tests/py/unit/test_tools_build_metadata.py +10 -0
|
@@ -1,111 +1,111 @@
|
|
|
1
1
|
{
|
|
2
2
|
"CUB": [
|
|
3
|
-
["
|
|
3
|
+
["Γ", "X", "M", "Γ", "R", "X"],
|
|
4
4
|
["M", "R"]
|
|
5
5
|
],
|
|
6
6
|
"BCC": [
|
|
7
|
-
["
|
|
7
|
+
["Γ", "H", "N", "Γ", "P", "H"],
|
|
8
8
|
["P", "N"]
|
|
9
9
|
],
|
|
10
10
|
"FCC": [
|
|
11
|
-
["
|
|
11
|
+
["Γ", "X", "W", "K", "Γ", "L", "U", "W", "L"],
|
|
12
12
|
["U", "X"]
|
|
13
13
|
],
|
|
14
14
|
"TET": [
|
|
15
|
-
["
|
|
15
|
+
["Γ", "X", "M", "Γ", "Z", "R", "A", "Z"],
|
|
16
16
|
["X", "R"],
|
|
17
17
|
["M", "A"]
|
|
18
18
|
],
|
|
19
19
|
"BCT-1": [
|
|
20
|
-
["
|
|
20
|
+
["Γ", "X", "M", "Γ", "Z", "P", "N", "Z1", "M"],
|
|
21
21
|
["X", "P"]
|
|
22
22
|
],
|
|
23
23
|
"BCT-2": [
|
|
24
|
-
["
|
|
24
|
+
["Γ", "X", "Y", "∑", "Γ", "Z", "∑1", "N", "P", "Y1", "Z"],
|
|
25
25
|
["X", "P"]
|
|
26
26
|
],
|
|
27
27
|
"ORC": [
|
|
28
|
-
["
|
|
28
|
+
["Γ", "X", "S", "Y", "Γ", "Z", "U", "R", "T", "Z"],
|
|
29
29
|
["Y", "T"],
|
|
30
30
|
["U", "X"],
|
|
31
31
|
["S", "R"]
|
|
32
32
|
],
|
|
33
33
|
"ORCF-1": [
|
|
34
|
-
["
|
|
34
|
+
["Γ", "Y", "T", "Z", "Γ", "X", "A1", "Y"],
|
|
35
35
|
["T", "X1"],
|
|
36
36
|
["X", "A", "Z"],
|
|
37
|
-
["L", "
|
|
37
|
+
["L", "Γ"]
|
|
38
38
|
],
|
|
39
39
|
"ORCF-2": [
|
|
40
|
-
["
|
|
40
|
+
["Γ", "Y", "C", "D", "X", "Γ", "Z", "D1", "H", "C"],
|
|
41
41
|
["C1", "Z"],
|
|
42
42
|
["X", "H1"],
|
|
43
43
|
["H", "Y"],
|
|
44
|
-
["L", "
|
|
44
|
+
["L", "Γ"]
|
|
45
45
|
],
|
|
46
46
|
"ORCF-3": [
|
|
47
|
-
["
|
|
47
|
+
["Γ", "Y", "T", "Z", "Γ", "X", "A1", "Y"],
|
|
48
48
|
["X", "A", "Z"],
|
|
49
|
-
["L", "
|
|
49
|
+
["L", "Γ"]
|
|
50
50
|
],
|
|
51
51
|
"ORCI": [
|
|
52
|
-
["
|
|
52
|
+
["Γ", "X", "L", "T", "W", "R", "X1", "Z", "Γ", "Y", "S", "W"],
|
|
53
53
|
["L1", "Y"],
|
|
54
54
|
["Y1", "Z"]
|
|
55
55
|
],
|
|
56
56
|
"ORCC": [
|
|
57
|
-
["
|
|
57
|
+
["Γ", "X", "S", "R", "A", "Z", "Γ", "Y", "X1", "A1", "T", "Y"],
|
|
58
58
|
["Z", "T"]
|
|
59
59
|
],
|
|
60
60
|
"HEX": [
|
|
61
|
-
["
|
|
61
|
+
["Γ", "M", "K", "Γ", "A", "L", "H", "A"],
|
|
62
62
|
["L", "M"],
|
|
63
63
|
["K", "H"]
|
|
64
64
|
],
|
|
65
65
|
"RHL-1": [
|
|
66
|
-
["
|
|
67
|
-
["B", "Z", "
|
|
66
|
+
["Γ", "L", "B1"],
|
|
67
|
+
["B", "Z", "Γ", "X"],
|
|
68
68
|
["Q", "F", "P1", "Z"],
|
|
69
69
|
["L", "P"]
|
|
70
70
|
],
|
|
71
|
-
"RHL-2": [["
|
|
71
|
+
"RHL-2": [["Γ", "P", "Z", "Q", "Γ", "F", "P1", "Q1", "L", "Z"]],
|
|
72
72
|
"MCL": [
|
|
73
|
-
["
|
|
73
|
+
["Γ", "Y", "H", "C", "E", "M1", "A", "X", "H1"],
|
|
74
74
|
["M", "D", "Z"],
|
|
75
75
|
["Y", "D"]
|
|
76
76
|
],
|
|
77
77
|
"MCLC-1": [
|
|
78
|
-
["
|
|
78
|
+
["Γ", "Y", "Γ", "L", "I"],
|
|
79
79
|
["I1", "Z", "F1"],
|
|
80
80
|
["Y", "X1"],
|
|
81
|
-
["X", "
|
|
82
|
-
["M", "
|
|
81
|
+
["X", "Γ", "N"],
|
|
82
|
+
["M", "Γ"]
|
|
83
83
|
],
|
|
84
84
|
"MCLC-2": [
|
|
85
|
-
["
|
|
85
|
+
["Γ", "Y", "F", "L", "I"],
|
|
86
86
|
["I1", "Z", "F1"],
|
|
87
|
-
["N", "
|
|
87
|
+
["N", "Γ", "M"]
|
|
88
88
|
],
|
|
89
89
|
"MCLC-3": [
|
|
90
|
-
["
|
|
91
|
-
["H1", "Y1", "X", "
|
|
92
|
-
["M", "
|
|
90
|
+
["Γ", "Y", "F", "H", "Z", "I", "F1"],
|
|
91
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
92
|
+
["M", "Γ"]
|
|
93
93
|
],
|
|
94
94
|
"MCLC-4": [
|
|
95
|
-
["
|
|
96
|
-
["H1", "Y1", "X", "
|
|
97
|
-
["M", "
|
|
95
|
+
["Γ", "Y", "F", "H", "Z", "I"],
|
|
96
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
97
|
+
["M", "Γ"]
|
|
98
98
|
],
|
|
99
99
|
"MCLC-5": [
|
|
100
|
-
["
|
|
100
|
+
["Γ", "Y", "F", "L", "I"],
|
|
101
101
|
["I1", "Z", "H", "F1"],
|
|
102
|
-
["H1", "Y1", "X", "
|
|
103
|
-
["M", "
|
|
102
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
103
|
+
["M", "Γ"]
|
|
104
104
|
],
|
|
105
105
|
"TRI": [
|
|
106
|
-
["X", "
|
|
107
|
-
["L", "
|
|
108
|
-
["N", "
|
|
109
|
-
["R", "
|
|
106
|
+
["X", "Γ", "Y"],
|
|
107
|
+
["L", "Γ", "Z"],
|
|
108
|
+
["N", "Γ", "M"],
|
|
109
|
+
["R", "Γ"]
|
|
110
110
|
]
|
|
111
111
|
}
|
|
@@ -1,111 +1,111 @@
|
|
|
1
1
|
{
|
|
2
2
|
"CUB": [
|
|
3
|
-
["
|
|
3
|
+
["Γ", "X", "M", "Γ", "R", "X"],
|
|
4
4
|
["M", "R"]
|
|
5
5
|
],
|
|
6
6
|
"BCC": [
|
|
7
|
-
["
|
|
7
|
+
["Γ", "H", "N", "Γ", "P", "H"],
|
|
8
8
|
["P", "N"]
|
|
9
9
|
],
|
|
10
10
|
"FCC": [
|
|
11
|
-
["
|
|
11
|
+
["Γ", "X", "W", "K", "Γ", "L", "U", "W", "L"],
|
|
12
12
|
["U", "X"]
|
|
13
13
|
],
|
|
14
14
|
"TET": [
|
|
15
|
-
["
|
|
15
|
+
["Γ", "X", "M", "Γ", "Z", "R", "A", "Z"],
|
|
16
16
|
["X", "R"],
|
|
17
17
|
["M", "A"]
|
|
18
18
|
],
|
|
19
19
|
"BCT-1": [
|
|
20
|
-
["
|
|
20
|
+
["Γ", "X", "M", "Γ", "Z", "P", "N", "Z1", "M"],
|
|
21
21
|
["X", "P"]
|
|
22
22
|
],
|
|
23
23
|
"BCT-2": [
|
|
24
|
-
["
|
|
24
|
+
["Γ", "X", "Y", "∑", "Γ", "Z", "∑1", "N", "P", "Y1", "Z"],
|
|
25
25
|
["X", "P"]
|
|
26
26
|
],
|
|
27
27
|
"ORC": [
|
|
28
|
-
["
|
|
28
|
+
["Γ", "X", "S", "Y", "Γ", "Z", "U", "R", "T", "Z"],
|
|
29
29
|
["Y", "T"],
|
|
30
30
|
["U", "X"],
|
|
31
31
|
["S", "R"]
|
|
32
32
|
],
|
|
33
33
|
"ORCF-1": [
|
|
34
|
-
["
|
|
34
|
+
["Γ", "Y", "T", "Z", "Γ", "X", "A1", "Y"],
|
|
35
35
|
["T", "X1"],
|
|
36
36
|
["X", "A", "Z"],
|
|
37
|
-
["L", "
|
|
37
|
+
["L", "Γ"]
|
|
38
38
|
],
|
|
39
39
|
"ORCF-2": [
|
|
40
|
-
["
|
|
40
|
+
["Γ", "Y", "C", "D", "X", "Γ", "Z", "D1", "H", "C"],
|
|
41
41
|
["C1", "Z"],
|
|
42
42
|
["X", "H1"],
|
|
43
43
|
["H", "Y"],
|
|
44
|
-
["L", "
|
|
44
|
+
["L", "Γ"]
|
|
45
45
|
],
|
|
46
46
|
"ORCF-3": [
|
|
47
|
-
["
|
|
47
|
+
["Γ", "Y", "T", "Z", "Γ", "X", "A1", "Y"],
|
|
48
48
|
["X", "A", "Z"],
|
|
49
|
-
["L", "
|
|
49
|
+
["L", "Γ"]
|
|
50
50
|
],
|
|
51
51
|
"ORCI": [
|
|
52
|
-
["
|
|
52
|
+
["Γ", "X", "L", "T", "W", "R", "X1", "Z", "Γ", "Y", "S", "W"],
|
|
53
53
|
["L1", "Y"],
|
|
54
54
|
["Y1", "Z"]
|
|
55
55
|
],
|
|
56
56
|
"ORCC": [
|
|
57
|
-
["
|
|
57
|
+
["Γ", "X", "S", "R", "A", "Z", "Γ", "Y", "X1", "A1", "T", "Y"],
|
|
58
58
|
["Z", "T"]
|
|
59
59
|
],
|
|
60
60
|
"HEX": [
|
|
61
|
-
["
|
|
61
|
+
["Γ", "M", "K", "Γ", "A", "L", "H", "A"],
|
|
62
62
|
["L", "M"],
|
|
63
63
|
["K", "H"]
|
|
64
64
|
],
|
|
65
65
|
"RHL-1": [
|
|
66
|
-
["
|
|
67
|
-
["B", "Z", "
|
|
66
|
+
["Γ", "L", "B1"],
|
|
67
|
+
["B", "Z", "Γ", "X"],
|
|
68
68
|
["Q", "F", "P1", "Z"],
|
|
69
69
|
["L", "P"]
|
|
70
70
|
],
|
|
71
|
-
"RHL-2": [["
|
|
71
|
+
"RHL-2": [["Γ", "P", "Z", "Q", "Γ", "F", "P1", "Q1", "L", "Z"]],
|
|
72
72
|
"MCL": [
|
|
73
|
-
["
|
|
73
|
+
["Γ", "Y", "H", "C", "E", "M1", "A", "X", "H1"],
|
|
74
74
|
["M", "D", "Z"],
|
|
75
75
|
["Y", "D"]
|
|
76
76
|
],
|
|
77
77
|
"MCLC-1": [
|
|
78
|
-
["
|
|
78
|
+
["Γ", "Y", "Γ", "L", "I"],
|
|
79
79
|
["I1", "Z", "F1"],
|
|
80
80
|
["Y", "X1"],
|
|
81
|
-
["X", "
|
|
82
|
-
["M", "
|
|
81
|
+
["X", "Γ", "N"],
|
|
82
|
+
["M", "Γ"]
|
|
83
83
|
],
|
|
84
84
|
"MCLC-2": [
|
|
85
|
-
["
|
|
85
|
+
["Γ", "Y", "F", "L", "I"],
|
|
86
86
|
["I1", "Z", "F1"],
|
|
87
|
-
["N", "
|
|
87
|
+
["N", "Γ", "M"]
|
|
88
88
|
],
|
|
89
89
|
"MCLC-3": [
|
|
90
|
-
["
|
|
91
|
-
["H1", "Y1", "X", "
|
|
92
|
-
["M", "
|
|
90
|
+
["Γ", "Y", "F", "H", "Z", "I", "F1"],
|
|
91
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
92
|
+
["M", "Γ"]
|
|
93
93
|
],
|
|
94
94
|
"MCLC-4": [
|
|
95
|
-
["
|
|
96
|
-
["H1", "Y1", "X", "
|
|
97
|
-
["M", "
|
|
95
|
+
["Γ", "Y", "F", "H", "Z", "I"],
|
|
96
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
97
|
+
["M", "Γ"]
|
|
98
98
|
],
|
|
99
99
|
"MCLC-5": [
|
|
100
|
-
["
|
|
100
|
+
["Γ", "Y", "F", "L", "I"],
|
|
101
101
|
["I1", "Z", "H", "F1"],
|
|
102
|
-
["H1", "Y1", "X", "
|
|
103
|
-
["M", "
|
|
102
|
+
["H1", "Y1", "X", "Γ", "N"],
|
|
103
|
+
["M", "Γ"]
|
|
104
104
|
],
|
|
105
105
|
"TRI": [
|
|
106
|
-
["X", "
|
|
107
|
-
["L", "
|
|
108
|
-
["N", "
|
|
109
|
-
["R", "
|
|
106
|
+
["X", "Γ", "Y"],
|
|
107
|
+
["L", "Γ", "Z"],
|
|
108
|
+
["N", "Γ", "M"],
|
|
109
|
+
["R", "Γ"]
|
|
110
110
|
]
|
|
111
111
|
}
|
package/package.json
CHANGED
|
@@ -367,7 +367,7 @@ def get_symmetry_points(lattice: Any) -> List[dict]:
|
|
|
367
367
|
"""
|
|
368
368
|
lattice_type = lattice.type.value if hasattr(lattice.type, "value") else str(lattice.type)
|
|
369
369
|
|
|
370
|
-
gamma_point = [{"point": "
|
|
370
|
+
gamma_point = [{"point": "Γ", "coordinates": [0.0, 0.0, 0.0]}]
|
|
371
371
|
|
|
372
372
|
points_fn = POINTS.get(lattice_type)
|
|
373
373
|
if points_fn is None:
|
|
@@ -16,6 +16,8 @@ class BuildMetadataAnalyzer(BaseMaterialAnalyzer, Generic[TypeConfiguration, Typ
|
|
|
16
16
|
|
|
17
17
|
@property
|
|
18
18
|
def build_metadata(self) -> BuildMetadata:
|
|
19
|
+
if not self.material.metadata.build:
|
|
20
|
+
raise ValueError("No metadata.build on the material.")
|
|
19
21
|
metadata = self.material.metadata.get_build_metadata_of_type(self.configuration_cls.__name__)
|
|
20
22
|
if not metadata:
|
|
21
23
|
raise ValueError(f"Build metadata for {self.configuration_cls.__name__} not found.")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Dict, Type
|
|
2
|
+
|
|
3
|
+
from ..build_components.metadata import MaterialWithBuildMetadata
|
|
4
|
+
from .build_metadata_analyzer import BuildMetadataAnalyzer
|
|
5
|
+
from .interface_material import (
|
|
6
|
+
InterfaceMaterialAnalyzer,
|
|
7
|
+
)
|
|
8
|
+
from .slab import SlabMaterialAnalyzer
|
|
9
|
+
|
|
10
|
+
BUILD_METADATA_ANALYZER_REGISTRY: Dict[str, Type[BuildMetadataAnalyzer]] = {
|
|
11
|
+
"SlabConfiguration": SlabMaterialAnalyzer,
|
|
12
|
+
"InterfaceConfiguration": InterfaceMaterialAnalyzer,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_build_metadata_analyzer(
|
|
17
|
+
material: MaterialWithBuildMetadata,
|
|
18
|
+
**analyzer_kwargs,
|
|
19
|
+
) -> BuildMetadataAnalyzer:
|
|
20
|
+
build_metadata = material.metadata.get_latest_build_metadata()
|
|
21
|
+
configuration_type = build_metadata.configuration.get("type")
|
|
22
|
+
if not configuration_type:
|
|
23
|
+
raise ValueError("Latest build step has no configuration type.")
|
|
24
|
+
analyzer_cls = BUILD_METADATA_ANALYZER_REGISTRY.get(configuration_type)
|
|
25
|
+
if analyzer_cls is None:
|
|
26
|
+
registered_types = ", ".join(sorted(BUILD_METADATA_ANALYZER_REGISTRY))
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"No build metadata analyzer registered for '{configuration_type}'. "
|
|
29
|
+
f"Registered types: {registered_types}."
|
|
30
|
+
)
|
|
31
|
+
return analyzer_cls(material=material, **analyzer_kwargs)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Type, Union
|
|
2
|
+
|
|
3
|
+
from ..build.compound_pristine_structures.two_dimensional.interface.base.build_parameters import (
|
|
4
|
+
InterfaceBuilderParameters,
|
|
5
|
+
)
|
|
6
|
+
from ..build.compound_pristine_structures.two_dimensional.interface.base.configuration import InterfaceConfiguration
|
|
7
|
+
from ..build.pristine_structures.two_dimensional.slab.configuration import SlabConfiguration
|
|
8
|
+
from ..build.pristine_structures.two_dimensional.slab_strained_supercell.configuration import (
|
|
9
|
+
SlabStrainedSupercellConfiguration,
|
|
10
|
+
)
|
|
11
|
+
from ..build_components.metadata import MaterialWithBuildMetadata
|
|
12
|
+
from ..convert.interface_parts_enum import INTERFACE_LABELS_MAP
|
|
13
|
+
from .build_metadata_analyzer import BuildMetadataAnalyzer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InterfaceMaterialAnalyzer(
|
|
17
|
+
BuildMetadataAnalyzer[InterfaceConfiguration, InterfaceBuilderParameters],
|
|
18
|
+
):
|
|
19
|
+
configuration_cls: Type[InterfaceConfiguration] = InterfaceConfiguration
|
|
20
|
+
build_parameters_cls: Type[InterfaceBuilderParameters] = InterfaceBuilderParameters
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def substrate_slab_configuration(self) -> Union[SlabStrainedSupercellConfiguration, SlabConfiguration]:
|
|
24
|
+
return self.build_configuration.substrate_configuration
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def film_slab_configuration(self) -> Union[SlabStrainedSupercellConfiguration, SlabConfiguration]:
|
|
28
|
+
return self.build_configuration.film_configuration
|
|
29
|
+
|
|
30
|
+
def get_bulk_crystal(self, part: str = "substrate") -> dict:
|
|
31
|
+
if part not in INTERFACE_LABELS_MAP:
|
|
32
|
+
raise ValueError(f"Unknown interface part '{part}'. Use 'substrate' or 'film'.")
|
|
33
|
+
slab_configuration = self.substrate_slab_configuration if part == "substrate" else self.film_slab_configuration
|
|
34
|
+
crystal = slab_configuration.atomic_layers.crystal
|
|
35
|
+
if crystal is None:
|
|
36
|
+
raise ValueError(f"No bulk crystal for {part} in interface build metadata.")
|
|
37
|
+
return crystal if isinstance(crystal, dict) else crystal.to_dict()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_interface_configuration(interface: MaterialWithBuildMetadata) -> InterfaceConfiguration:
|
|
41
|
+
return InterfaceMaterialAnalyzer(material=interface).build_configuration
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_interface_bulk_crystal(interface: MaterialWithBuildMetadata, part: str = "substrate") -> dict:
|
|
45
|
+
return InterfaceMaterialAnalyzer(material=interface).get_bulk_crystal(part=part)
|
|
@@ -13,7 +13,11 @@ from .twisted import (
|
|
|
13
13
|
TwistedNanoribbonsInterfaceConfiguration,
|
|
14
14
|
create_interface_twisted,
|
|
15
15
|
)
|
|
16
|
-
from .utils import
|
|
16
|
+
from .utils import (
|
|
17
|
+
get_optimal_film_displacement,
|
|
18
|
+
get_slab,
|
|
19
|
+
remove_duplicate_interfaces,
|
|
20
|
+
)
|
|
17
21
|
from .zsl import (
|
|
18
22
|
create_interface_zsl,
|
|
19
23
|
create_interface_zsl_between_slabs,
|
|
@@ -10,6 +10,11 @@ class MaterialBuildMetadata(BaseMetadata):
|
|
|
10
10
|
# Other metadata fields can be added as needed
|
|
11
11
|
build: List[BuildMetadata] = Field([])
|
|
12
12
|
|
|
13
|
+
def get_latest_build_metadata(self) -> BuildMetadata:
|
|
14
|
+
if not self.build:
|
|
15
|
+
raise ValueError("No metadata.build on the material.")
|
|
16
|
+
return self.build[-1]
|
|
17
|
+
|
|
13
18
|
def get_build_metadata_of_type(self, configuration_type: str) -> Optional[BuildMetadata]:
|
|
14
19
|
"""
|
|
15
20
|
Returns the first build configuration of the specified type.
|
|
@@ -2,8 +2,9 @@ from typing import Optional
|
|
|
2
2
|
|
|
3
3
|
from ...material import Material
|
|
4
4
|
from ..analyze.other import get_surface_area
|
|
5
|
-
from ..
|
|
6
|
-
from ..
|
|
5
|
+
from ..analyze.interface_material import get_interface_bulk_crystal
|
|
6
|
+
from ..build.compound_pristine_structures.two_dimensional.interface import get_slab
|
|
7
|
+
from ..build_components.metadata import MaterialWithBuildMetadata
|
|
7
8
|
from ..convert import decorator_convert_material_args_kwargs_to_atoms
|
|
8
9
|
from ..third_party import ASEAtoms, ASECalculator, ASECalculatorEMT
|
|
9
10
|
from .calculators import InterfaceMaterialCalculatorParameters
|
|
@@ -114,19 +115,14 @@ def calculate_interfacial_energy(
|
|
|
114
115
|
substrate_slab = get_slab(interface, part="substrate") if substrate_slab is None else substrate_slab
|
|
115
116
|
film_slab = get_slab(interface, part="film") if film_slab is None else film_slab
|
|
116
117
|
|
|
117
|
-
metadata = (
|
|
118
|
-
interface.metadata
|
|
119
|
-
if isinstance(interface, MaterialWithBuildMetadata)
|
|
120
|
-
else MaterialBuildMetadata(**interface.metadata)
|
|
121
|
-
)
|
|
122
|
-
build_configuration = metadata.build[-1].configuration if metadata.build else {}
|
|
123
|
-
config = InterfaceConfiguration(**build_configuration)
|
|
124
|
-
|
|
125
118
|
try:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
119
|
+
interface_with_metadata = MaterialWithBuildMetadata.create(interface.to_dict())
|
|
120
|
+
substrate_bulk = Material.create(get_interface_bulk_crystal(interface_with_metadata, part="substrate"))
|
|
121
|
+
film_bulk = Material.create(get_interface_bulk_crystal(interface_with_metadata, part="film"))
|
|
122
|
+
except ValueError as exc:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
"The substrate and film bulk materials must be provided or defined in the interface metadata."
|
|
125
|
+
) from exc
|
|
130
126
|
surface_energy_substrate = calculate_surface_energy(substrate_slab, substrate_bulk, calculator)
|
|
131
127
|
surface_energy_film = calculate_surface_energy(film_slab, film_bulk, calculator)
|
|
132
128
|
adhesion_energy = calculate_adhesion_energy(interface, substrate_slab, film_slab, calculator)
|
|
@@ -14,7 +14,7 @@ describe("Lattice Reciprocal", () => {
|
|
|
14
14
|
const lattice = new ReciprocalLattice(Na4Cl4.lattice as LatticeSchema);
|
|
15
15
|
const expectedPath = [
|
|
16
16
|
{
|
|
17
|
-
point: "
|
|
17
|
+
point: "Γ",
|
|
18
18
|
steps: 0,
|
|
19
19
|
coordinates: [0, 0, 0],
|
|
20
20
|
},
|
|
@@ -54,7 +54,7 @@ describe("Lattice Reciprocal", () => {
|
|
|
54
54
|
const actualPoints = lattice.symmetryPoints;
|
|
55
55
|
const expectedPoints = [
|
|
56
56
|
{
|
|
57
|
-
point: "
|
|
57
|
+
point: "Γ",
|
|
58
58
|
coordinates: [0, 0, 0],
|
|
59
59
|
},
|
|
60
60
|
{
|
|
@@ -3,7 +3,7 @@ import pytest
|
|
|
3
3
|
|
|
4
4
|
from mat3ra.made.cell.primitive_cell import get_primitive_lattice_vectors_from_config
|
|
5
5
|
from mat3ra.made.lattice import Lattice, LatticeTypeEnum
|
|
6
|
-
from
|
|
6
|
+
from unit.utils import assert_two_entities_deep_almost_equal
|
|
7
7
|
|
|
8
8
|
EXPECTED_PRIMITIVE_CELL_NA4CL4 = [
|
|
9
9
|
[5.691694, 0.0, 0.0],
|
|
@@ -5,18 +5,18 @@ from mat3ra.utils import assertion as assertion_utils
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_reciprocal_lattice_symmetry_points_cub():
|
|
8
|
-
"""CUB lattice should have
|
|
8
|
+
"""CUB lattice should have Γ, R, X, M symmetry points."""
|
|
9
9
|
lattice = ReciprocalLattice(a=5.64, b=5.64, c=5.64, type="CUB")
|
|
10
10
|
points = lattice.symmetry_points
|
|
11
11
|
labels = [p["point"] for p in points]
|
|
12
|
-
assert labels == ["
|
|
12
|
+
assert labels == ["Γ", "R", "X", "M"]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def test_reciprocal_lattice_default_kpoint_path_cub():
|
|
16
16
|
lattice = ReciprocalLattice(a=5.64, b=5.64, c=5.64, type="CUB")
|
|
17
17
|
path = lattice.default_kpoint_path
|
|
18
18
|
labels = [p["point"] for p in path]
|
|
19
|
-
assert labels == ["
|
|
19
|
+
assert labels == ["Γ", "X", "M", "Γ", "R", "X", "M", "R"]
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def test_reciprocal_lattice_default_kpoint_path_bct():
|
|
@@ -32,7 +32,7 @@ def test_reciprocal_lattice_extract_kpoint_path():
|
|
|
32
32
|
data_points = [[0, 0, 0], [0.5, 0.5, 0.5]]
|
|
33
33
|
result = lattice.extract_kpoint_path(data_points)
|
|
34
34
|
assert len(result) == 2
|
|
35
|
-
assert result[0]["point"] == "
|
|
35
|
+
assert result[0]["point"] == "Γ"
|
|
36
36
|
assert result[0]["steps"] == 0
|
|
37
37
|
assert result[1]["point"] == "R"
|
|
38
38
|
assert result[1]["steps"] == 1
|
|
@@ -49,16 +49,16 @@ def test_reciprocal_paths_entry_structure():
|
|
|
49
49
|
def test_reciprocal_paths_cub():
|
|
50
50
|
path = RECIPROCAL_PATHS["CUB"]
|
|
51
51
|
labels = [p["point"] for p in path]
|
|
52
|
-
assert labels == ["
|
|
52
|
+
assert labels == ["Γ", "X", "M", "Γ", "R", "X", "M", "R"]
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
def test_reciprocal_paths_fcc():
|
|
56
56
|
path = RECIPROCAL_PATHS["FCC"]
|
|
57
57
|
labels = [p["point"] for p in path]
|
|
58
|
-
assert labels == ["
|
|
58
|
+
assert labels == ["Γ", "X", "W", "K", "Γ", "L", "U", "W", "L", "U", "X"]
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def test_reciprocal_paths_hex():
|
|
62
62
|
path = RECIPROCAL_PATHS["HEX"]
|
|
63
63
|
labels = [p["point"] for p in path]
|
|
64
|
-
assert labels == ["
|
|
64
|
+
assert labels == ["Γ", "M", "K", "Γ", "A", "L", "H", "A", "L", "M", "K", "H"]
|
|
@@ -10,7 +10,7 @@ def test_symmetry_points_gamma_always_first():
|
|
|
10
10
|
"""Gamma point should always be the first symmetry point."""
|
|
11
11
|
lattice = Lattice(a=1.0, b=1.0, c=1.0, type="CUB")
|
|
12
12
|
points = get_symmetry_points(lattice)
|
|
13
|
-
assert points[0]["point"] == "
|
|
13
|
+
assert points[0]["point"] == "Γ"
|
|
14
14
|
assert points[0]["coordinates"] == [0.0, 0.0, 0.0]
|
|
15
15
|
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ def test_symmetry_points_cub():
|
|
|
18
18
|
lattice = Lattice(a=5.64, b=5.64, c=5.64, type="CUB")
|
|
19
19
|
points = get_symmetry_points(lattice)
|
|
20
20
|
labels = [p["point"] for p in points]
|
|
21
|
-
assert labels == ["
|
|
21
|
+
assert labels == ["Γ", "R", "X", "M"]
|
|
22
22
|
# Verify specific coordinates
|
|
23
23
|
r_point = next(p for p in points if p["point"] == "R")
|
|
24
24
|
assertion_utils.assert_deep_almost_equal(r_point["coordinates"], [0.5, 0.5, 0.5])
|
|
@@ -45,7 +45,7 @@ def test_symmetry_points_bcc():
|
|
|
45
45
|
lattice = Lattice(a=3.0, b=3.0, c=3.0, type="BCC")
|
|
46
46
|
points = get_symmetry_points(lattice)
|
|
47
47
|
labels = [p["point"] for p in points]
|
|
48
|
-
assert labels == ["
|
|
48
|
+
assert labels == ["Γ", "H", "P", "N"]
|
|
49
49
|
h_point = next(p for p in points if p["point"] == "H")
|
|
50
50
|
assertion_utils.assert_deep_almost_equal(h_point["coordinates"], [0.5, -0.5, 0.5])
|
|
51
51
|
|
|
@@ -54,7 +54,7 @@ def test_symmetry_points_hex():
|
|
|
54
54
|
lattice = Lattice(a=2.46, b=2.46, c=6.71, type="HEX")
|
|
55
55
|
points = get_symmetry_points(lattice)
|
|
56
56
|
labels = [p["point"] for p in points]
|
|
57
|
-
assert set(labels) == {"
|
|
57
|
+
assert set(labels) == {"Γ", "A", "H", "K", "L", "M"}
|
|
58
58
|
k_point = next(p for p in points if p["point"] == "K")
|
|
59
59
|
assertion_utils.assert_deep_almost_equal(k_point["coordinates"], [1 / 3, 1 / 3, 0.0])
|
|
60
60
|
|
|
@@ -86,14 +86,14 @@ def test_symmetry_points_tet():
|
|
|
86
86
|
lattice = Lattice(a=3.0, b=3.0, c=5.0, type="TET")
|
|
87
87
|
points = get_symmetry_points(lattice)
|
|
88
88
|
labels = [p["point"] for p in points]
|
|
89
|
-
assert set(labels) == {"
|
|
89
|
+
assert set(labels) == {"Γ", "A", "M", "R", "X", "Z"}
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def test_symmetry_points_orc():
|
|
93
93
|
lattice = Lattice(a=2.0, b=3.0, c=4.0, type="ORC")
|
|
94
94
|
points = get_symmetry_points(lattice)
|
|
95
95
|
labels = [p["point"] for p in points]
|
|
96
|
-
assert set(labels) == {"
|
|
96
|
+
assert set(labels) == {"Γ", "R", "S", "T", "U", "X", "Y", "Z"}
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
def test_symmetry_points_rhl1():
|
|
@@ -144,7 +144,7 @@ def test_symmetry_points_all_lattice_types(lattice_type):
|
|
|
144
144
|
lattice = Lattice(a=3.0, b=4.0, c=5.0, alpha=80.0, beta=85.0, gamma=75.0, type=lattice_type)
|
|
145
145
|
points = get_symmetry_points(lattice)
|
|
146
146
|
assert len(points) >= 1
|
|
147
|
-
assert points[0]["point"] == "
|
|
147
|
+
assert points[0]["point"] == "Γ"
|
|
148
148
|
# All coordinates should be lists of 3 floats
|
|
149
149
|
for p in points:
|
|
150
150
|
assert len(p["coordinates"]) == 3
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Final, Type
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from mat3ra.made.tools.analyze.build_metadata_analyzer import BuildMetadataAnalyzer
|
|
5
|
+
from mat3ra.made.tools.analyze.build_metadata_analyzer_factory import create_build_metadata_analyzer
|
|
6
|
+
from mat3ra.made.tools.analyze.interface_material import (
|
|
7
|
+
InterfaceMaterialAnalyzer,
|
|
8
|
+
get_interface_bulk_crystal,
|
|
9
|
+
get_interface_configuration,
|
|
10
|
+
)
|
|
11
|
+
from mat3ra.made.tools.analyze.slab import SlabMaterialAnalyzer
|
|
12
|
+
from mat3ra.made.tools.build import MaterialWithBuildMetadata
|
|
13
|
+
from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interface import InterfaceConfiguration
|
|
14
|
+
from unit.fixtures.interface.zsl import GRAPHENE_NICKEL_INTERFACE
|
|
15
|
+
from unit.fixtures.slab import SI_CONVENTIONAL_SLAB_001
|
|
16
|
+
|
|
17
|
+
INTERFACE_MATERIAL: Final = MaterialWithBuildMetadata.create(GRAPHENE_NICKEL_INTERFACE)
|
|
18
|
+
SLAB_MATERIAL: Final = MaterialWithBuildMetadata.create(SI_CONVENTIONAL_SLAB_001)
|
|
19
|
+
SUBSTRATE_CRYSTAL_NAME: Final = "Ni, Nickel, FCC (Fm-3m) 3D (Bulk), mp-23"
|
|
20
|
+
FILM_CRYSTAL_NAME: Final = "Graphene"
|
|
21
|
+
|
|
22
|
+
GET_BULK_CRYSTAL_CASES = [
|
|
23
|
+
("substrate", SUBSTRATE_CRYSTAL_NAME),
|
|
24
|
+
("film", FILM_CRYSTAL_NAME),
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
CREATE_BUILD_METADATA_ANALYZER_CASES = [
|
|
28
|
+
(INTERFACE_MATERIAL, InterfaceMaterialAnalyzer),
|
|
29
|
+
(SLAB_MATERIAL, SlabMaterialAnalyzer),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_build_configuration():
|
|
34
|
+
configuration = InterfaceMaterialAnalyzer(material=INTERFACE_MATERIAL).build_configuration
|
|
35
|
+
assert isinstance(configuration, InterfaceConfiguration)
|
|
36
|
+
assert configuration.type == "InterfaceConfiguration"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.parametrize("part,expected_name", GET_BULK_CRYSTAL_CASES)
|
|
40
|
+
def test_get_bulk_crystal(part, expected_name):
|
|
41
|
+
crystal = InterfaceMaterialAnalyzer(material=INTERFACE_MATERIAL).get_bulk_crystal(part=part)
|
|
42
|
+
assert crystal["name"] == expected_name
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_get_interface_configuration():
|
|
46
|
+
configuration = get_interface_configuration(INTERFACE_MATERIAL)
|
|
47
|
+
assert isinstance(configuration, InterfaceConfiguration)
|
|
48
|
+
assert configuration.type == "InterfaceConfiguration"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize("part,expected_name", GET_BULK_CRYSTAL_CASES)
|
|
52
|
+
def test_get_interface_bulk_crystal(part, expected_name):
|
|
53
|
+
crystal = get_interface_bulk_crystal(INTERFACE_MATERIAL, part=part)
|
|
54
|
+
assert crystal["name"] == expected_name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("material,expected_analyzer_cls", CREATE_BUILD_METADATA_ANALYZER_CASES)
|
|
58
|
+
def test_create_build_metadata_analyzer(
|
|
59
|
+
material: MaterialWithBuildMetadata,
|
|
60
|
+
expected_analyzer_cls: Type[BuildMetadataAnalyzer],
|
|
61
|
+
):
|
|
62
|
+
analyzer = create_build_metadata_analyzer(material)
|
|
63
|
+
assert isinstance(analyzer, expected_analyzer_cls)
|
|
@@ -28,6 +28,16 @@ def test_metadata_empty_initialization():
|
|
|
28
28
|
assert metadata.model_dump(exclude_none=True) == {"build": []}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def test_get_latest_build_metadata():
|
|
32
|
+
metadata = MaterialBuildMetadata(build=[BuildMetadata(configuration={"type": "SlabConfiguration"})])
|
|
33
|
+
assert metadata.get_latest_build_metadata().configuration["type"] == "SlabConfiguration"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_get_latest_build_metadata_raises_when_empty():
|
|
37
|
+
with pytest.raises(ValueError, match="No metadata.build"):
|
|
38
|
+
MaterialBuildMetadata().get_latest_build_metadata()
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
@pytest.mark.parametrize(
|
|
32
42
|
"config_object, expected_dict",
|
|
33
43
|
[
|