@mat3ra/made 2025.8.8-0 → 2025.8.9-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/package.json +1 -1
- package/src/py/mat3ra/made/tools/analyze/interface/simple.py +86 -2
- package/src/py/mat3ra/made/tools/analyze/other.py +1 -1
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/__init__.py +7 -0
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/helpers.py +101 -0
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/types.py +26 -0
- package/src/py/mat3ra/made/tools/build/defective_structures/two_dimensional/adatom/helpers.py +15 -15
- package/src/py/mat3ra/made/tools/build/defective_structures/two_dimensional/adatom/types.py +22 -0
- package/src/py/mat3ra/made/tools/build/defective_structures/zero_dimensional/point_defect/helpers.py +15 -18
- package/src/py/mat3ra/made/tools/build/defective_structures/zero_dimensional/point_defect/types.py +24 -0
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanoribbon/__init__.py +2 -2
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanoribbon/helpers.py +2 -2
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanotape/builders.py +3 -3
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanotape/helpers.py +2 -2
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/slab/builder.py +1 -1
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/slab/configuration.py +1 -1
- package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/slab/utils.py +13 -1
- package/src/py/mat3ra/made/tools/build_components/__init__.py +0 -1
- package/src/py/mat3ra/made/tools/build_components/entities/reusable/one_dimensional/crystal_lattice_lines/builder.py +1 -1
- package/src/py/mat3ra/made/tools/build_components/entities/reusable/one_dimensional/crystal_lattice_lines/edge_types.py +4 -4
- package/src/py/mat3ra/made/tools/build_components/entities/reusable/one_dimensional/crystal_lattice_lines/helpers.py +3 -3
- package/src/py/mat3ra/made/tools/build_components/operations/core/combinations/stack/configuration.py +0 -1
- package/src/py/mat3ra/made/tools/build_components/utils.py +2 -57
- package/src/py/mat3ra/made/tools/entities/coordinate/box_coordinate_condition.py +2 -1
- package/src/py/mat3ra/made/tools/entities/coordinate/cylinder_coordinate_condition.py +2 -1
- package/src/py/mat3ra/made/tools/entities/coordinate/plane_coordinate_condition.py +3 -1
- package/src/py/mat3ra/made/tools/entities/coordinate/sphere_coordinate_condition.py +2 -1
- package/src/py/mat3ra/made/tools/entities/coordinate/triangular_prism_coordinate_condition.py +3 -1
- package/src/py/mat3ra/made/tools/helpers.py +24 -0
- package/src/py/mat3ra/made/tools/modify.py +1 -1
- package/src/py/mat3ra/made/tools/operations/core/unary.py +1 -1
- package/tests/py/unit/fixtures/bulk.py +5 -0
- package/tests/py/unit/test_tools_analyze_interface.py +53 -1
- package/tests/py/unit/test_tools_analyze_interface_zsl.py +3 -4
- package/tests/py/unit/test_tools_build_defect/test_adatom.py +11 -4
- package/tests/py/unit/test_tools_build_defect/test_point_defect.py +4 -1
- package/tests/py/unit/test_tools_build_heterostructure.py +48 -0
- package/tests/py/unit/test_tools_build_interface.py +2 -3
- package/tests/py/unit/test_tools_build_interface_zsl.py +2 -2
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from functools import cached_property
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Optional, Tuple
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
from mat3ra.code.entity import InMemoryEntityPydantic
|
|
@@ -14,7 +14,10 @@ from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.configurat
|
|
|
14
14
|
from ...build.pristine_structures.two_dimensional.slab_strained_supercell.configuration import (
|
|
15
15
|
SlabStrainedSupercellConfiguration,
|
|
16
16
|
)
|
|
17
|
+
from ...operations.core.unary import supercell
|
|
18
|
+
from ...utils import unwrap
|
|
17
19
|
from ..interface.utils.holders import MatchedSubstrateFilmConfigurationHolder
|
|
20
|
+
from ..utils import calculate_von_mises_strain
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class InterfaceAnalyzer(InMemoryEntityPydantic):
|
|
@@ -30,6 +33,7 @@ class InterfaceAnalyzer(InMemoryEntityPydantic):
|
|
|
30
33
|
film_slab_configuration: SlabConfiguration
|
|
31
34
|
substrate_build_parameters: Optional[SlabBuilderParameters] = None
|
|
32
35
|
film_build_parameters: Optional[SlabBuilderParameters] = None
|
|
36
|
+
optimize_film_supercell: bool = False
|
|
33
37
|
|
|
34
38
|
def get_component_material(self, configuration: SlabConfiguration):
|
|
35
39
|
return SlabBuilder().get_material(configuration)
|
|
@@ -87,6 +91,83 @@ class InterfaceAnalyzer(InMemoryEntityPydantic):
|
|
|
87
91
|
def get_substrate_strain_matrix(self) -> Matrix3x3Schema:
|
|
88
92
|
return self._no_strain_matrix
|
|
89
93
|
|
|
94
|
+
def _calculate_strain_for_film_supercell(self, film_n: int, film_m: int) -> float:
|
|
95
|
+
"""
|
|
96
|
+
Calculate strain for given film supercell configuration against unchanged substrate.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
film_n: Film supercell multiplier in a direction
|
|
100
|
+
film_m: Film supercell multiplier in b direction
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Von Mises strain percentage
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
# Apply supercell to film material
|
|
107
|
+
supercell_matrix = [[film_n, 0], [0, film_m]]
|
|
108
|
+
film_with_supercell = supercell(self.film_material, supercell_matrix)
|
|
109
|
+
|
|
110
|
+
# Use existing get_film_strain_matrix with supercelled film
|
|
111
|
+
strain_matrix = self.get_film_strain_matrix(
|
|
112
|
+
self.substrate_material.lattice.vector_arrays, film_with_supercell.lattice.vector_arrays
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return calculate_von_mises_strain(np.array(unwrap(strain_matrix.root)))
|
|
116
|
+
|
|
117
|
+
def _find_optimal_supercell_factor_for_direction(self, substrate_length: float, film_length: float) -> int:
|
|
118
|
+
"""
|
|
119
|
+
Finds a multiplier for the component of diagonal supercell matrix
|
|
120
|
+
that will get film supercell lattice vector length around substrate length.
|
|
121
|
+
The N leads to the film supercell vector length being shorter than substrate length,
|
|
122
|
+
and N+1 leads to the film supercell vector length being longer than substrate length.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
# Find optimal: when n*film_length < substrate_length < (n+1)*film_length
|
|
126
|
+
optimal = max(1, int(substrate_length / film_length))
|
|
127
|
+
if (optimal + 1) * film_length - substrate_length < substrate_length - optimal * film_length:
|
|
128
|
+
optimal += 1
|
|
129
|
+
|
|
130
|
+
return optimal
|
|
131
|
+
|
|
132
|
+
def find_optimal_film_supercell(self) -> Tuple[int, int]:
|
|
133
|
+
"""
|
|
134
|
+
Find optimal (n, m) supercell multipliers for film that minimize strain.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Tuple of (n, m) supercell multipliers that minimize strain
|
|
138
|
+
"""
|
|
139
|
+
substrate_vectors = np.array(self.substrate_material.lattice.vector_arrays[:2])
|
|
140
|
+
film_vectors = np.array(self.film_material.lattice.vector_arrays[:2])
|
|
141
|
+
|
|
142
|
+
substrate_lengths = [np.linalg.norm(substrate_vectors[i, :2]) for i in range(2)]
|
|
143
|
+
film_lengths = [np.linalg.norm(film_vectors[i, :2]) for i in range(2)]
|
|
144
|
+
|
|
145
|
+
optimal_values = []
|
|
146
|
+
for substrate_length, film_length in zip(substrate_lengths, film_lengths):
|
|
147
|
+
optimal = self._find_optimal_supercell_factor_for_direction(substrate_length, film_length)
|
|
148
|
+
optimal_values.append(optimal)
|
|
149
|
+
|
|
150
|
+
n_optimal, m_optimal = optimal_values
|
|
151
|
+
|
|
152
|
+
# Test neighboring values to find minimum strain
|
|
153
|
+
candidates = [
|
|
154
|
+
(n_optimal, m_optimal),
|
|
155
|
+
(n_optimal + 1, m_optimal),
|
|
156
|
+
(n_optimal, m_optimal + 1),
|
|
157
|
+
(n_optimal + 1, m_optimal + 1),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
min_strain = float("inf")
|
|
161
|
+
best_n, best_m = n_optimal, m_optimal
|
|
162
|
+
|
|
163
|
+
for n, m in candidates:
|
|
164
|
+
strain = self._calculate_strain_for_film_supercell(n, m)
|
|
165
|
+
if strain < min_strain:
|
|
166
|
+
min_strain = strain
|
|
167
|
+
best_n, best_m = n, m
|
|
168
|
+
|
|
169
|
+
return best_n, best_m
|
|
170
|
+
|
|
90
171
|
def get_component_strained_configuration(
|
|
91
172
|
self,
|
|
92
173
|
configuration: SlabConfiguration,
|
|
@@ -146,7 +227,10 @@ class InterfaceAnalyzer(InMemoryEntityPydantic):
|
|
|
146
227
|
|
|
147
228
|
@property
|
|
148
229
|
def film_supercell_matrix(self) -> SupercellMatrix2DSchema:
|
|
149
|
-
if self.
|
|
230
|
+
if self.optimize_film_supercell:
|
|
231
|
+
n, m = self.find_optimal_film_supercell()
|
|
232
|
+
return SupercellMatrix2DSchema(root=[[n, 0], [0, m]])
|
|
233
|
+
elif self.film_build_parameters and self.film_build_parameters.xy_supercell_matrix:
|
|
150
234
|
return SupercellMatrix2DSchema(root=self.film_build_parameters.xy_supercell_matrix)
|
|
151
235
|
return self.identity_supercell
|
|
152
236
|
|
|
@@ -2,9 +2,9 @@ from typing import Callable, List, Literal, Optional
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from mat3ra.made.material import Material
|
|
5
|
-
from mat3ra.made.tools.build.processed_structures.two_dimensional.passivation.enums import SurfaceTypesEnum
|
|
6
5
|
from scipy.spatial import cKDTree
|
|
7
6
|
|
|
7
|
+
from ..build.processed_structures.two_dimensional.passivation.enums import SurfaceTypesEnum
|
|
8
8
|
from ..convert import decorator_convert_material_args_kwargs_to_atoms, to_pymatgen
|
|
9
9
|
from ..third_party import ASEAtoms, PymatgenIStructure
|
|
10
10
|
from ..utils import decorator_convert_position_to_coordinate
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
4
|
+
|
|
5
|
+
from .types import StackComponentDict
|
|
6
|
+
from mat3ra.made.utils import adjust_material_cell_to_set_gap_along_direction
|
|
7
|
+
from ....pristine_structures.two_dimensional.slab.helpers import create_slab
|
|
8
|
+
from ....pristine_structures.two_dimensional.slab_strained_supercell.builder import SlabStrainedSupercellBuilder
|
|
9
|
+
from .....analyze import BaseMaterialAnalyzer
|
|
10
|
+
from .....analyze.interface import InterfaceAnalyzer
|
|
11
|
+
from .....analyze.slab import SlabMaterialAnalyzer
|
|
12
|
+
from .....build_components import MaterialWithBuildMetadata
|
|
13
|
+
from .....operations.core.binary import stack
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_heterostructure(
|
|
17
|
+
stack_component_dicts: List[StackComponentDict],
|
|
18
|
+
gaps: List[float],
|
|
19
|
+
vacuum: float = 10.0,
|
|
20
|
+
use_conventional_cell: bool = True,
|
|
21
|
+
optimize_layer_supercells: bool = True,
|
|
22
|
+
) -> MaterialWithBuildMetadata:
|
|
23
|
+
"""
|
|
24
|
+
Create a heterostructure by stacking multiple slabs, while applying strain to each slab relative to the first slab.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
stack_component_dicts: List of validated stack component configurations
|
|
28
|
+
gaps: List of gaps between adjacent slabs (in Angstroms)
|
|
29
|
+
vacuum: Size of vacuum layer in Angstroms
|
|
30
|
+
use_conventional_cell: Whether to use conventional cell
|
|
31
|
+
optimize_layer_supercells: Whether to find optimal supercells for strained layers
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Heterostructure material with stacked strained slabs
|
|
35
|
+
"""
|
|
36
|
+
if len(stack_component_dicts) < 2:
|
|
37
|
+
raise ValueError("At least 2 stack components are required for a heterostructure")
|
|
38
|
+
|
|
39
|
+
if len(gaps) != len(stack_component_dicts) - 1:
|
|
40
|
+
raise ValueError("Number of gaps must be one less than number of stack components")
|
|
41
|
+
|
|
42
|
+
slabs = []
|
|
43
|
+
for i, component in enumerate(stack_component_dicts):
|
|
44
|
+
slab = create_slab(
|
|
45
|
+
crystal=component.crystal,
|
|
46
|
+
miller_indices=component.miller_indices,
|
|
47
|
+
number_of_layers=component.thickness,
|
|
48
|
+
vacuum=0.0 if i < len(stack_component_dicts) - 1 else vacuum,
|
|
49
|
+
use_conventional_cell=use_conventional_cell,
|
|
50
|
+
xy_supercell_matrix=component.xy_supercell_matrix or [[1, 0], [0, 1]],
|
|
51
|
+
)
|
|
52
|
+
slabs.append(slab)
|
|
53
|
+
|
|
54
|
+
strained_slabs = [slabs[0]] # First slab is the substrate, not strained
|
|
55
|
+
|
|
56
|
+
for i in range(1, len(slabs)):
|
|
57
|
+
substrate_slab = strained_slabs[0]
|
|
58
|
+
film_slab = slabs[i]
|
|
59
|
+
|
|
60
|
+
substrate_analyzer = SlabMaterialAnalyzer(material=substrate_slab)
|
|
61
|
+
film_analyzer = SlabMaterialAnalyzer(material=film_slab)
|
|
62
|
+
|
|
63
|
+
analyzer = InterfaceAnalyzer(
|
|
64
|
+
substrate_slab_configuration=substrate_analyzer.build_configuration,
|
|
65
|
+
film_slab_configuration=film_analyzer.build_configuration,
|
|
66
|
+
substrate_build_parameters=substrate_analyzer.build_parameters,
|
|
67
|
+
film_build_parameters=film_analyzer.build_parameters,
|
|
68
|
+
optimize_film_supercell=optimize_layer_supercells,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
strained_film_config = analyzer.film_strained_configuration
|
|
72
|
+
|
|
73
|
+
builder = SlabStrainedSupercellBuilder()
|
|
74
|
+
strained_slab = builder.get_material(strained_film_config)
|
|
75
|
+
strained_slabs.append(strained_slab)
|
|
76
|
+
|
|
77
|
+
stacked_materials = []
|
|
78
|
+
for i, slab in enumerate(strained_slabs):
|
|
79
|
+
if i < len(gaps):
|
|
80
|
+
slab_with_gap = adjust_material_cell_to_set_gap_along_direction(slab, gaps[i], AxisEnum.z)
|
|
81
|
+
stacked_materials.append(slab_with_gap)
|
|
82
|
+
else:
|
|
83
|
+
stacked_materials.append(slab)
|
|
84
|
+
|
|
85
|
+
heterostructure = stack(stacked_materials, AxisEnum.z)
|
|
86
|
+
heterostructure.name = generate_heterostructure_name(stack_component_dicts)
|
|
87
|
+
|
|
88
|
+
return heterostructure
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def generate_heterostructure_name(stack_component_dicts: List[StackComponentDict]):
|
|
92
|
+
"""Generate a descriptive name for the heterostructure."""
|
|
93
|
+
|
|
94
|
+
components = []
|
|
95
|
+
for component in stack_component_dicts:
|
|
96
|
+
analyzer = BaseMaterialAnalyzer(material=component.crystal)
|
|
97
|
+
formula = analyzer.formula
|
|
98
|
+
miller_str = "".join(map(str, component.miller_indices))
|
|
99
|
+
components.append(f"{formula}({miller_str})")
|
|
100
|
+
|
|
101
|
+
return f"Heterostructure [{'-'.join(components)}]"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import List, Optional, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from mat3ra.code.entity import InMemoryEntityPydantic
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from mat3ra.made.material import Material
|
|
7
|
+
from .....build_components import MaterialWithBuildMetadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StackComponentDict(InMemoryEntityPydantic):
|
|
11
|
+
"""
|
|
12
|
+
Pydantic model for heterostructure stack component configurations.
|
|
13
|
+
|
|
14
|
+
Required fields:
|
|
15
|
+
crystal: The crystal material to create a slab from
|
|
16
|
+
miller_indices: Miller indices for the slab surface as (h, k, l)
|
|
17
|
+
thickness: Number of layers in the slab
|
|
18
|
+
|
|
19
|
+
Optional fields:
|
|
20
|
+
xy_supercell_matrix: Optional supercell matrix for the xy plane
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
crystal: Union[Material, MaterialWithBuildMetadata] = Field(..., description="Crystal material for the slab")
|
|
24
|
+
miller_indices: Tuple[int, int, int] = Field(..., description="Miller indices for the slab surface")
|
|
25
|
+
thickness: int = Field(..., gt=0, description="Number of layers in the slab")
|
|
26
|
+
xy_supercell_matrix: Optional[List[List[int]]] = Field(None, description="Optional xy supercell matrix")
|
package/src/py/mat3ra/made/tools/build/defective_structures/two_dimensional/adatom/helpers.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from types import SimpleNamespace
|
|
2
1
|
from typing import List, Optional
|
|
3
2
|
|
|
4
3
|
from mat3ra.made.material import Material
|
|
@@ -6,6 +5,7 @@ from .builder import AdatomDefectBuilder
|
|
|
6
5
|
from .configuration import (
|
|
7
6
|
AdatomDefectConfiguration,
|
|
8
7
|
)
|
|
8
|
+
from .types import AdatomDefectDict
|
|
9
9
|
from .....analyze.crystal_site.adatom_crystal_site_material_analyzer import (
|
|
10
10
|
AdatomCrystalSiteMaterialAnalyzer,
|
|
11
11
|
)
|
|
@@ -52,12 +52,14 @@ def create_defect_adatom(
|
|
|
52
52
|
slab_with_adatom = create_multiple_adatom_defects(
|
|
53
53
|
slab,
|
|
54
54
|
defect_dicts=[
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
AdatomDefectDict(
|
|
56
|
+
**{
|
|
57
|
+
"element": element or "Si", # Default to Silicon if no element provided
|
|
58
|
+
"coordinate_2d": position_on_surface,
|
|
59
|
+
"distance_z": distance_z,
|
|
60
|
+
"use_cartesian_coordinates": use_cartesian_coordinates,
|
|
61
|
+
}
|
|
62
|
+
)
|
|
61
63
|
],
|
|
62
64
|
placement_method=placement_method,
|
|
63
65
|
)
|
|
@@ -65,10 +67,10 @@ def create_defect_adatom(
|
|
|
65
67
|
|
|
66
68
|
|
|
67
69
|
def create_multiple_adatom_defects(
|
|
68
|
-
slab: MaterialWithBuildMetadata, defect_dicts: List[
|
|
70
|
+
slab: MaterialWithBuildMetadata, defect_dicts: List[AdatomDefectDict], placement_method: str
|
|
69
71
|
) -> Material:
|
|
70
72
|
"""
|
|
71
|
-
Create multiple adatom defects
|
|
73
|
+
Create multiple adatom defects from a list of AdatomDefectDict.
|
|
72
74
|
|
|
73
75
|
Args:
|
|
74
76
|
slab: The slab material.
|
|
@@ -99,10 +101,8 @@ def create_multiple_adatom_defects(
|
|
|
99
101
|
last_analyzer = None
|
|
100
102
|
|
|
101
103
|
for defect_dict in defect_dicts:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
coordinate_2d = defect_configuration.coordinate
|
|
105
|
-
use_cartesian = getattr(defect_configuration, "use_cartesian_coordinates", False)
|
|
104
|
+
coordinate_2d = defect_dict.coordinate_2d
|
|
105
|
+
use_cartesian = defect_dict.use_cartesian_coordinates
|
|
106
106
|
|
|
107
107
|
if use_cartesian:
|
|
108
108
|
coordinate_3d = coordinate_2d + [0.0]
|
|
@@ -112,9 +112,9 @@ def create_multiple_adatom_defects(
|
|
|
112
112
|
analyzer = analyzer_cls(
|
|
113
113
|
material=slab,
|
|
114
114
|
coordinate_2d=coordinate_2d,
|
|
115
|
-
distance_z=
|
|
115
|
+
distance_z=defect_dict.distance_z,
|
|
116
116
|
placement_method=placement_method,
|
|
117
|
-
element=
|
|
117
|
+
element=defect_dict.element,
|
|
118
118
|
)
|
|
119
119
|
last_analyzer = analyzer
|
|
120
120
|
all_adatom_configs.append(analyzer.added_component)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AdatomDefectDict(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Pydantic model for adatom defect configurations used with create_multiple_adatom_defects.
|
|
9
|
+
|
|
10
|
+
Required fields:
|
|
11
|
+
element: Chemical element for the adatom
|
|
12
|
+
coordinate: Position on surface as [x, y] list
|
|
13
|
+
distance_z: Distance above surface in Angstroms
|
|
14
|
+
|
|
15
|
+
Optional fields:
|
|
16
|
+
use_cartesian_coordinates: Whether coordinates are in Cartesian units
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
element: str = Field(..., description="Chemical element for the adatom")
|
|
20
|
+
coordinate_2d: List[float] = Field(..., min_items=2, max_items=2, description="Position on surface as [x, y]")
|
|
21
|
+
distance_z: float = Field(..., gt=0, description="Distance above surface in Angstroms")
|
|
22
|
+
use_cartesian_coordinates: bool = False
|
package/src/py/mat3ra/made/tools/build/defective_structures/zero_dimensional/point_defect/helpers.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from types import SimpleNamespace
|
|
2
1
|
from typing import List, Union
|
|
3
2
|
|
|
4
3
|
from mat3ra.made.material import Material
|
|
@@ -7,6 +6,7 @@ from .interstitial.interstitial_placement_method_enum import InterstitialPlaceme
|
|
|
7
6
|
from .point_defect_type_enum import PointDefectTypeEnum
|
|
8
7
|
from .substitutional.helpers import create_defect_point_substitution
|
|
9
8
|
from .substitutional.substitution_placement_method_enum import SubstitutionPlacementMethodEnum
|
|
9
|
+
from .types import PointDefectDict
|
|
10
10
|
from .vacancy.helpers import create_defect_point_vacancy
|
|
11
11
|
from .vacancy.vacancy_placement_method_enum import VacancyPlacementMethodEnum
|
|
12
12
|
from .....build_components import MaterialWithBuildMetadata
|
|
@@ -32,14 +32,14 @@ DEFECT_TYPE_MAPPING = {
|
|
|
32
32
|
|
|
33
33
|
def create_multiple_defects(
|
|
34
34
|
material: Union[Material, MaterialWithBuildMetadata],
|
|
35
|
-
defect_dicts: List[
|
|
35
|
+
defect_dicts: List[PointDefectDict],
|
|
36
36
|
) -> Material:
|
|
37
37
|
"""
|
|
38
|
-
Create multiple point defects from a list of
|
|
38
|
+
Create multiple point defects from a list of PointDefectDict.
|
|
39
39
|
|
|
40
40
|
Args:
|
|
41
41
|
material (Material): The host material.
|
|
42
|
-
defect_dicts (List[
|
|
42
|
+
defect_dicts (List[PointDefectDict]): List of defect dictionaries with keys:
|
|
43
43
|
- type: str ("vacancy", "substitution", "interstitial")
|
|
44
44
|
- coordinate: List[float]
|
|
45
45
|
- element: str (required for substitution and interstitial)
|
|
@@ -56,39 +56,36 @@ def create_multiple_defects(
|
|
|
56
56
|
current_material = material
|
|
57
57
|
|
|
58
58
|
for defect_dict in defect_dicts:
|
|
59
|
-
|
|
60
|
-
defect_type = defect_configuration.type
|
|
59
|
+
defect_type = defect_dict.type
|
|
61
60
|
|
|
62
61
|
if defect_type not in [e.value for e in PointDefectTypeEnum]:
|
|
63
|
-
raise ValueError(f"Unsupported defect type: {
|
|
62
|
+
raise ValueError(f"Unsupported defect type: {defect_dict.type}")
|
|
64
63
|
|
|
65
|
-
use_cartesian = getattr(
|
|
64
|
+
use_cartesian = getattr(defect_dict, "use_cartesian_coordinates", False)
|
|
66
65
|
|
|
67
66
|
if defect_type == "vacancy":
|
|
68
67
|
current_material = create_defect_point_vacancy(
|
|
69
68
|
current_material,
|
|
70
|
-
coordinate=
|
|
71
|
-
placement_method=
|
|
69
|
+
coordinate=defect_dict.coordinate,
|
|
70
|
+
placement_method=defect_dict.placement_method or VacancyPlacementMethodEnum.CLOSEST_SITE.value,
|
|
72
71
|
use_cartesian_coordinates=use_cartesian,
|
|
73
72
|
)
|
|
74
73
|
|
|
75
74
|
elif defect_type == "substitution":
|
|
76
75
|
current_material = create_defect_point_substitution(
|
|
77
76
|
current_material,
|
|
78
|
-
coordinate=
|
|
79
|
-
element=
|
|
80
|
-
placement_method=
|
|
81
|
-
or SubstitutionPlacementMethodEnum.CLOSEST_SITE.value,
|
|
77
|
+
coordinate=defect_dict.coordinate,
|
|
78
|
+
element=defect_dict.element, # type: ignore # Pydantic validates this at creation
|
|
79
|
+
placement_method=defect_dict.placement_method or SubstitutionPlacementMethodEnum.CLOSEST_SITE.value,
|
|
82
80
|
use_cartesian_coordinates=use_cartesian,
|
|
83
81
|
)
|
|
84
82
|
|
|
85
83
|
elif defect_type == "interstitial":
|
|
86
84
|
current_material = create_defect_point_interstitial(
|
|
87
85
|
current_material,
|
|
88
|
-
coordinate=
|
|
89
|
-
element=
|
|
90
|
-
placement_method=
|
|
91
|
-
or InterstitialPlacementMethodEnum.EXACT_COORDINATE.value,
|
|
86
|
+
coordinate=defect_dict.coordinate,
|
|
87
|
+
element=defect_dict.element, # type: ignore # Pydantic validates this at creation
|
|
88
|
+
placement_method=defect_dict.placement_method or InterstitialPlacementMethodEnum.EXACT_COORDINATE.value,
|
|
92
89
|
use_cartesian_coordinates=use_cartesian,
|
|
93
90
|
)
|
|
94
91
|
|
package/src/py/mat3ra/made/tools/build/defective_structures/zero_dimensional/point_defect/types.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PointDefectDict(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Pydantic model for point defect configurations used with create_multiple_defects.
|
|
9
|
+
|
|
10
|
+
Required fields:
|
|
11
|
+
type: The type of defect ("vacancy", "substitution", "interstitial")
|
|
12
|
+
coordinate: Position coordinates as [x, y, z] list
|
|
13
|
+
|
|
14
|
+
Optional fields:
|
|
15
|
+
element: Chemical element (required for substitution and interstitial)
|
|
16
|
+
placement_method: Method for placing the defect
|
|
17
|
+
use_cartesian_coordinates: Whether coordinates are in Cartesian units
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
type: Literal["vacancy", "substitution", "interstitial"]
|
|
21
|
+
coordinate: List[float] = Field(..., min_items=3, max_items=3, description="Position coordinates as [x, y, z]")
|
|
22
|
+
element: Optional[str] = None
|
|
23
|
+
placement_method: Optional[Literal["closest_site", "exact_coordinate", "voronoi_site"]] = None
|
|
24
|
+
use_cartesian_coordinates: bool = False
|
package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanoribbon/__init__.py
CHANGED
|
@@ -3,7 +3,7 @@ from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lin
|
|
|
3
3
|
CrystalLatticeLinesConfiguration,
|
|
4
4
|
)
|
|
5
5
|
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import (
|
|
6
|
-
|
|
6
|
+
EdgeTypesEnum,
|
|
7
7
|
)
|
|
8
8
|
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines_unique_repeated import (
|
|
9
9
|
CrystalLatticeLinesRepeatedBuilder,
|
|
@@ -25,5 +25,5 @@ __all__ = [
|
|
|
25
25
|
"CrystalLatticeLinesBuilder",
|
|
26
26
|
"CrystalLatticeLinesRepeatedBuilder",
|
|
27
27
|
"create_nanoribbon",
|
|
28
|
-
"
|
|
28
|
+
"EdgeTypesEnum",
|
|
29
29
|
]
|
package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanoribbon/helpers.py
CHANGED
|
@@ -10,7 +10,7 @@ from ..nanotape import NanoTapeConfiguration
|
|
|
10
10
|
from .....build_components import MaterialWithBuildMetadata
|
|
11
11
|
from .....build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
12
12
|
from .....build_components.entities.reusable.base_builder import BaseBuilderParameters
|
|
13
|
-
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import
|
|
13
|
+
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import EdgeTypesEnum
|
|
14
14
|
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.helpers import (
|
|
15
15
|
create_lattice_lines_config_and_material,
|
|
16
16
|
)
|
|
@@ -21,7 +21,7 @@ P = TypeVar("P", bound=BaseBuilderParameters)
|
|
|
21
21
|
def create_nanoribbon(
|
|
22
22
|
material: Union[Material, MaterialWithBuildMetadata],
|
|
23
23
|
miller_indices_2d: Optional[Tuple[int, int]] = None,
|
|
24
|
-
edge_type:
|
|
24
|
+
edge_type: EdgeTypesEnum = EdgeTypesEnum.zigzag,
|
|
25
25
|
width: int = 2,
|
|
26
26
|
length: int = 2,
|
|
27
27
|
vacuum_width: float = 10.0,
|
package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanotape/builders.py
CHANGED
|
@@ -5,7 +5,7 @@ from mat3ra.made.material import Material
|
|
|
5
5
|
from . import NanoTapeBuilderParameters
|
|
6
6
|
from .configuration import NanoTapeConfiguration
|
|
7
7
|
from .....build_components import MaterialWithBuildMetadata, TypeConfiguration
|
|
8
|
-
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import
|
|
8
|
+
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import EdgeTypesEnum
|
|
9
9
|
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines_unique_repeated.builder import (
|
|
10
10
|
CrystalLatticeLinesRepeatedBuilder,
|
|
11
11
|
)
|
|
@@ -44,9 +44,9 @@ class NanoTapeBuilder(StackNComponentsBuilder):
|
|
|
44
44
|
|
|
45
45
|
def _get_edge_type_from_miller_indices(self, miller_indices_2d: tuple) -> str:
|
|
46
46
|
if miller_indices_2d == (1, 1):
|
|
47
|
-
return
|
|
47
|
+
return EdgeTypesEnum.armchair.value.capitalize()
|
|
48
48
|
elif miller_indices_2d == (0, 1):
|
|
49
|
-
return
|
|
49
|
+
return EdgeTypesEnum.zigzag.value.capitalize()
|
|
50
50
|
else:
|
|
51
51
|
miller_str = f"{miller_indices_2d[0]}{miller_indices_2d[1]}"
|
|
52
52
|
return f"({miller_str})"
|
package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/nanotape/helpers.py
CHANGED
|
@@ -7,7 +7,7 @@ from . import NanoTapeConfiguration
|
|
|
7
7
|
from .builders import NanoTapeBuilder, NanoTapeBuilderParameters
|
|
8
8
|
from .....build_components import MaterialWithBuildMetadata
|
|
9
9
|
from .....build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
10
|
-
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import
|
|
10
|
+
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import EdgeTypesEnum
|
|
11
11
|
from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lines.helpers import (
|
|
12
12
|
create_lattice_lines_config_and_material,
|
|
13
13
|
)
|
|
@@ -16,7 +16,7 @@ from .....build_components.entities.reusable.one_dimensional.crystal_lattice_lin
|
|
|
16
16
|
def create_nanotape(
|
|
17
17
|
material: Union[Material, MaterialWithBuildMetadata],
|
|
18
18
|
miller_indices_2d: Optional[Tuple[int, int]] = None,
|
|
19
|
-
edge_type:
|
|
19
|
+
edge_type: EdgeTypesEnum = EdgeTypesEnum.zigzag,
|
|
20
20
|
width: int = 2,
|
|
21
21
|
length: int = 2,
|
|
22
22
|
vacuum_width: float = 10.0,
|
|
@@ -2,6 +2,7 @@ from typing import Type, cast
|
|
|
2
2
|
|
|
3
3
|
from .build_parameters import SlabBuilderParameters
|
|
4
4
|
from .configuration import SlabConfiguration
|
|
5
|
+
from .utils import get_orthogonal_c_slab
|
|
5
6
|
from .....build_components import MaterialWithBuildMetadata
|
|
6
7
|
from .....build_components.entities.reusable.two_dimensional import (
|
|
7
8
|
AtomicLayersUniqueRepeatedConfiguration,
|
|
@@ -9,7 +10,6 @@ from .....build_components.entities.reusable.two_dimensional import (
|
|
|
9
10
|
)
|
|
10
11
|
|
|
11
12
|
from .....build_components.operations.core.combinations.stack.builder import StackNComponentsBuilder
|
|
12
|
-
from .....build_components.utils import get_orthogonal_c_slab
|
|
13
13
|
from .....operations.core.unary import supercell
|
|
14
14
|
|
|
15
15
|
|
package/src/py/mat3ra/made/tools/build/pristine_structures/two_dimensional/slab/configuration.py
CHANGED
|
@@ -3,13 +3,13 @@ from typing import List, Optional, Tuple, Union
|
|
|
3
3
|
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
4
4
|
from mat3ra.esse.models.materials_category.pristine_structures.two_dimensional.slab import SlabConfigurationSchema
|
|
5
5
|
from mat3ra.made.material import Material
|
|
6
|
+
from .termination_utils import select_slab_termination
|
|
6
7
|
|
|
7
8
|
from .....analyze.lattice_planes import CrystalLatticePlanesMaterialAnalyzer
|
|
8
9
|
from .....build_components.entities.reusable.two_dimensional.atomic_layers_unique_repeated.configuration import (
|
|
9
10
|
AtomicLayersUniqueRepeatedConfiguration,
|
|
10
11
|
)
|
|
11
12
|
from .....build_components.metadata import MaterialWithBuildMetadata
|
|
12
|
-
from .....build_components import select_slab_termination
|
|
13
13
|
from .....build_components.operations.core.combinations.stack.configuration import StackConfiguration
|
|
14
14
|
from .....build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
15
15
|
from .....build_components.entities.auxiliary.two_dimensional.termination import Termination
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
from typing import Union
|
|
1
|
+
from typing import Union, Optional, List
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
from mat3ra.made.lattice import Lattice
|
|
6
6
|
from mat3ra.made.material import Material
|
|
7
7
|
from .....build_components import MaterialWithBuildMetadata
|
|
8
|
+
from .....build_components.entities.auxiliary.two_dimensional.termination import Termination
|
|
8
9
|
from .....modify import wrap_to_unit_cell
|
|
9
10
|
from .....operations.core.unary import edit_cell
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
def select_slab_termination(terminations: List[Termination], formula: Optional[str] = None) -> Termination:
|
|
14
|
+
if not terminations:
|
|
15
|
+
raise ValueError("No terminations available.")
|
|
16
|
+
if formula is None:
|
|
17
|
+
return terminations[0]
|
|
18
|
+
for termination in terminations:
|
|
19
|
+
if termination.formula == formula:
|
|
20
|
+
return termination
|
|
21
|
+
raise ValueError(f"Termination with formula {formula} not found in available terminations: {terminations}")
|
|
22
|
+
|
|
23
|
+
|
|
12
24
|
def get_orthogonal_c_slab(material: Union[Material, MaterialWithBuildMetadata]) -> Material:
|
|
13
25
|
"""
|
|
14
26
|
Make the c-vector orthogonal to the ab plane and update the basis.
|
|
@@ -4,10 +4,10 @@ from mat3ra.made.material import Material
|
|
|
4
4
|
from mat3ra.made.tools.build_components.entities.reusable.base_builder import BaseSingleBuilder, TypeConfiguration
|
|
5
5
|
|
|
6
6
|
from ......analyze.lattice_lines import CrystalLatticeLinesMaterialAnalyzer
|
|
7
|
+
from ......build.pristine_structures.two_dimensional.slab.utils import get_orthogonal_c_slab
|
|
7
8
|
from ......modify import translate_to_z_level
|
|
8
9
|
from ......operations.core.unary import supercell
|
|
9
10
|
from ..... import MaterialWithBuildMetadata
|
|
10
|
-
from .....utils import get_orthogonal_c_slab
|
|
11
11
|
from .configuration import CrystalLatticeLinesConfiguration
|
|
12
12
|
|
|
13
13
|
|
|
@@ -2,7 +2,7 @@ from enum import Enum
|
|
|
2
2
|
from typing import Tuple
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class EdgeTypesEnum(str, Enum):
|
|
6
6
|
"""
|
|
7
7
|
Enum for nanoribbon/nanotape edge types.
|
|
8
8
|
"""
|
|
@@ -11,7 +11,7 @@ class EdgeTypes(str, Enum):
|
|
|
11
11
|
armchair = "armchair"
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def get_miller_indices_from_edge_type(edge_type:
|
|
14
|
+
def get_miller_indices_from_edge_type(edge_type: EdgeTypesEnum) -> Tuple[int, int]:
|
|
15
15
|
"""
|
|
16
16
|
Convert edge type shorthand to (u,v) Miller indices.
|
|
17
17
|
|
|
@@ -21,9 +21,9 @@ def get_miller_indices_from_edge_type(edge_type: EdgeTypes) -> Tuple[int, int]:
|
|
|
21
21
|
Returns:
|
|
22
22
|
Tuple of (u,v) Miller indices.
|
|
23
23
|
"""
|
|
24
|
-
if edge_type ==
|
|
24
|
+
if edge_type == EdgeTypesEnum.zigzag:
|
|
25
25
|
return (1, 1)
|
|
26
|
-
elif edge_type ==
|
|
26
|
+
elif edge_type == EdgeTypesEnum.armchair:
|
|
27
27
|
return (0, 1)
|
|
28
28
|
else:
|
|
29
29
|
raise ValueError(f"Unknown edge type: {edge_type}. Use 'zigzag' or 'armchair'.")
|
|
@@ -3,16 +3,16 @@ from typing import Optional, Tuple, Union
|
|
|
3
3
|
from mat3ra.made.material import Material
|
|
4
4
|
|
|
5
5
|
from ......analyze.lattice_lines import CrystalLatticeLinesMaterialAnalyzer
|
|
6
|
+
from ......build.pristine_structures.two_dimensional.slab.termination_utils import select_slab_termination
|
|
6
7
|
from ..... import MaterialWithBuildMetadata
|
|
7
|
-
from .....utils import select_slab_termination
|
|
8
8
|
from ..crystal_lattice_lines_unique_repeated.configuration import CrystalLatticeLinesUniqueRepeatedConfiguration
|
|
9
|
-
from .edge_types import
|
|
9
|
+
from .edge_types import EdgeTypesEnum, get_miller_indices_from_edge_type
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def create_lattice_lines_config_and_material(
|
|
13
13
|
material: Union[Material, MaterialWithBuildMetadata],
|
|
14
14
|
miller_indices_2d: Optional[Tuple[int, int]],
|
|
15
|
-
edge_type: Optional[
|
|
15
|
+
edge_type: Optional[EdgeTypesEnum],
|
|
16
16
|
width: int,
|
|
17
17
|
length: int,
|
|
18
18
|
termination_formula: Optional[str] = None,
|
|
@@ -15,7 +15,6 @@ class StackConfiguration(StackSchema, BaseConfigurationPydantic):
|
|
|
15
15
|
direction: AxisEnum = AxisEnum.z
|
|
16
16
|
|
|
17
17
|
def __init__(self, **data):
|
|
18
|
-
# Convert gaps to ArrayWithIds if not already
|
|
19
18
|
gaps = data.get("gaps", [])
|
|
20
19
|
if not isinstance(gaps, ArrayWithIds):
|
|
21
20
|
data["gaps"] = ArrayWithIds.from_values(gaps)
|
|
@@ -1,67 +1,12 @@
|
|
|
1
|
-
from typing import List,
|
|
1
|
+
from typing import List, Union
|
|
2
2
|
|
|
3
|
-
import numpy as np
|
|
4
|
-
from mat3ra.made.lattice import Lattice
|
|
5
3
|
from mat3ra.made.material import Material
|
|
6
4
|
|
|
7
|
-
from ..modify import filter_by_box
|
|
8
|
-
from ..operations.core.unary import edit_cell
|
|
5
|
+
from ..modify import filter_by_box
|
|
9
6
|
from . import MaterialWithBuildMetadata
|
|
10
|
-
from .entities.auxiliary.two_dimensional.termination import Termination
|
|
11
7
|
from .entities.reusable.three_dimensional.supercell.helpers import create_supercell
|
|
12
8
|
|
|
13
9
|
|
|
14
|
-
def select_slab_termination(terminations: List[Termination], formula: Optional[str] = None) -> Termination:
|
|
15
|
-
if not terminations:
|
|
16
|
-
raise ValueError("No terminations available.")
|
|
17
|
-
if formula is None:
|
|
18
|
-
return terminations[0]
|
|
19
|
-
for termination in terminations:
|
|
20
|
-
if termination.formula == formula:
|
|
21
|
-
return termination
|
|
22
|
-
raise ValueError(f"Termination with formula {formula} not found in available terminations: {terminations}")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def get_orthogonal_c_slab(material: Union[Material, MaterialWithBuildMetadata]) -> Material:
|
|
26
|
-
"""
|
|
27
|
-
Make the c-vector orthogonal to the ab plane and update the basis.
|
|
28
|
-
|
|
29
|
-
This function calculates a new c-vector that is orthogonal to the a and b vectors
|
|
30
|
-
of the lattice. It then computes the transformation matrix between the old and new
|
|
31
|
-
lattice vectors and applies this transformation to the atomic coordinates.
|
|
32
|
-
|
|
33
|
-
A new material is returned with an updated lattice and basis, where the new
|
|
34
|
-
lattice is defined by its parameters (a, b, c, alpha, beta, gamma) to avoid
|
|
35
|
-
storing raw vectors, preserving a standard representation.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
material (Material): The input material object.
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Material: A new material object with an orthogonalized c-vector and
|
|
42
|
-
updated basis.
|
|
43
|
-
"""
|
|
44
|
-
new_material = material.clone()
|
|
45
|
-
current_vectors = np.array(new_material.lattice.vector_arrays)
|
|
46
|
-
a_vec, b_vec, c_old_vec = current_vectors
|
|
47
|
-
|
|
48
|
-
normal = np.cross(a_vec, b_vec)
|
|
49
|
-
n_hat = normal / np.linalg.norm(normal)
|
|
50
|
-
height = float(np.dot(c_old_vec, n_hat))
|
|
51
|
-
c_new_vec = n_hat * height
|
|
52
|
-
|
|
53
|
-
new_vectors = np.array([a_vec, b_vec, c_new_vec])
|
|
54
|
-
transform_matrix = np.dot(current_vectors, np.linalg.inv(new_vectors))
|
|
55
|
-
|
|
56
|
-
new_basis = new_material.basis.clone()
|
|
57
|
-
new_basis.transform_by_matrix(transform_matrix)
|
|
58
|
-
new_lattice_from_vectors = Lattice.from_vectors_array(new_vectors.tolist())
|
|
59
|
-
new_material = edit_cell(new_material, new_lattice_from_vectors.vector_arrays)
|
|
60
|
-
new_material.basis = new_basis
|
|
61
|
-
new_material = wrap_to_unit_cell(new_material)
|
|
62
|
-
return new_material
|
|
63
|
-
|
|
64
|
-
|
|
65
10
|
def double_and_filter_material(
|
|
66
11
|
material: Union[Material, MaterialWithBuildMetadata], start: List[float], end: List[float]
|
|
67
12
|
) -> Material:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from mat3ra.esse.models.core.reusable.coordinate_conditions import BoxCoordinateConditionSchema
|
|
3
4
|
from pydantic import Field
|
|
4
5
|
|
|
5
6
|
from .coordinate_condition import CoordinateCondition
|
|
6
7
|
from .coordinate_functions import is_coordinate_in_box
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class BoxCoordinateCondition(CoordinateCondition):
|
|
10
|
+
class BoxCoordinateCondition(BoxCoordinateConditionSchema, CoordinateCondition):
|
|
10
11
|
min_coordinate: List[float] = Field(default_factory=lambda: [0, 0, 0])
|
|
11
12
|
max_coordinate: List[float] = Field(default_factory=lambda: [1, 1, 1])
|
|
12
13
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from mat3ra.esse.models.core.reusable.coordinate_conditions import CylinderCoordinateConditionSchema
|
|
3
4
|
from pydantic import Field
|
|
4
5
|
|
|
5
6
|
from .coordinate_condition import CoordinateCondition
|
|
6
7
|
from .coordinate_functions import is_coordinate_in_cylinder
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class CylinderCoordinateCondition(CoordinateCondition):
|
|
10
|
+
class CylinderCoordinateCondition(CylinderCoordinateConditionSchema, CoordinateCondition):
|
|
10
11
|
center_position: List[float] = Field(default_factory=lambda: [0.5, 0.5])
|
|
11
12
|
radius: float = 0.25
|
|
12
13
|
min_z: float = 0
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from mat3ra.esse.models.core.reusable.coordinate_conditions import PlaneCoordinateConditionSchema
|
|
4
|
+
|
|
3
5
|
from .coordinate_condition import CoordinateCondition
|
|
4
6
|
from .coordinate_functions import is_coordinate_behind_plane
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
class PlaneCoordinateCondition(CoordinateCondition):
|
|
9
|
+
class PlaneCoordinateCondition(PlaneCoordinateConditionSchema, CoordinateCondition):
|
|
8
10
|
plane_normal: List[float]
|
|
9
11
|
plane_point_coordinate: List[float]
|
|
10
12
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from mat3ra.esse.models.core.reusable.coordinate_conditions import SphereCoordinateConditionSchema
|
|
3
4
|
from pydantic import Field
|
|
4
5
|
|
|
5
6
|
from .coordinate_condition import CoordinateCondition
|
|
6
7
|
from .coordinate_functions import is_coordinate_in_sphere
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class SphereCoordinateCondition(CoordinateCondition):
|
|
10
|
+
class SphereCoordinateCondition(SphereCoordinateConditionSchema, CoordinateCondition):
|
|
10
11
|
center_coordinate: List[float] = Field(default_factory=lambda: [0.5, 0.5, 0.5])
|
|
11
12
|
radius: float = 0.25
|
|
12
13
|
|
package/src/py/mat3ra/made/tools/entities/coordinate/triangular_prism_coordinate_condition.py
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
+
from mat3ra.esse.models.core.reusable.coordinate_conditions import TriangularPrismCoordinateConditionSchema
|
|
4
|
+
|
|
3
5
|
from .coordinate_condition import CoordinateCondition
|
|
4
6
|
from .coordinate_functions import is_coordinate_in_triangular_prism
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
class TriangularPrismCoordinateCondition(CoordinateCondition):
|
|
9
|
+
class TriangularPrismCoordinateCondition(TriangularPrismCoordinateConditionSchema, CoordinateCondition):
|
|
8
10
|
position_on_surface_1: List[float] = [0, 0]
|
|
9
11
|
position_on_surface_2: List[float] = [1, 0]
|
|
10
12
|
position_on_surface_3: List[float] = [0, 1]
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# Defective Structures
|
|
2
|
+
from .build.compound_pristine_structures.two_dimensional.heterostructure import create_heterostructure
|
|
3
|
+
from .build.compound_pristine_structures.two_dimensional.heterostructure.types import StackComponentDict
|
|
4
|
+
|
|
1
5
|
# Compound Pristine Structures
|
|
2
6
|
from .build.compound_pristine_structures.two_dimensional.interface.base.helpers import (
|
|
3
7
|
create_interface_simple,
|
|
@@ -21,6 +25,7 @@ from .build.defective_structures.two_dimensional.adatom.helpers import (
|
|
|
21
25
|
create_multiple_adatom_defects,
|
|
22
26
|
get_adatom_defect_analyzer_cls,
|
|
23
27
|
)
|
|
28
|
+
from .build.defective_structures.two_dimensional.adatom.types import AdatomDefectDict
|
|
24
29
|
from .build.defective_structures.two_dimensional.grain_boundary_planar.helpers import create_grain_boundary_planar
|
|
25
30
|
from .build.defective_structures.two_dimensional.island.helpers import create_defect_island, get_coordinate_condition
|
|
26
31
|
from .build.defective_structures.two_dimensional.terrace.helpers import create_defect_terrace
|
|
@@ -32,6 +37,7 @@ from .build.defective_structures.zero_dimensional.point_defect.interstitial.help
|
|
|
32
37
|
from .build.defective_structures.zero_dimensional.point_defect.substitutional.helpers import (
|
|
33
38
|
create_defect_point_substitution,
|
|
34
39
|
)
|
|
40
|
+
from .build.defective_structures.zero_dimensional.point_defect.types import PointDefectDict
|
|
35
41
|
from .build.defective_structures.zero_dimensional.point_defect.vacancy.helpers import create_defect_point_vacancy
|
|
36
42
|
|
|
37
43
|
# Pristine Structures
|
|
@@ -51,6 +57,9 @@ from .build.pristine_structures.zero_dimensional.nanoparticle.helpers import (
|
|
|
51
57
|
create_nanoparticle_from_material,
|
|
52
58
|
)
|
|
53
59
|
|
|
60
|
+
# Enums
|
|
61
|
+
from .build.processed_structures.two_dimensional.passivation.enums import SurfaceTypesEnum
|
|
62
|
+
|
|
54
63
|
# Processed Structures
|
|
55
64
|
from .build.processed_structures.two_dimensional.passivation.helpers import (
|
|
56
65
|
get_coordination_numbers_distribution,
|
|
@@ -58,6 +67,7 @@ from .build.processed_structures.two_dimensional.passivation.helpers import (
|
|
|
58
67
|
passivate_dangling_bonds,
|
|
59
68
|
passivate_surface,
|
|
60
69
|
)
|
|
70
|
+
from .build_components.entities.reusable.one_dimensional.crystal_lattice_lines.edge_types import EdgeTypesEnum
|
|
61
71
|
from .build_components.entities.reusable.one_dimensional.crystal_lattice_lines.helpers import (
|
|
62
72
|
create_lattice_lines_config_and_material,
|
|
63
73
|
)
|
|
@@ -73,6 +83,9 @@ from .build_components.entities.reusable.two_dimensional.slab_stack.helpers impo
|
|
|
73
83
|
)
|
|
74
84
|
from .build_components.operations.core.modifications.perturb.helpers import create_perturbation
|
|
75
85
|
|
|
86
|
+
# Entities
|
|
87
|
+
from .entities.coordinate import CoordinateCondition
|
|
88
|
+
|
|
76
89
|
__all__ = [
|
|
77
90
|
# Slab and related Functions
|
|
78
91
|
"create_slab",
|
|
@@ -84,6 +97,8 @@ __all__ = [
|
|
|
84
97
|
"create_supercell",
|
|
85
98
|
"create_monolayer",
|
|
86
99
|
"create_perturbation",
|
|
100
|
+
# heterostructure Functions
|
|
101
|
+
"create_heterostructure",
|
|
87
102
|
"create_lattice_lines_config_and_material",
|
|
88
103
|
# Nanostructure Functions
|
|
89
104
|
"create_nanoparticle_from_material",
|
|
@@ -125,6 +140,15 @@ __all__ = [
|
|
|
125
140
|
"get_coordination_numbers_distribution",
|
|
126
141
|
# Utility Functions
|
|
127
142
|
"get_optimal_film_displacement",
|
|
143
|
+
# Type Definitions
|
|
144
|
+
"AdatomDefectDict",
|
|
145
|
+
"PointDefectDict",
|
|
146
|
+
"StackComponentDict",
|
|
147
|
+
# Entities
|
|
148
|
+
"CoordinateCondition",
|
|
149
|
+
# Enums
|
|
150
|
+
"EdgeTypesEnum",
|
|
151
|
+
"SurfaceTypesEnum",
|
|
128
152
|
]
|
|
129
153
|
|
|
130
154
|
# Aliases
|
|
@@ -5,7 +5,7 @@ from mat3ra.made.material import Material
|
|
|
5
5
|
from mat3ra.made.utils import get_atomic_coordinates_extremum
|
|
6
6
|
|
|
7
7
|
from .analyze.other import get_atom_indices_with_condition_on_coordinates, get_atom_indices_within_radius_pbc
|
|
8
|
-
from .build_components import MaterialWithBuildMetadata
|
|
8
|
+
from .build_components.metadata import MaterialWithBuildMetadata
|
|
9
9
|
from .convert import from_ase, to_ase
|
|
10
10
|
from .convert.interface_parts_enum import InterfacePartsEnum
|
|
11
11
|
from .entities.coordinate import (
|
|
@@ -5,7 +5,7 @@ from mat3ra.code.vector import Vector3D
|
|
|
5
5
|
from mat3ra.esse.models.core.abstract.matrix_3x3 import Matrix3x3Schema
|
|
6
6
|
from mat3ra.made.material import Material
|
|
7
7
|
|
|
8
|
-
from ...build_components import MaterialWithBuildMetadata
|
|
8
|
+
from ...build_components.metadata import MaterialWithBuildMetadata
|
|
9
9
|
from ...build_components.operations.core.modifications.perturb import FunctionHolder
|
|
10
10
|
from ...convert import from_ase, to_ase
|
|
11
11
|
from ...modify import translate_by_vector, wrap_to_unit_cell
|
|
@@ -4,6 +4,11 @@ from mat3ra.standata.materials import Materials
|
|
|
4
4
|
|
|
5
5
|
BULK_SrTiO3 = Materials.get_by_name_first_match("SrTiO3")
|
|
6
6
|
BULK_GRAPHITE = Materials.get_by_name_first_match("Graphite")
|
|
7
|
+
BULK_SiO2 = Materials.get_by_name_first_match("SiO2")
|
|
8
|
+
BULK_Hf2O_MCL = Materials.get_by_name_first_match("Hafnium.*MCL")
|
|
9
|
+
BULK_TiN = Materials.get_by_name_first_match("TiN")
|
|
10
|
+
BULK_Ni_PRIMITIVE = Materials.get_by_name_first_match("Nickel")
|
|
11
|
+
BULK_GRAPHENE = Materials.get_by_name_first_match("Graphene")
|
|
7
12
|
|
|
8
13
|
BULK_Si_PRIMITIVE: Dict[str, Any] = {
|
|
9
14
|
"name": "Silicon FCC",
|
|
@@ -6,7 +6,7 @@ import pytest
|
|
|
6
6
|
from mat3ra.made.tools.analyze.interface import InterfaceAnalyzer
|
|
7
7
|
from mat3ra.made.tools.analyze.interface.commensurate import CommensurateLatticeInterfaceAnalyzer
|
|
8
8
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab import SlabConfiguration
|
|
9
|
-
from unit.fixtures.bulk import BULK_Ge_CONVENTIONAL, BULK_Si_CONVENTIONAL
|
|
9
|
+
from unit.fixtures.bulk import BULK_GRAPHENE, BULK_Ge_CONVENTIONAL, BULK_Si_CONVENTIONAL
|
|
10
10
|
|
|
11
11
|
from .fixtures.monolayer import GRAPHENE
|
|
12
12
|
from .utils import assert_two_entities_deep_almost_equal
|
|
@@ -35,6 +35,25 @@ EXPECTED_PROPERTIES_SI_GE_001: Final = SimpleNamespace(
|
|
|
35
35
|
TEST_CASES = [(SUBSTRATE_SI_001, FILM_GE_001, EXPECTED_PROPERTIES_SI_GE_001)]
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
SUBSTRATE_SI_111: Final = SimpleNamespace(
|
|
39
|
+
bulk_config=BULK_Si_CONVENTIONAL,
|
|
40
|
+
miller_indices=(1, 1, 1),
|
|
41
|
+
number_of_layers=2,
|
|
42
|
+
vacuum=0.0,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
FILM_GRAPHENE_001: Final = SimpleNamespace(
|
|
46
|
+
bulk_config=BULK_GRAPHENE,
|
|
47
|
+
miller_indices=(0, 0, 1),
|
|
48
|
+
number_of_layers=1,
|
|
49
|
+
vacuum=0.0,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
OPTIMAL_SUPERCELL_TEST_CASES = [
|
|
53
|
+
(SUBSTRATE_SI_111, FILM_GRAPHENE_001, 4, 4), # n, m
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
38
57
|
@pytest.mark.parametrize("substrate, film, expected", TEST_CASES)
|
|
39
58
|
def test_interface_analyzer(substrate, film, expected):
|
|
40
59
|
substrate_slab_config = SlabConfiguration.from_parameters(
|
|
@@ -143,3 +162,36 @@ def test_commensurate_analyzer_functionality(
|
|
|
143
162
|
# Test negative match ID
|
|
144
163
|
with pytest.raises(ValueError, match="Match ID .* out of range"):
|
|
145
164
|
analyzer.get_strained_configuration_by_match_id(-1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@pytest.mark.parametrize("substrate, film, expected_n, expected_m", OPTIMAL_SUPERCELL_TEST_CASES)
|
|
168
|
+
def test_optimal_supercell_functions(substrate, film, expected_n, expected_m):
|
|
169
|
+
"""Test the optimal supercell functions with Si/Ge fixtures."""
|
|
170
|
+
substrate_slab_config = SlabConfiguration.from_parameters(
|
|
171
|
+
substrate.bulk_config,
|
|
172
|
+
substrate.miller_indices,
|
|
173
|
+
substrate.number_of_layers,
|
|
174
|
+
vacuum=substrate.vacuum,
|
|
175
|
+
termination_top_formula=None,
|
|
176
|
+
termination_bottom_formula=None,
|
|
177
|
+
)
|
|
178
|
+
film_slab_config = SlabConfiguration.from_parameters(
|
|
179
|
+
film.bulk_config,
|
|
180
|
+
film.miller_indices,
|
|
181
|
+
film.number_of_layers,
|
|
182
|
+
vacuum=film.vacuum,
|
|
183
|
+
termination_top_formula=None,
|
|
184
|
+
termination_bottom_formula=None,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
analyzer = InterfaceAnalyzer(
|
|
188
|
+
substrate_slab_configuration=substrate_slab_config,
|
|
189
|
+
film_slab_configuration=film_slab_config,
|
|
190
|
+
optimize_film_supercell=True,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Test find_optimal_film_supercell
|
|
194
|
+
optimal_n, optimal_m = analyzer.find_optimal_film_supercell()
|
|
195
|
+
|
|
196
|
+
assert optimal_n == expected_n
|
|
197
|
+
assert optimal_m == expected_m
|
|
@@ -9,9 +9,8 @@ from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab_strained_s
|
|
|
9
9
|
SlabStrainedSupercellBuilder,
|
|
10
10
|
)
|
|
11
11
|
from mat3ra.made.tools.utils import supercell_matrix_2d_schema_to_list, unwrap
|
|
12
|
-
from mat3ra.standata.materials import Materials
|
|
13
12
|
from mat3ra.utils.matrix import convert_2x2_to_3x3
|
|
14
|
-
from unit.fixtures.bulk import BULK_Ge_CONVENTIONAL, BULK_Si_CONVENTIONAL
|
|
13
|
+
from unit.fixtures.bulk import BULK_Ge_CONVENTIONAL, BULK_Ni_PRIMITIVE, BULK_Si_CONVENTIONAL
|
|
15
14
|
|
|
16
15
|
from .fixtures.monolayer import GRAPHENE
|
|
17
16
|
from .utils import OSPlatform, get_platform_specific_value
|
|
@@ -49,7 +48,7 @@ EXPECTED_PROPERTIES_SI_GE_001: Final = SimpleNamespace(
|
|
|
49
48
|
),
|
|
50
49
|
(
|
|
51
50
|
SimpleNamespace(
|
|
52
|
-
bulk_config=
|
|
51
|
+
bulk_config=BULK_Ni_PRIMITIVE,
|
|
53
52
|
miller_indices=(0, 0, 1),
|
|
54
53
|
number_of_layers=2,
|
|
55
54
|
vacuum=0.0,
|
|
@@ -123,7 +122,7 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
123
122
|
[
|
|
124
123
|
(
|
|
125
124
|
SimpleNamespace(
|
|
126
|
-
bulk_config=
|
|
125
|
+
bulk_config=BULK_Ni_PRIMITIVE,
|
|
127
126
|
miller_indices=(1, 1, 1),
|
|
128
127
|
number_of_layers=3,
|
|
129
128
|
vacuum=0.0,
|
|
@@ -5,6 +5,7 @@ from mat3ra.made.tools.build.defective_structures.two_dimensional.adatom.helpers
|
|
|
5
5
|
create_defect_adatom,
|
|
6
6
|
create_multiple_adatom_defects,
|
|
7
7
|
)
|
|
8
|
+
from mat3ra.made.tools.build.defective_structures.two_dimensional.adatom.types import AdatomDefectDict
|
|
8
9
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.helpers import create_slab
|
|
9
10
|
from mat3ra.made.tools.build_components.operations.core.combinations.enums import AdatomPlacementMethodEnum
|
|
10
11
|
from mat3ra.utils import assertion as assertion_utils
|
|
@@ -61,9 +62,9 @@ def test_create_adatom(
|
|
|
61
62
|
(
|
|
62
63
|
SI_CONVENTIONAL_SLAB_001,
|
|
63
64
|
[
|
|
64
|
-
{"element": "Si", "
|
|
65
|
-
{"element": "C", "
|
|
66
|
-
{"element": "N", "
|
|
65
|
+
{"element": "Si", "coordinate_2d": [0.5, 0.5], "distance_z": 2.0},
|
|
66
|
+
{"element": "C", "coordinate_2d": [0.25, 0.25], "distance_z": 1.5},
|
|
67
|
+
{"element": "N", "coordinate_2d": [0.75, 0.75], "distance_z": 1.0},
|
|
67
68
|
],
|
|
68
69
|
AdatomPlacementMethodEnum.EXACT_COORDINATE.value,
|
|
69
70
|
SLAB_Si_3_ADATOMS,
|
|
@@ -72,6 +73,12 @@ def test_create_adatom(
|
|
|
72
73
|
)
|
|
73
74
|
def test_create_multiple_adatom_defects(crystal_config, adatom_dicts, placement_method, expected_material_config):
|
|
74
75
|
slab = MaterialWithBuildMetadata.create(crystal_config)
|
|
75
|
-
|
|
76
|
+
|
|
77
|
+
defect_models = []
|
|
78
|
+
for adatom_data in adatom_dicts:
|
|
79
|
+
defect_dict = AdatomDefectDict(**adatom_data)
|
|
80
|
+
defect_models.append(defect_dict)
|
|
81
|
+
|
|
82
|
+
defects = create_multiple_adatom_defects(slab, defect_models, placement_method)
|
|
76
83
|
defects.metadata.build = []
|
|
77
84
|
assert_two_entities_deep_almost_equal(defects, expected_material_config)
|
|
@@ -14,6 +14,7 @@ from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.
|
|
|
14
14
|
create_defect_point_vacancy,
|
|
15
15
|
create_multiple_defects,
|
|
16
16
|
)
|
|
17
|
+
from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.types import PointDefectDict
|
|
17
18
|
from unit.fixtures.bulk import BULK_Si_CONVENTIONAL, BULK_Si_PRIMITIVE
|
|
18
19
|
from unit.fixtures.point_defects import (
|
|
19
20
|
INTERSTITIAL_DEFECT_BULK_PRIMITIVE_Si,
|
|
@@ -123,13 +124,15 @@ def test_create_multiple_defects(material_config, defect_params_list, expected_m
|
|
|
123
124
|
|
|
124
125
|
defect_dicts = []
|
|
125
126
|
for defect_params in defect_params_list:
|
|
126
|
-
|
|
127
|
+
defect_data = {
|
|
127
128
|
"type": defect_params.defect_type,
|
|
128
129
|
"coordinate": defect_params.coordinate,
|
|
129
130
|
"placement_method": defect_params.placement_method if hasattr(defect_params, "placement_method") else None,
|
|
130
131
|
"element": defect_params.element if hasattr(defect_params, "element") else None,
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
defect_dict = PointDefectDict(**defect_data)
|
|
135
|
+
|
|
133
136
|
defect_dicts.append(defect_dict)
|
|
134
137
|
|
|
135
138
|
defects = create_multiple_defects(
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from mat3ra.made.material import Material
|
|
5
|
+
from mat3ra.made.tools.helpers import StackComponentDict, create_heterostructure
|
|
6
|
+
|
|
7
|
+
from .fixtures.bulk import BULK_Hf2O_MCL, BULK_Si_CONVENTIONAL, BULK_SiO2, BULK_TiN
|
|
8
|
+
|
|
9
|
+
PRECISION = 1e-3
|
|
10
|
+
|
|
11
|
+
Si_SiO2_Hf2O_HETEROSTRUCTURE_TEST_CASE = (
|
|
12
|
+
[
|
|
13
|
+
SimpleNamespace(bulk_config=BULK_Si_CONVENTIONAL, miller_indices=(0, 0, 1), number_of_layers=4),
|
|
14
|
+
SimpleNamespace(bulk_config=BULK_SiO2, miller_indices=(1, 1, 1), number_of_layers=5),
|
|
15
|
+
SimpleNamespace(bulk_config=BULK_Hf2O_MCL, miller_indices=(0, 0, 1), number_of_layers=2),
|
|
16
|
+
SimpleNamespace(bulk_config=BULK_TiN, miller_indices=(1, 1, 1), number_of_layers=5),
|
|
17
|
+
],
|
|
18
|
+
[1.5, 1.0, 1.0], # gaps
|
|
19
|
+
10.0, # vacuum
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.parametrize("layers, gaps, vacuum", [Si_SiO2_Hf2O_HETEROSTRUCTURE_TEST_CASE])
|
|
24
|
+
def test_create_heterostructure_simple(layers, gaps, vacuum):
|
|
25
|
+
# Convert raw test data to Pydantic models
|
|
26
|
+
stack_components = []
|
|
27
|
+
for layer in layers:
|
|
28
|
+
component_data = {
|
|
29
|
+
"crystal": Material.create(layer.bulk_config),
|
|
30
|
+
"miller_indices": layer.miller_indices,
|
|
31
|
+
"thickness": layer.number_of_layers,
|
|
32
|
+
}
|
|
33
|
+
component = StackComponentDict(**component_data)
|
|
34
|
+
stack_components.append(component)
|
|
35
|
+
|
|
36
|
+
heterostructure = create_heterostructure(
|
|
37
|
+
stack_component_dicts=stack_components,
|
|
38
|
+
gaps=gaps,
|
|
39
|
+
vacuum=vacuum,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
assert isinstance(heterostructure, Material)
|
|
43
|
+
elements = set()
|
|
44
|
+
for layer in layers:
|
|
45
|
+
elements.update(Material.create(layer.bulk_config).basis.elements.values)
|
|
46
|
+
assert set(heterostructure.basis.elements.values) == elements
|
|
47
|
+
|
|
48
|
+
assert len(heterostructure.basis.elements.values) > 0
|
|
@@ -23,8 +23,7 @@ from mat3ra.made.tools.build.pristine_structures.two_dimensional.nanoribbon.help
|
|
|
23
23
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab import SlabBuilder, SlabConfiguration
|
|
24
24
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab.helpers import create_slab
|
|
25
25
|
from mat3ra.made.tools.helpers import create_interface_simple, create_interface_simple_between_slabs
|
|
26
|
-
from
|
|
27
|
-
from unit.fixtures.bulk import BULK_Ge_CONVENTIONAL, BULK_Si_CONVENTIONAL
|
|
26
|
+
from unit.fixtures.bulk import BULK_Ge_CONVENTIONAL, BULK_Ni_PRIMITIVE, BULK_Si_CONVENTIONAL
|
|
28
27
|
|
|
29
28
|
from .fixtures.interface.commensurate import INTERFACE_GRAPHENE_GRAPHENE_X, INTERFACE_GRAPHENE_GRAPHENE_Z
|
|
30
29
|
from .fixtures.interface.gr_ni_111_top_hcp import (
|
|
@@ -55,7 +54,7 @@ Si_Ge_SIMPLE_INTERFACE_TEST_CASE = (
|
|
|
55
54
|
|
|
56
55
|
GRAPHENE_NICKEL_TEST_CASE = (
|
|
57
56
|
SimpleNamespace(
|
|
58
|
-
bulk_config=
|
|
57
|
+
bulk_config=BULK_Ni_PRIMITIVE,
|
|
59
58
|
miller_indices=(1, 1, 1),
|
|
60
59
|
number_of_layers=3,
|
|
61
60
|
vacuum=0.0,
|
|
@@ -12,8 +12,8 @@ from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interf
|
|
|
12
12
|
)
|
|
13
13
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab import SlabBuilder, SlabConfiguration
|
|
14
14
|
from mat3ra.made.tools.build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
15
|
-
from mat3ra.standata.materials import Materials
|
|
16
15
|
|
|
16
|
+
from .fixtures.bulk import BULK_Ni_PRIMITIVE
|
|
17
17
|
from .fixtures.interface.gr_ni_111_top_hcp import (
|
|
18
18
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP,
|
|
19
19
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP_GH_WF,
|
|
@@ -23,7 +23,7 @@ from .utils import OSPlatform, assert_two_entities_deep_almost_equal, get_platfo
|
|
|
23
23
|
|
|
24
24
|
GRAPHENE_NICKEL_TEST_CASE = (
|
|
25
25
|
SimpleNamespace(
|
|
26
|
-
bulk_config=
|
|
26
|
+
bulk_config=BULK_Ni_PRIMITIVE,
|
|
27
27
|
miller_indices=(1, 1, 1),
|
|
28
28
|
number_of_layers=3,
|
|
29
29
|
vacuum=0.0,
|