@mat3ra/made 2025.8.9-0 → 2025.8.23-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/basis/__init__.py +9 -1
- package/src/py/mat3ra/made/material.py +2 -2
- package/src/py/mat3ra/made/tools/analyze/basis/__init__.py +4 -0
- package/src/py/mat3ra/made/tools/analyze/basis/analyzer.py +82 -0
- package/src/py/mat3ra/made/tools/analyze/basis/fingerprint.py +65 -0
- package/src/py/mat3ra/made/tools/analyze/interface/zsl.py +91 -22
- package/src/py/mat3ra/made/tools/analyze/lattice.py +44 -10
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/interface/base/builder.py +3 -2
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/interface/commensurate/helpers.py +3 -1
- package/tests/py/unit/fixtures/grain_boundary.py +312 -0
- package/tests/py/unit/fixtures/interface/zsl.py +216 -0
- package/tests/py/unit/test_material.py +27 -0
- package/tests/py/unit/test_tools_analyze.py +1 -0
- package/tests/py/unit/test_tools_analyze_basis.py +74 -0
- package/tests/py/unit/test_tools_analyze_interface_zsl.py +1 -4
- package/tests/py/unit/test_tools_build_grain_boundary.py +14 -6
- package/tests/py/unit/test_tools_build_interface_zsl.py +38 -4
package/package.json
CHANGED
|
@@ -184,9 +184,17 @@ class Basis(BasisSchema, InMemoryEntityPydantic):
|
|
|
184
184
|
self.coordinates.filter_by_ids(ids)
|
|
185
185
|
return self
|
|
186
186
|
|
|
187
|
-
def set_labels_from_list(self, labels: List[Union[int, str]]) -> None:
|
|
187
|
+
def set_labels_from_list(self, labels: Optional[List[Union[int, str]]]) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Set the labels of the basis from a list of labels (i. e. [1,1,1] for a 3-atom basis).
|
|
190
|
+
If None or [] are passed, the labels are removed (set to an empty array).
|
|
191
|
+
"""
|
|
188
192
|
num_atoms = len(self.elements.values)
|
|
189
193
|
|
|
194
|
+
if labels is None or len(labels) == 0:
|
|
195
|
+
self.labels = ArrayWithIds.from_values([])
|
|
196
|
+
return
|
|
197
|
+
|
|
190
198
|
if len(labels) != num_atoms:
|
|
191
199
|
raise ValueError(f"Number of labels ({len(labels)}) must match number of atoms ({num_atoms})")
|
|
192
200
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, List, Union
|
|
1
|
+
from typing import Any, List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from mat3ra.code.constants import AtomicCoordinateUnits, Units
|
|
4
4
|
from mat3ra.code.entity import HasDescriptionHasMetadataNamedDefaultableInMemoryEntityPydantic
|
|
@@ -103,7 +103,7 @@ class Material(MaterialSchema, HasDescriptionHasMetadataNamedDefaultableInMemory
|
|
|
103
103
|
def add_atom(self, element: str, coordinate: List[float], use_cartesian_coordinates: bool = False) -> None:
|
|
104
104
|
self.basis.add_atom(element, coordinate, use_cartesian_coordinates)
|
|
105
105
|
|
|
106
|
-
def set_labels_from_list(self, labels: List[Union[int, str]]) -> None:
|
|
106
|
+
def set_labels_from_list(self, labels: Optional[List[Union[int, str]]]) -> None:
|
|
107
107
|
self.basis.set_labels_from_list(labels)
|
|
108
108
|
|
|
109
109
|
def set_labels_from_value(self, value: Union[int, str]) -> None:
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
2
|
+
from mat3ra.made.utils import AXIS_TO_INDEX_MAP
|
|
3
|
+
|
|
4
|
+
from ...build_components.metadata import MaterialWithBuildMetadata
|
|
5
|
+
from .. import BaseMaterialAnalyzer
|
|
6
|
+
from .fingerprint import LayeredFingerprintAlongAxis, LayerFingerprint
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasisMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
10
|
+
def get_layer_fingerprint(
|
|
11
|
+
self, layer_thickness: float = 1.0, axis: AxisEnum = AxisEnum.z
|
|
12
|
+
) -> LayeredFingerprintAlongAxis:
|
|
13
|
+
"""
|
|
14
|
+
Create a fingerprint of the material by analyzing layers along the specified axis.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
layer_thickness: Thickness of each layer in Angstroms
|
|
18
|
+
axis: Axis along which to compute the fingerprint
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
LayeredFingerprintAlongAxis: Structured fingerprint with layer information
|
|
22
|
+
"""
|
|
23
|
+
material_cartesian = self.material.clone()
|
|
24
|
+
material_cartesian.to_cartesian()
|
|
25
|
+
|
|
26
|
+
coordinates = material_cartesian.basis.coordinates.values
|
|
27
|
+
elements = material_cartesian.basis.elements.values
|
|
28
|
+
|
|
29
|
+
axis_index = AXIS_TO_INDEX_MAP[axis.value]
|
|
30
|
+
axis_coords = [coord[axis_index] for coord in coordinates]
|
|
31
|
+
min_coord, max_coord = min(axis_coords), max(axis_coords)
|
|
32
|
+
|
|
33
|
+
fingerprint = LayeredFingerprintAlongAxis(axis=axis, layer_thickness=layer_thickness)
|
|
34
|
+
|
|
35
|
+
current_coord = min_coord
|
|
36
|
+
while current_coord < max_coord:
|
|
37
|
+
layer_min = current_coord
|
|
38
|
+
layer_max = current_coord + layer_thickness
|
|
39
|
+
|
|
40
|
+
layer_elements = []
|
|
41
|
+
for i, coord_val in enumerate(axis_coords):
|
|
42
|
+
if layer_min <= coord_val < layer_max:
|
|
43
|
+
layer_elements.append(elements[i])
|
|
44
|
+
|
|
45
|
+
unique_elements = sorted(list(set(layer_elements))) if layer_elements else []
|
|
46
|
+
layer = LayerFingerprint(min_coord=layer_min, max_coord=layer_max, elements=unique_elements)
|
|
47
|
+
fingerprint.layers.append(layer)
|
|
48
|
+
|
|
49
|
+
current_coord += layer_thickness
|
|
50
|
+
|
|
51
|
+
return fingerprint
|
|
52
|
+
|
|
53
|
+
def is_orientation_flipped(
|
|
54
|
+
self, original_material: MaterialWithBuildMetadata, layer_thickness: float = 1.0
|
|
55
|
+
) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Detect if the material orientation is flipped compared to the original.
|
|
58
|
+
Uses Jaccard similarity to compare fingerprints in normal and flipped orientations.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
original_material: The original material before primitivization
|
|
62
|
+
layer_thickness: Thickness of layers for fingerprint comparison
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: True if orientation is flipped, False otherwise
|
|
66
|
+
"""
|
|
67
|
+
original_analyzer = BasisMaterialAnalyzer(material=original_material)
|
|
68
|
+
original_fingerprint = original_analyzer.get_layer_fingerprint(layer_thickness)
|
|
69
|
+
current_fingerprint = self.get_layer_fingerprint(layer_thickness)
|
|
70
|
+
|
|
71
|
+
normal_score = original_fingerprint.get_similarity_score(current_fingerprint)
|
|
72
|
+
flipped_score = original_fingerprint.get_similarity_score(self._reverse_fingerprint(current_fingerprint))
|
|
73
|
+
|
|
74
|
+
# If flipped orientation has significantly higher similarity, material is flipped
|
|
75
|
+
threshold = 0.1
|
|
76
|
+
return flipped_score > normal_score + threshold
|
|
77
|
+
|
|
78
|
+
def _reverse_fingerprint(self, fingerprint: LayeredFingerprintAlongAxis) -> LayeredFingerprintAlongAxis:
|
|
79
|
+
reversed_layers = list(reversed(fingerprint.layers))
|
|
80
|
+
return LayeredFingerprintAlongAxis(
|
|
81
|
+
layers=reversed_layers, axis=fingerprint.axis, layer_thickness=fingerprint.layer_thickness
|
|
82
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class LayerFingerprint(BaseModel):
|
|
8
|
+
min_coord: float = Field(..., description="Minimum coordinate value for the layer")
|
|
9
|
+
max_coord: float = Field(..., description="Maximum coordinate value for the layer")
|
|
10
|
+
elements: List[str] = Field(default_factory=list, description="Sorted unique chemical elements in the layer")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LayeredFingerprintAlongAxis(BaseModel):
|
|
14
|
+
layers: List[LayerFingerprint] = Field(default_factory=list, description="List of layer fingerprints")
|
|
15
|
+
axis: AxisEnum = Field(default=AxisEnum.z, description="Axis along which the fingerprint is computed")
|
|
16
|
+
layer_thickness: float = Field(default=1.0, gt=0, description="Thickness of each layer in Angstroms")
|
|
17
|
+
|
|
18
|
+
def get_non_empty_layers(self) -> List[LayerFingerprint]:
|
|
19
|
+
return [layer for layer in self.layers if layer.elements]
|
|
20
|
+
|
|
21
|
+
def get_element_sequence(self) -> List[List[str]]:
|
|
22
|
+
return [layer.elements for layer in self.layers]
|
|
23
|
+
|
|
24
|
+
def get_non_empty_element_sequence(self) -> List[List[str]]:
|
|
25
|
+
return [layer.elements for layer in self.layers if layer.elements]
|
|
26
|
+
|
|
27
|
+
def get_similarity_score(self, other: "LayeredFingerprintAlongAxis") -> float:
|
|
28
|
+
"""
|
|
29
|
+
Calculate Jaccard similarity score between this and another fingerprint.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
other: Another LayeredFingerprintAlongAxis to compare with
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
float: Average Jaccard similarity score (0.0 to 1.0)
|
|
36
|
+
"""
|
|
37
|
+
if not self.layers or not other.layers:
|
|
38
|
+
return 0.0
|
|
39
|
+
|
|
40
|
+
min_length = min(len(self.layers), len(other.layers))
|
|
41
|
+
if min_length == 0:
|
|
42
|
+
return 0.0
|
|
43
|
+
|
|
44
|
+
total_score = 0.0
|
|
45
|
+
|
|
46
|
+
for i in range(min_length):
|
|
47
|
+
elements1 = self.layers[i].elements
|
|
48
|
+
elements2 = other.layers[i].elements
|
|
49
|
+
|
|
50
|
+
# Handle empty layers
|
|
51
|
+
if len(elements1) == 0 and len(elements2) == 0:
|
|
52
|
+
layer_score = 1.0
|
|
53
|
+
elif len(elements1) == 0 or len(elements2) == 0:
|
|
54
|
+
layer_score = 0.0
|
|
55
|
+
else:
|
|
56
|
+
# Calculate Jaccard similarity
|
|
57
|
+
set1 = set(elements1)
|
|
58
|
+
set2 = set(elements2)
|
|
59
|
+
intersection = len(set1.intersection(set2))
|
|
60
|
+
union = len(set1.union(set2))
|
|
61
|
+
layer_score = intersection / union if union > 0 else 0.0
|
|
62
|
+
|
|
63
|
+
total_score += layer_score
|
|
64
|
+
|
|
65
|
+
return total_score / min_length
|
|
@@ -84,38 +84,49 @@ class ZSLInterfaceAnalyzer(InterfaceAnalyzer):
|
|
|
84
84
|
continue
|
|
85
85
|
match_holders.append(match_holder)
|
|
86
86
|
|
|
87
|
+
match_holders = self.purge_equivalent_matches(match_holders)
|
|
88
|
+
|
|
87
89
|
# sort matches by strain in ascending order, then for each equal strain by area in ascending order
|
|
88
90
|
match_holders = self.sort_by_strain_then_area(match_holders)
|
|
89
91
|
|
|
90
92
|
return match_holders
|
|
91
93
|
|
|
94
|
+
def purge_equivalent_matches(self, match_holders: List[ZSLMatchHolder]) -> List[ZSLMatchHolder]:
|
|
95
|
+
unique_matches = []
|
|
96
|
+
seen_matches = set()
|
|
97
|
+
|
|
98
|
+
for holder in match_holders:
|
|
99
|
+
match_key = (
|
|
100
|
+
round(holder.match_area / self.math_precision) * self.math_precision,
|
|
101
|
+
round(holder.total_strain_percentage / self.math_precision) * self.math_precision,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if match_key not in seen_matches:
|
|
105
|
+
seen_matches.add(match_key)
|
|
106
|
+
unique_matches.append(holder)
|
|
107
|
+
|
|
108
|
+
return unique_matches
|
|
109
|
+
|
|
92
110
|
def convert_generated_match_to_match_holder(self, match_id: int, match_pymatgen) -> Optional[ZSLMatchHolder]:
|
|
93
111
|
film_slab_vectors = np.array(self.film_slab.lattice.vector_arrays[0:2])[:, :2]
|
|
94
112
|
substrate_slab_vectors = np.array(self.substrate_slab.lattice.vector_arrays[0:2])[:, :2]
|
|
95
113
|
|
|
96
114
|
pymatgen_film_sl_vectors = match_pymatgen.film_sl_vectors[:, :2]
|
|
97
|
-
pymatgen_film_sl_vectors = align_first_vector_to_x_2d_right_handed(pymatgen_film_sl_vectors)
|
|
98
|
-
|
|
99
115
|
pymatgen_substrate_sl_vectors = match_pymatgen.substrate_sl_vectors[:, :2]
|
|
100
|
-
pymatgen_substrate_sl_vectors = align_first_vector_to_x_2d_right_handed(pymatgen_substrate_sl_vectors)
|
|
101
116
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
film_sl_vectors, substrate_sl_vectors = self._get_sl_vectors_for_supercell_calculation(
|
|
118
|
+
pymatgen_film_sl_vectors, pymatgen_substrate_sl_vectors
|
|
119
|
+
)
|
|
120
|
+
if film_sl_vectors is None:
|
|
105
121
|
return None
|
|
106
122
|
|
|
107
|
-
film_supercell_matrix =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
# If possible, reduce supercell matrices by their GCD
|
|
115
|
-
g = get_global_gcd(film_supercell_matrix, substrate_supercell_matrix)
|
|
116
|
-
film_supercell_matrix = film_supercell_matrix // g
|
|
117
|
-
substrate_supercell_matrix = substrate_supercell_matrix // g
|
|
118
|
-
area = area / g**2
|
|
123
|
+
film_supercell_matrix, substrate_supercell_matrix, area = self._calculate_and_reduce_supercell_matrices(
|
|
124
|
+
film_slab_vectors,
|
|
125
|
+
substrate_slab_vectors,
|
|
126
|
+
film_sl_vectors,
|
|
127
|
+
substrate_sl_vectors,
|
|
128
|
+
match_pymatgen.match_area,
|
|
129
|
+
)
|
|
119
130
|
|
|
120
131
|
film_sl_supercell_vectors = film_supercell_matrix @ film_slab_vectors
|
|
121
132
|
substrate_sl_supercell_vectors = substrate_supercell_matrix @ substrate_slab_vectors
|
|
@@ -126,10 +137,9 @@ class ZSLInterfaceAnalyzer(InterfaceAnalyzer):
|
|
|
126
137
|
):
|
|
127
138
|
return None
|
|
128
139
|
|
|
129
|
-
real_strain_matrix =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
strain_percentage = self.calculate_total_strain_percentage(real_strain_matrix)
|
|
140
|
+
real_strain_matrix, strain_percentage = self._calculate_strain(
|
|
141
|
+
film_sl_supercell_vectors, substrate_sl_supercell_vectors
|
|
142
|
+
)
|
|
133
143
|
|
|
134
144
|
return ZSLMatchHolder(
|
|
135
145
|
match_id=match_id,
|
|
@@ -140,6 +150,65 @@ class ZSLInterfaceAnalyzer(InterfaceAnalyzer):
|
|
|
140
150
|
total_strain_percentage=strain_percentage,
|
|
141
151
|
)
|
|
142
152
|
|
|
153
|
+
def _get_sl_vectors_for_supercell_calculation(self, pymatgen_film_sl_vectors, pymatgen_substrate_sl_vectors):
|
|
154
|
+
"""
|
|
155
|
+
Determine which superlattice vectors to use for supercell matrix calculation.
|
|
156
|
+
|
|
157
|
+
Calculating the supercell transformation matrix requires that corresponding
|
|
158
|
+
superlattice vectors of film and substrate are colinear. Non-colinear vectors
|
|
159
|
+
cannot be used to derive a valid integer transformation matrix from original to supercell.
|
|
160
|
+
While diagonal matrices can be used, and can give smaller area match, so they should be preserved.
|
|
161
|
+
"""
|
|
162
|
+
pymatgen_substrate_sl_vectors_aligned = align_first_vector_to_x_2d_right_handed(pymatgen_substrate_sl_vectors)
|
|
163
|
+
pymatgen_film_sl_vectors_aligned = align_first_vector_to_x_2d_right_handed(pymatgen_film_sl_vectors)
|
|
164
|
+
|
|
165
|
+
a_colinear = are_vectors_colinear(pymatgen_film_sl_vectors_aligned[0], pymatgen_substrate_sl_vectors_aligned[0])
|
|
166
|
+
b_colinear = are_vectors_colinear(pymatgen_film_sl_vectors_aligned[1], pymatgen_substrate_sl_vectors_aligned[1])
|
|
167
|
+
|
|
168
|
+
if not (a_colinear and b_colinear):
|
|
169
|
+
return None, None
|
|
170
|
+
|
|
171
|
+
is_film_diag = np.allclose(pymatgen_film_sl_vectors_aligned, np.diag(np.diag(pymatgen_film_sl_vectors_aligned)))
|
|
172
|
+
is_substrate_diag = np.allclose(
|
|
173
|
+
pymatgen_substrate_sl_vectors_aligned, np.diag(np.diag(pymatgen_substrate_sl_vectors_aligned))
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if is_film_diag and is_substrate_diag:
|
|
177
|
+
return pymatgen_film_sl_vectors, pymatgen_substrate_sl_vectors
|
|
178
|
+
else:
|
|
179
|
+
return pymatgen_film_sl_vectors_aligned, pymatgen_substrate_sl_vectors_aligned
|
|
180
|
+
|
|
181
|
+
def _calculate_and_reduce_supercell_matrices(
|
|
182
|
+
self, film_slab_vectors, substrate_slab_vectors, film_sl_vectors, substrate_sl_vectors, match_area
|
|
183
|
+
):
|
|
184
|
+
film_supercell_matrix = np.rint(np.linalg.solve(film_slab_vectors, film_sl_vectors)).astype(int)
|
|
185
|
+
substrate_supercell_matrix = np.rint(np.linalg.solve(substrate_slab_vectors, substrate_sl_vectors)).astype(int)
|
|
186
|
+
|
|
187
|
+
area = match_area
|
|
188
|
+
if self.reduce_result_cell:
|
|
189
|
+
g = get_global_gcd(film_supercell_matrix, substrate_supercell_matrix)
|
|
190
|
+
film_supercell_matrix = film_supercell_matrix // g
|
|
191
|
+
substrate_supercell_matrix = substrate_supercell_matrix // g
|
|
192
|
+
area = area / g**2
|
|
193
|
+
|
|
194
|
+
return film_supercell_matrix, substrate_supercell_matrix, area
|
|
195
|
+
|
|
196
|
+
def _calculate_strain(self, film_sl_supercell_vectors, substrate_sl_supercell_vectors):
|
|
197
|
+
film_det = np.linalg.det(film_sl_supercell_vectors)
|
|
198
|
+
substrate_det = np.linalg.det(substrate_sl_supercell_vectors)
|
|
199
|
+
|
|
200
|
+
# Handle cases where lattices match but are oriented in opposite directions (e.g., 180° rotation).
|
|
201
|
+
# This keeps the right-handed system for the strain calculation.
|
|
202
|
+
# The strain calculation uses vector magnitudes, so we need consistent orientation to avoid artificial strain
|
|
203
|
+
if (film_det > 0) != (substrate_det > 0):
|
|
204
|
+
film_sl_supercell_vectors = film_sl_supercell_vectors.copy()
|
|
205
|
+
film_sl_supercell_vectors[1] = -film_sl_supercell_vectors[1]
|
|
206
|
+
|
|
207
|
+
real_strain_matrix = np.linalg.solve(film_sl_supercell_vectors, substrate_sl_supercell_vectors)
|
|
208
|
+
real_strain_matrix = convert_2x2_to_3x3(real_strain_matrix.tolist())
|
|
209
|
+
strain_percentage = self.calculate_total_strain_percentage(real_strain_matrix)
|
|
210
|
+
return real_strain_matrix, strain_percentage
|
|
211
|
+
|
|
143
212
|
def get_strained_configuration_by_match_id(self, match_id: int) -> MatchedSubstrateFilmConfigurationHolder:
|
|
144
213
|
match_holders = self.zsl_match_holders
|
|
145
214
|
if not match_holders:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from ..build_components.metadata import MaterialWithBuildMetadata
|
|
2
2
|
from ..convert import from_pymatgen, to_pymatgen
|
|
3
|
+
from ..operations.core.unary import rotate
|
|
3
4
|
from ..third_party import PymatgenSpacegroupAnalyzer
|
|
4
5
|
from . import BaseMaterialAnalyzer
|
|
6
|
+
from .basis import BasisMaterialAnalyzer
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class LatticeMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
@@ -18,6 +20,14 @@ class LatticeMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
|
18
20
|
from_pymatgen(self.spacegroup_analyzer.get_primitive_standard_structure())
|
|
19
21
|
)
|
|
20
22
|
|
|
23
|
+
@property
|
|
24
|
+
def material_with_primitive_lattice_standard(self) -> MaterialWithBuildMetadata:
|
|
25
|
+
"""
|
|
26
|
+
Get material with primitive lattice and standard orientation correction.
|
|
27
|
+
Uses default parameters: return_original_if_not_reduced=False, keep_orientation=True
|
|
28
|
+
"""
|
|
29
|
+
return self.get_material_with_primitive_lattice_standard()
|
|
30
|
+
|
|
21
31
|
@property
|
|
22
32
|
def material_with_conventional_lattice(self: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
23
33
|
"""
|
|
@@ -27,6 +37,38 @@ class LatticeMaterialAnalyzer(BaseMaterialAnalyzer):
|
|
|
27
37
|
from_pymatgen(self.spacegroup_analyzer.get_conventional_standard_structure())
|
|
28
38
|
)
|
|
29
39
|
|
|
40
|
+
def get_material_with_primitive_lattice_standard(
|
|
41
|
+
self, return_original_if_not_reduced: bool = False, keep_orientation: bool = True, layer_thickness: float = 1.0
|
|
42
|
+
) -> MaterialWithBuildMetadata:
|
|
43
|
+
"""
|
|
44
|
+
Get material with primitive lattice and optional orientation correction to be standardized.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
return_original_if_not_reduced: If True, return original material when no reduction occurs
|
|
48
|
+
keep_orientation: If True, correct orientation after primitive conversion
|
|
49
|
+
layer_thickness: Thickness of layers for orientation detection
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
MaterialWithBuildMetadata: Material with primitive lattice
|
|
53
|
+
"""
|
|
54
|
+
material_with_primitive_lattice = self.material_with_primitive_lattice
|
|
55
|
+
original_number_of_atoms = self.material.basis.number_of_atoms
|
|
56
|
+
primitive_structure_number_of_atoms = material_with_primitive_lattice.basis.number_of_atoms
|
|
57
|
+
|
|
58
|
+
if original_number_of_atoms == primitive_structure_number_of_atoms:
|
|
59
|
+
if return_original_if_not_reduced:
|
|
60
|
+
return self.material
|
|
61
|
+
|
|
62
|
+
if keep_orientation:
|
|
63
|
+
basis_analyzer = BasisMaterialAnalyzer(material=material_with_primitive_lattice)
|
|
64
|
+
if basis_analyzer.is_orientation_flipped(self.material, layer_thickness):
|
|
65
|
+
material_with_primitive_lattice = rotate(
|
|
66
|
+
material_with_primitive_lattice, axis=[1, 0, 0], angle=180, rotate_cell=False
|
|
67
|
+
)
|
|
68
|
+
print("Orientation corrected after primitive conversion.")
|
|
69
|
+
|
|
70
|
+
return material_with_primitive_lattice
|
|
71
|
+
|
|
30
72
|
|
|
31
73
|
def get_material_with_conventional_lattice(material: MaterialWithBuildMetadata) -> MaterialWithBuildMetadata:
|
|
32
74
|
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
@@ -34,15 +76,7 @@ def get_material_with_conventional_lattice(material: MaterialWithBuildMetadata)
|
|
|
34
76
|
|
|
35
77
|
|
|
36
78
|
def get_material_with_primitive_lattice(
|
|
37
|
-
material: MaterialWithBuildMetadata, return_original_if_not_reduced=False
|
|
79
|
+
material: MaterialWithBuildMetadata, return_original_if_not_reduced=False, keep_orientation=True
|
|
38
80
|
) -> MaterialWithBuildMetadata:
|
|
39
81
|
analyzer = LatticeMaterialAnalyzer(material=material)
|
|
40
|
-
|
|
41
|
-
original_number_of_atoms = material.basis.number_of_atoms
|
|
42
|
-
primitive_structure_number_of_atoms = material_with_primitive_lattice.basis.number_of_atoms
|
|
43
|
-
if original_number_of_atoms == primitive_structure_number_of_atoms:
|
|
44
|
-
# Not reduced, return original material if requested, to avoid unnecessary editions
|
|
45
|
-
if return_original_if_not_reduced:
|
|
46
|
-
return material
|
|
47
|
-
# Reduced, return the primitive structure
|
|
48
|
-
return material_with_primitive_lattice
|
|
82
|
+
return analyzer.get_material_with_primitive_lattice_standard(return_original_if_not_reduced, keep_orientation)
|
|
@@ -82,8 +82,9 @@ class InterfaceBuilder(StackNComponentsBuilder):
|
|
|
82
82
|
) -> MaterialWithBuildMetadata:
|
|
83
83
|
build_params = cast(InterfaceBuilderParameters, self.build_parameters)
|
|
84
84
|
if build_params.make_primitive:
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
primitive_material = get_material_with_primitive_lattice(
|
|
86
|
+
material, return_original_if_not_reduced=True, keep_orientation=True
|
|
87
|
+
)
|
|
87
88
|
|
|
88
89
|
if primitive_material != material:
|
|
89
90
|
if configuration is None:
|
|
@@ -5,6 +5,7 @@ from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
|
5
5
|
|
|
6
6
|
from mat3ra.made.material import Material
|
|
7
7
|
from .. import InterfaceBuilder, InterfaceConfiguration
|
|
8
|
+
from ......build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
8
9
|
from .....pristine_structures.two_dimensional.slab_strained_supercell.configuration import (
|
|
9
10
|
SlabStrainedSupercellConfiguration,
|
|
10
11
|
)
|
|
@@ -142,8 +143,9 @@ def create_interface_commensurate(
|
|
|
142
143
|
use_conventional_cell=use_conventional_cell,
|
|
143
144
|
)
|
|
144
145
|
|
|
146
|
+
vacuum_config = VacuumConfiguration(size=vacuum, crystal=None, direction=direction)
|
|
145
147
|
interface_config = InterfaceConfiguration(
|
|
146
|
-
stack_components=strained_configs,
|
|
148
|
+
stack_components=strained_configs + [vacuum_config],
|
|
147
149
|
gaps=ArrayWithIds.from_values([gap, gap]),
|
|
148
150
|
direction=direction,
|
|
149
151
|
)
|
|
@@ -1,5 +1,317 @@
|
|
|
1
1
|
GRAIN_BOUNDARY_SI_001_011 = {
|
|
2
2
|
"name": "Si(011)-Si(001), Grain Boundary",
|
|
3
|
+
"basis": {
|
|
4
|
+
"elements": [
|
|
5
|
+
{"id": 0, "value": "Si"},
|
|
6
|
+
{"id": 1, "value": "Si"},
|
|
7
|
+
{"id": 2, "value": "Si"},
|
|
8
|
+
{"id": 3, "value": "Si"},
|
|
9
|
+
{"id": 4, "value": "Si"},
|
|
10
|
+
{"id": 5, "value": "Si"},
|
|
11
|
+
{"id": 6, "value": "Si"},
|
|
12
|
+
{"id": 7, "value": "Si"},
|
|
13
|
+
{"id": 8, "value": "Si"},
|
|
14
|
+
{"id": 9, "value": "Si"},
|
|
15
|
+
{"id": 10, "value": "Si"},
|
|
16
|
+
{"id": 11, "value": "Si"},
|
|
17
|
+
{"id": 12, "value": "Si"},
|
|
18
|
+
{"id": 13, "value": "Si"},
|
|
19
|
+
{"id": 14, "value": "Si"},
|
|
20
|
+
{"id": 15, "value": "Si"},
|
|
21
|
+
{"id": 16, "value": "Si"},
|
|
22
|
+
{"id": 17, "value": "Si"},
|
|
23
|
+
{"id": 18, "value": "Si"},
|
|
24
|
+
{"id": 19, "value": "Si"},
|
|
25
|
+
{"id": 20, "value": "Si"},
|
|
26
|
+
{"id": 21, "value": "Si"},
|
|
27
|
+
{"id": 22, "value": "Si"},
|
|
28
|
+
{"id": 23, "value": "Si"},
|
|
29
|
+
{"id": 24, "value": "Si"},
|
|
30
|
+
{"id": 25, "value": "Si"},
|
|
31
|
+
{"id": 26, "value": "Si"},
|
|
32
|
+
{"id": 27, "value": "Si"},
|
|
33
|
+
{"id": 28, "value": "Si"},
|
|
34
|
+
{"id": 29, "value": "Si"},
|
|
35
|
+
{"id": 30, "value": "Si"},
|
|
36
|
+
{"id": 31, "value": "Si"},
|
|
37
|
+
{"id": 32, "value": "Si"},
|
|
38
|
+
{"id": 33, "value": "Si"},
|
|
39
|
+
{"id": 34, "value": "Si"},
|
|
40
|
+
{"id": 35, "value": "Si"},
|
|
41
|
+
{"id": 36, "value": "Si"},
|
|
42
|
+
{"id": 37, "value": "Si"},
|
|
43
|
+
{"id": 38, "value": "Si"},
|
|
44
|
+
{"id": 39, "value": "Si"},
|
|
45
|
+
{"id": 40, "value": "Si"},
|
|
46
|
+
{"id": 41, "value": "Si"},
|
|
47
|
+
{"id": 42, "value": "Si"},
|
|
48
|
+
{"id": 43, "value": "Si"},
|
|
49
|
+
{"id": 44, "value": "Si"},
|
|
50
|
+
{"id": 45, "value": "Si"},
|
|
51
|
+
{"id": 46, "value": "Si"},
|
|
52
|
+
{"id": 47, "value": "Si"},
|
|
53
|
+
{"id": 48, "value": "Si"},
|
|
54
|
+
{"id": 49, "value": "Si"},
|
|
55
|
+
{"id": 50, "value": "Si"},
|
|
56
|
+
{"id": 51, "value": "Si"},
|
|
57
|
+
{"id": 52, "value": "Si"},
|
|
58
|
+
{"id": 53, "value": "Si"},
|
|
59
|
+
{"id": 54, "value": "Si"},
|
|
60
|
+
{"id": 55, "value": "Si"},
|
|
61
|
+
{"id": 56, "value": "Si"},
|
|
62
|
+
{"id": 57, "value": "Si"},
|
|
63
|
+
{"id": 58, "value": "Si"},
|
|
64
|
+
{"id": 59, "value": "Si"},
|
|
65
|
+
{"id": 60, "value": "Si"},
|
|
66
|
+
{"id": 61, "value": "Si"},
|
|
67
|
+
{"id": 62, "value": "Si"},
|
|
68
|
+
{"id": 63, "value": "Si"},
|
|
69
|
+
{"id": 64, "value": "Si"},
|
|
70
|
+
{"id": 65, "value": "Si"},
|
|
71
|
+
{"id": 66, "value": "Si"},
|
|
72
|
+
{"id": 67, "value": "Si"},
|
|
73
|
+
{"id": 68, "value": "Si"},
|
|
74
|
+
{"id": 69, "value": "Si"},
|
|
75
|
+
{"id": 70, "value": "Si"},
|
|
76
|
+
{"id": 71, "value": "Si"},
|
|
77
|
+
{"id": 72, "value": "Si"},
|
|
78
|
+
{"id": 73, "value": "Si"},
|
|
79
|
+
{"id": 74, "value": "Si"},
|
|
80
|
+
{"id": 75, "value": "Si"},
|
|
81
|
+
{"id": 76, "value": "Si"},
|
|
82
|
+
{"id": 77, "value": "Si"},
|
|
83
|
+
{"id": 78, "value": "Si"},
|
|
84
|
+
{"id": 79, "value": "Si"},
|
|
85
|
+
{"id": 80, "value": "Si"},
|
|
86
|
+
{"id": 81, "value": "Si"},
|
|
87
|
+
{"id": 82, "value": "Si"},
|
|
88
|
+
{"id": 83, "value": "Si"},
|
|
89
|
+
{"id": 84, "value": "Si"},
|
|
90
|
+
{"id": 85, "value": "Si"},
|
|
91
|
+
{"id": 86, "value": "Si"},
|
|
92
|
+
{"id": 87, "value": "Si"},
|
|
93
|
+
{"id": 88, "value": "Si"},
|
|
94
|
+
{"id": 89, "value": "Si"},
|
|
95
|
+
{"id": 90, "value": "Si"},
|
|
96
|
+
{"id": 91, "value": "Si"},
|
|
97
|
+
{"id": 92, "value": "Si"},
|
|
98
|
+
{"id": 93, "value": "Si"},
|
|
99
|
+
{"id": 94, "value": "Si"},
|
|
100
|
+
{"id": 95, "value": "Si"},
|
|
101
|
+
],
|
|
102
|
+
"coordinates": [
|
|
103
|
+
{"id": 0, "value": [0.408723941, 0.857142857, 0.5]},
|
|
104
|
+
{"id": 1, "value": [0.272482809, 0.821428571, 0.25]},
|
|
105
|
+
{"id": 2, "value": [0.136241677, 0.785714286, 0.5]},
|
|
106
|
+
{"id": 3, "value": [5.45e-7, 0.75, 0.25]},
|
|
107
|
+
{"id": 4, "value": [0.136241677, 0.857142857, 0]},
|
|
108
|
+
{"id": 5, "value": [5.45e-7, 0.821428571, 0.75]},
|
|
109
|
+
{"id": 6, "value": [0.408723941, 0.785714286, 0]},
|
|
110
|
+
{"id": 7, "value": [0.272482809, 0.75, 0.75]},
|
|
111
|
+
{"id": 8, "value": [0.408723941, 0.714285714, 0.5]},
|
|
112
|
+
{"id": 9, "value": [0.272482809, 0.678571429, 0.25]},
|
|
113
|
+
{"id": 10, "value": [0.136241677, 0.642857143, 0.5]},
|
|
114
|
+
{"id": 11, "value": [5.45e-7, 0.607142857, 0.25]},
|
|
115
|
+
{"id": 12, "value": [0.136241677, 0.714285714, 0]},
|
|
116
|
+
{"id": 13, "value": [5.45e-7, 0.678571429, 0.75]},
|
|
117
|
+
{"id": 14, "value": [0.408723941, 0.642857143, 0]},
|
|
118
|
+
{"id": 15, "value": [0.272482809, 0.607142857, 0.75]},
|
|
119
|
+
{"id": 16, "value": [0.408723941, 0.571428571, 0.5]},
|
|
120
|
+
{"id": 17, "value": [0.272482809, 0.535714286, 0.25]},
|
|
121
|
+
{"id": 18, "value": [0.136241677, 0.5, 0.5]},
|
|
122
|
+
{"id": 19, "value": [5.45e-7, 0.464285714, 0.25]},
|
|
123
|
+
{"id": 20, "value": [0.136241677, 0.571428571, 0]},
|
|
124
|
+
{"id": 21, "value": [5.45e-7, 0.535714286, 0.75]},
|
|
125
|
+
{"id": 22, "value": [0.408723941, 0.5, 0]},
|
|
126
|
+
{"id": 23, "value": [0.272482809, 0.464285714, 0.75]},
|
|
127
|
+
{"id": 24, "value": [0.408723941, 0.428571429, 0.5]},
|
|
128
|
+
{"id": 25, "value": [0.272482809, 0.392857143, 0.25]},
|
|
129
|
+
{"id": 26, "value": [0.136241677, 0.357142857, 0.5]},
|
|
130
|
+
{"id": 27, "value": [5.45e-7, 0.321428571, 0.25]},
|
|
131
|
+
{"id": 28, "value": [0.136241677, 0.428571429, 0]},
|
|
132
|
+
{"id": 29, "value": [5.45e-7, 0.392857143, 0.75]},
|
|
133
|
+
{"id": 30, "value": [0.408723941, 0.357142857, 0]},
|
|
134
|
+
{"id": 31, "value": [0.272482809, 0.321428571, 0.75]},
|
|
135
|
+
{"id": 32, "value": [0.408723941, 0.285714286, 0.5]},
|
|
136
|
+
{"id": 33, "value": [0.272482809, 0.25, 0.25]},
|
|
137
|
+
{"id": 34, "value": [0.136241677, 0.214285714, 0.5]},
|
|
138
|
+
{"id": 35, "value": [5.45e-7, 0.178571429, 0.25]},
|
|
139
|
+
{"id": 36, "value": [0.136241677, 0.285714286, 0]},
|
|
140
|
+
{"id": 37, "value": [5.45e-7, 0.25, 0.75]},
|
|
141
|
+
{"id": 38, "value": [0.408723941, 0.214285714, 0]},
|
|
142
|
+
{"id": 39, "value": [0.272482809, 0.178571429, 0.75]},
|
|
143
|
+
{"id": 40, "value": [0.408723941, 0.142857143, 0.5]},
|
|
144
|
+
{"id": 41, "value": [0.272482809, 0.107142857, 0.25]},
|
|
145
|
+
{"id": 42, "value": [0.136241677, 0.071428571, 0.5]},
|
|
146
|
+
{"id": 43, "value": [5.45e-7, 0.035714286, 0.25]},
|
|
147
|
+
{"id": 44, "value": [0.136241677, 0.142857143, 0]},
|
|
148
|
+
{"id": 45, "value": [5.45e-7, 0.107142857, 0.75]},
|
|
149
|
+
{"id": 46, "value": [0.408723941, 0.071428571, 0]},
|
|
150
|
+
{"id": 47, "value": [0.272482809, 0.035714286, 0.75]},
|
|
151
|
+
{"id": 48, "value": [0.408723941, 0, 0.5]},
|
|
152
|
+
{"id": 49, "value": [0.272482809, 0.964285714, 0.25]},
|
|
153
|
+
{"id": 50, "value": [0.136241677, 0.928571429, 0.5]},
|
|
154
|
+
{"id": 51, "value": [5.45e-7, 0.892857143, 0.25]},
|
|
155
|
+
{"id": 52, "value": [0.136241677, 0, 0]},
|
|
156
|
+
{"id": 53, "value": [5.45e-7, 0.964285714, 0.75]},
|
|
157
|
+
{"id": 54, "value": [0.408723941, 0.928571429, 0]},
|
|
158
|
+
{"id": 55, "value": [0.272482809, 0.892857143, 0.75]},
|
|
159
|
+
{"id": 56, "value": [0.800699192, 0.676122483, 0.86571336]},
|
|
160
|
+
{"id": 57, "value": [0.800699192, 0.626122483, 0.11571336]},
|
|
161
|
+
{"id": 58, "value": [0.800699192, 0.776122483, 0.86571336]},
|
|
162
|
+
{"id": 59, "value": [0.800699192, 0.726122483, 0.11571336]},
|
|
163
|
+
{"id": 60, "value": [0.608025135, 0.726122483, 0.36571336]},
|
|
164
|
+
{"id": 61, "value": [0.608025135, 0.676122483, 0.61571336]},
|
|
165
|
+
{"id": 62, "value": [0.608025135, 0.626122483, 0.36571336]},
|
|
166
|
+
{"id": 63, "value": [0.608025135, 0.776122483, 0.61571336]},
|
|
167
|
+
{"id": 64, "value": [0.800699192, 0.476122483, 0.86571336]},
|
|
168
|
+
{"id": 65, "value": [0.800699192, 0.426122483, 0.11571336]},
|
|
169
|
+
{"id": 66, "value": [0.800699192, 0.576122483, 0.86571336]},
|
|
170
|
+
{"id": 67, "value": [0.800699192, 0.526122483, 0.11571336]},
|
|
171
|
+
{"id": 68, "value": [0.608025135, 0.526122483, 0.36571336]},
|
|
172
|
+
{"id": 69, "value": [0.608025135, 0.476122483, 0.61571336]},
|
|
173
|
+
{"id": 70, "value": [0.608025135, 0.426122483, 0.36571336]},
|
|
174
|
+
{"id": 71, "value": [0.608025135, 0.576122483, 0.61571336]},
|
|
175
|
+
{"id": 72, "value": [0.800699192, 0.276122483, 0.86571336]},
|
|
176
|
+
{"id": 73, "value": [0.800699192, 0.226122483, 0.11571336]},
|
|
177
|
+
{"id": 74, "value": [0.800699192, 0.376122483, 0.86571336]},
|
|
178
|
+
{"id": 75, "value": [0.800699192, 0.326122483, 0.11571336]},
|
|
179
|
+
{"id": 76, "value": [0.608025135, 0.326122483, 0.36571336]},
|
|
180
|
+
{"id": 77, "value": [0.608025135, 0.276122483, 0.61571336]},
|
|
181
|
+
{"id": 78, "value": [0.608025135, 0.226122483, 0.36571336]},
|
|
182
|
+
{"id": 79, "value": [0.608025135, 0.376122483, 0.61571336]},
|
|
183
|
+
{"id": 80, "value": [0.800699192, 0.076122483, 0.86571336]},
|
|
184
|
+
{"id": 81, "value": [0.800699192, 0.026122483, 0.11571336]},
|
|
185
|
+
{"id": 82, "value": [0.800699192, 0.176122483, 0.86571336]},
|
|
186
|
+
{"id": 83, "value": [0.800699192, 0.126122483, 0.11571336]},
|
|
187
|
+
{"id": 84, "value": [0.608025135, 0.126122483, 0.36571336]},
|
|
188
|
+
{"id": 85, "value": [0.608025135, 0.076122483, 0.61571336]},
|
|
189
|
+
{"id": 86, "value": [0.608025135, 0.026122483, 0.36571336]},
|
|
190
|
+
{"id": 87, "value": [0.608025135, 0.176122483, 0.61571336]},
|
|
191
|
+
{"id": 88, "value": [0.800699192, 0.876122483, 0.86571336]},
|
|
192
|
+
{"id": 89, "value": [0.800699192, 0.826122483, 0.11571336]},
|
|
193
|
+
{"id": 90, "value": [0.800699192, 0.976122483, 0.86571336]},
|
|
194
|
+
{"id": 91, "value": [0.800699192, 0.926122483, 0.11571336]},
|
|
195
|
+
{"id": 92, "value": [0.608025135, 0.926122483, 0.36571336]},
|
|
196
|
+
{"id": 93, "value": [0.608025135, 0.876122483, 0.61571336]},
|
|
197
|
+
{"id": 94, "value": [0.608025135, 0.826122483, 0.36571336]},
|
|
198
|
+
{"id": 95, "value": [0.608025135, 0.976122483, 0.61571336]},
|
|
199
|
+
],
|
|
200
|
+
"units": "crystal",
|
|
201
|
+
"labels": [
|
|
202
|
+
{"id": 0, "value": 0},
|
|
203
|
+
{"id": 1, "value": 0},
|
|
204
|
+
{"id": 2, "value": 0},
|
|
205
|
+
{"id": 3, "value": 0},
|
|
206
|
+
{"id": 4, "value": 0},
|
|
207
|
+
{"id": 5, "value": 0},
|
|
208
|
+
{"id": 6, "value": 0},
|
|
209
|
+
{"id": 7, "value": 0},
|
|
210
|
+
{"id": 8, "value": 0},
|
|
211
|
+
{"id": 9, "value": 0},
|
|
212
|
+
{"id": 10, "value": 0},
|
|
213
|
+
{"id": 11, "value": 0},
|
|
214
|
+
{"id": 12, "value": 0},
|
|
215
|
+
{"id": 13, "value": 0},
|
|
216
|
+
{"id": 14, "value": 0},
|
|
217
|
+
{"id": 15, "value": 0},
|
|
218
|
+
{"id": 16, "value": 0},
|
|
219
|
+
{"id": 17, "value": 0},
|
|
220
|
+
{"id": 18, "value": 0},
|
|
221
|
+
{"id": 19, "value": 0},
|
|
222
|
+
{"id": 20, "value": 0},
|
|
223
|
+
{"id": 21, "value": 0},
|
|
224
|
+
{"id": 22, "value": 0},
|
|
225
|
+
{"id": 23, "value": 0},
|
|
226
|
+
{"id": 24, "value": 0},
|
|
227
|
+
{"id": 25, "value": 0},
|
|
228
|
+
{"id": 26, "value": 0},
|
|
229
|
+
{"id": 27, "value": 0},
|
|
230
|
+
{"id": 28, "value": 0},
|
|
231
|
+
{"id": 29, "value": 0},
|
|
232
|
+
{"id": 30, "value": 0},
|
|
233
|
+
{"id": 31, "value": 0},
|
|
234
|
+
{"id": 32, "value": 0},
|
|
235
|
+
{"id": 33, "value": 0},
|
|
236
|
+
{"id": 34, "value": 0},
|
|
237
|
+
{"id": 35, "value": 0},
|
|
238
|
+
{"id": 36, "value": 0},
|
|
239
|
+
{"id": 37, "value": 0},
|
|
240
|
+
{"id": 38, "value": 0},
|
|
241
|
+
{"id": 39, "value": 0},
|
|
242
|
+
{"id": 40, "value": 0},
|
|
243
|
+
{"id": 41, "value": 0},
|
|
244
|
+
{"id": 42, "value": 0},
|
|
245
|
+
{"id": 43, "value": 0},
|
|
246
|
+
{"id": 44, "value": 0},
|
|
247
|
+
{"id": 45, "value": 0},
|
|
248
|
+
{"id": 46, "value": 0},
|
|
249
|
+
{"id": 47, "value": 0},
|
|
250
|
+
{"id": 48, "value": 0},
|
|
251
|
+
{"id": 49, "value": 0},
|
|
252
|
+
{"id": 50, "value": 0},
|
|
253
|
+
{"id": 51, "value": 0},
|
|
254
|
+
{"id": 52, "value": 0},
|
|
255
|
+
{"id": 53, "value": 0},
|
|
256
|
+
{"id": 54, "value": 0},
|
|
257
|
+
{"id": 55, "value": 0},
|
|
258
|
+
{"id": 56, "value": 1},
|
|
259
|
+
{"id": 57, "value": 1},
|
|
260
|
+
{"id": 58, "value": 1},
|
|
261
|
+
{"id": 59, "value": 1},
|
|
262
|
+
{"id": 60, "value": 1},
|
|
263
|
+
{"id": 61, "value": 1},
|
|
264
|
+
{"id": 62, "value": 1},
|
|
265
|
+
{"id": 63, "value": 1},
|
|
266
|
+
{"id": 64, "value": 1},
|
|
267
|
+
{"id": 65, "value": 1},
|
|
268
|
+
{"id": 66, "value": 1},
|
|
269
|
+
{"id": 67, "value": 1},
|
|
270
|
+
{"id": 68, "value": 1},
|
|
271
|
+
{"id": 69, "value": 1},
|
|
272
|
+
{"id": 70, "value": 1},
|
|
273
|
+
{"id": 71, "value": 1},
|
|
274
|
+
{"id": 72, "value": 1},
|
|
275
|
+
{"id": 73, "value": 1},
|
|
276
|
+
{"id": 74, "value": 1},
|
|
277
|
+
{"id": 75, "value": 1},
|
|
278
|
+
{"id": 76, "value": 1},
|
|
279
|
+
{"id": 77, "value": 1},
|
|
280
|
+
{"id": 78, "value": 1},
|
|
281
|
+
{"id": 79, "value": 1},
|
|
282
|
+
{"id": 80, "value": 1},
|
|
283
|
+
{"id": 81, "value": 1},
|
|
284
|
+
{"id": 82, "value": 1},
|
|
285
|
+
{"id": 83, "value": 1},
|
|
286
|
+
{"id": 84, "value": 1},
|
|
287
|
+
{"id": 85, "value": 1},
|
|
288
|
+
{"id": 86, "value": 1},
|
|
289
|
+
{"id": 87, "value": 1},
|
|
290
|
+
{"id": 88, "value": 1},
|
|
291
|
+
{"id": 89, "value": 1},
|
|
292
|
+
{"id": 90, "value": 1},
|
|
293
|
+
{"id": 91, "value": 1},
|
|
294
|
+
{"id": 92, "value": 1},
|
|
295
|
+
{"id": 93, "value": 1},
|
|
296
|
+
{"id": 94, "value": 1},
|
|
297
|
+
{"id": 95, "value": 1},
|
|
298
|
+
],
|
|
299
|
+
"constraints": [],
|
|
300
|
+
},
|
|
301
|
+
"lattice": {
|
|
302
|
+
"a": 10.03508222,
|
|
303
|
+
"b": 38.281346922,
|
|
304
|
+
"c": 5.468763846,
|
|
305
|
+
"alpha": 90,
|
|
306
|
+
"beta": 90,
|
|
307
|
+
"gamma": 90,
|
|
308
|
+
"units": {"length": "angstrom", "angle": "degree"},
|
|
309
|
+
"type": "TRI",
|
|
310
|
+
},
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
GRAIN_BOUNDARY_SI_001_011_GH = {
|
|
314
|
+
"name": "Si(011)-Si(001), Grain Boundary",
|
|
3
315
|
"basis": {
|
|
4
316
|
"elements": [
|
|
5
317
|
{"id": 0, "value": "Si"},
|
|
@@ -538,3 +538,219 @@ GRAPHENE_NICKEL_INTERFACE = {
|
|
|
538
538
|
"type": "TRI",
|
|
539
539
|
},
|
|
540
540
|
}
|
|
541
|
+
|
|
542
|
+
DIAMOND_GaAs_INTERFACE = {
|
|
543
|
+
"name": "AsGa(001)-C(001), Interface, Strain 7.114pct",
|
|
544
|
+
"basis": {
|
|
545
|
+
"elements": [
|
|
546
|
+
{"id": 0, "value": "Ga"},
|
|
547
|
+
{"id": 1, "value": "Ga"},
|
|
548
|
+
{"id": 2, "value": "Ga"},
|
|
549
|
+
{"id": 3, "value": "Ga"},
|
|
550
|
+
{"id": 4, "value": "As"},
|
|
551
|
+
{"id": 5, "value": "As"},
|
|
552
|
+
{"id": 6, "value": "As"},
|
|
553
|
+
{"id": 7, "value": "As"},
|
|
554
|
+
{"id": 8, "value": "C"},
|
|
555
|
+
{"id": 9, "value": "C"},
|
|
556
|
+
{"id": 10, "value": "C"},
|
|
557
|
+
{"id": 11, "value": "C"},
|
|
558
|
+
{"id": 12, "value": "C"},
|
|
559
|
+
{"id": 13, "value": "C"},
|
|
560
|
+
{"id": 14, "value": "C"},
|
|
561
|
+
{"id": 15, "value": "C"},
|
|
562
|
+
{"id": 16, "value": "C"},
|
|
563
|
+
{"id": 17, "value": "C"},
|
|
564
|
+
{"id": 18, "value": "C"},
|
|
565
|
+
{"id": 19, "value": "C"},
|
|
566
|
+
{"id": 20, "value": "C"},
|
|
567
|
+
{"id": 21, "value": "C"},
|
|
568
|
+
{"id": 22, "value": "C"},
|
|
569
|
+
{"id": 23, "value": "C"},
|
|
570
|
+
{"id": 24, "value": "C"},
|
|
571
|
+
{"id": 25, "value": "C"},
|
|
572
|
+
{"id": 26, "value": "C"},
|
|
573
|
+
{"id": 27, "value": "C"},
|
|
574
|
+
],
|
|
575
|
+
"coordinates": [
|
|
576
|
+
{"id": 0, "value": [0, 0, 0.208703663]},
|
|
577
|
+
{"id": 1, "value": [0.5, 0, 0.352579044]},
|
|
578
|
+
{"id": 2, "value": [0, 0.5, 0.352579044]},
|
|
579
|
+
{"id": 3, "value": [0.5, 0.5, 0.208703663]},
|
|
580
|
+
{"id": 4, "value": [0.75, 0.75, 0.280641354]},
|
|
581
|
+
{"id": 5, "value": [0.25, 0.75, 0.424516735]},
|
|
582
|
+
{"id": 6, "value": [0.75, 0.25, 0.424516735]},
|
|
583
|
+
{"id": 7, "value": [0.25, 0.25, 0.280641354]},
|
|
584
|
+
{"id": 8, "value": [0.1, 0.7, 0.133640341]},
|
|
585
|
+
{"id": 9, "value": [0.2, 0.9, 0.08909362]},
|
|
586
|
+
{"id": 10, "value": [0, 0, 0.044546899]},
|
|
587
|
+
{"id": 11, "value": [0.1, 0.2, 1.78e-7]},
|
|
588
|
+
{"id": 12, "value": [0.4, 0.8, 0.044546899]},
|
|
589
|
+
{"id": 13, "value": [0.9, 0.8, 1.78e-7]},
|
|
590
|
+
{"id": 14, "value": [0.3, 0.1, 0.133640341]},
|
|
591
|
+
{"id": 15, "value": [0.8, 0.1, 0.08909362]},
|
|
592
|
+
{"id": 16, "value": [0.9, 0.3, 0.133640341]},
|
|
593
|
+
{"id": 17, "value": [0, 0.5, 0.08909362]},
|
|
594
|
+
{"id": 18, "value": [0.8, 0.6, 0.044546899]},
|
|
595
|
+
{"id": 19, "value": [0.2, 0.4, 0.044546899]},
|
|
596
|
+
{"id": 20, "value": [0.7, 0.4, 1.78e-7]},
|
|
597
|
+
{"id": 21, "value": [0.6, 0.7, 0.08909362]},
|
|
598
|
+
{"id": 22, "value": [0.7, 0.9, 0.133640341]},
|
|
599
|
+
{"id": 23, "value": [0.6, 0.2, 0.044546899]},
|
|
600
|
+
{"id": 24, "value": [0.5, 0, 1.78e-7]},
|
|
601
|
+
{"id": 25, "value": [0.4, 0.3, 0.08909362]},
|
|
602
|
+
{"id": 26, "value": [0.5, 0.5, 0.133640341]},
|
|
603
|
+
{"id": 27, "value": [0.3, 0.6, 1.78e-7]},
|
|
604
|
+
],
|
|
605
|
+
"units": "crystal",
|
|
606
|
+
"labels": [
|
|
607
|
+
{"id": 0, "value": 1},
|
|
608
|
+
{"id": 1, "value": 1},
|
|
609
|
+
{"id": 2, "value": 1},
|
|
610
|
+
{"id": 3, "value": 1},
|
|
611
|
+
{"id": 4, "value": 1},
|
|
612
|
+
{"id": 5, "value": 1},
|
|
613
|
+
{"id": 6, "value": 1},
|
|
614
|
+
{"id": 7, "value": 1},
|
|
615
|
+
{"id": 8, "value": 0},
|
|
616
|
+
{"id": 9, "value": 0},
|
|
617
|
+
{"id": 10, "value": 0},
|
|
618
|
+
{"id": 11, "value": 0},
|
|
619
|
+
{"id": 12, "value": 0},
|
|
620
|
+
{"id": 13, "value": 0},
|
|
621
|
+
{"id": 14, "value": 0},
|
|
622
|
+
{"id": 15, "value": 0},
|
|
623
|
+
{"id": 16, "value": 0},
|
|
624
|
+
{"id": 17, "value": 0},
|
|
625
|
+
{"id": 18, "value": 0},
|
|
626
|
+
{"id": 19, "value": 0},
|
|
627
|
+
{"id": 20, "value": 0},
|
|
628
|
+
{"id": 21, "value": 0},
|
|
629
|
+
{"id": 22, "value": 0},
|
|
630
|
+
{"id": 23, "value": 0},
|
|
631
|
+
{"id": 24, "value": 0},
|
|
632
|
+
{"id": 25, "value": 0},
|
|
633
|
+
{"id": 26, "value": 0},
|
|
634
|
+
{"id": 27, "value": 0},
|
|
635
|
+
],
|
|
636
|
+
"constraints": [],
|
|
637
|
+
},
|
|
638
|
+
"lattice": {
|
|
639
|
+
"a": 5.630032184,
|
|
640
|
+
"b": 5.630032184,
|
|
641
|
+
"c": 19.983204895,
|
|
642
|
+
"alpha": 90,
|
|
643
|
+
"beta": 90,
|
|
644
|
+
"gamma": 90,
|
|
645
|
+
"units": {"length": "angstrom", "angle": "degree"},
|
|
646
|
+
"type": "TRI",
|
|
647
|
+
},
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
DIAMOND_GaAs_INTERFACE_GH = {
|
|
651
|
+
"name": "AsGa(001)-C(001), Interface, Strain 69.038pct",
|
|
652
|
+
"basis": {
|
|
653
|
+
"elements": [
|
|
654
|
+
{"id": 0, "value": "Ga"},
|
|
655
|
+
{"id": 1, "value": "Ga"},
|
|
656
|
+
{"id": 2, "value": "Ga"},
|
|
657
|
+
{"id": 3, "value": "Ga"},
|
|
658
|
+
{"id": 4, "value": "As"},
|
|
659
|
+
{"id": 5, "value": "As"},
|
|
660
|
+
{"id": 6, "value": "As"},
|
|
661
|
+
{"id": 7, "value": "As"},
|
|
662
|
+
{"id": 8, "value": "C"},
|
|
663
|
+
{"id": 9, "value": "C"},
|
|
664
|
+
{"id": 10, "value": "C"},
|
|
665
|
+
{"id": 11, "value": "C"},
|
|
666
|
+
{"id": 12, "value": "C"},
|
|
667
|
+
{"id": 13, "value": "C"},
|
|
668
|
+
{"id": 14, "value": "C"},
|
|
669
|
+
{"id": 15, "value": "C"},
|
|
670
|
+
{"id": 16, "value": "C"},
|
|
671
|
+
{"id": 17, "value": "C"},
|
|
672
|
+
{"id": 18, "value": "C"},
|
|
673
|
+
{"id": 19, "value": "C"},
|
|
674
|
+
{"id": 20, "value": "C"},
|
|
675
|
+
{"id": 21, "value": "C"},
|
|
676
|
+
{"id": 22, "value": "C"},
|
|
677
|
+
{"id": 23, "value": "C"},
|
|
678
|
+
{"id": 24, "value": "C"},
|
|
679
|
+
{"id": 25, "value": "C"},
|
|
680
|
+
{"id": 26, "value": "C"},
|
|
681
|
+
{"id": 27, "value": "C"},
|
|
682
|
+
],
|
|
683
|
+
"coordinates": [
|
|
684
|
+
{"id": 0, "value": [0.0, 0.0, 0.208703663]},
|
|
685
|
+
{"id": 1, "value": [0.5, 0.0, 0.352579044]},
|
|
686
|
+
{"id": 2, "value": [0.0, 0.5, 0.352579044]},
|
|
687
|
+
{"id": 3, "value": [0.5, 0.5, 0.208703663]},
|
|
688
|
+
{"id": 4, "value": [0.75, 0.25, 0.280641354]},
|
|
689
|
+
{"id": 5, "value": [0.25, 0.25, 0.424516735]},
|
|
690
|
+
{"id": 6, "value": [0.75, 0.75, 0.424516735]},
|
|
691
|
+
{"id": 7, "value": [0.25, 0.75, 0.280641354]},
|
|
692
|
+
{"id": 8, "value": [0.7, 0.9, 0.133640341]},
|
|
693
|
+
{"id": 9, "value": [0.8, 0.1, 0.08909362]},
|
|
694
|
+
{"id": 10, "value": [0.6, 0.2, 0.044546899]},
|
|
695
|
+
{"id": 11, "value": [0.7, 0.4, 1.78e-07]},
|
|
696
|
+
{"id": 12, "value": [0.0, 0.0, 0.044546899]},
|
|
697
|
+
{"id": 13, "value": [0.5, 0.0, 1.78e-07]},
|
|
698
|
+
{"id": 14, "value": [0.9, 0.3, 0.133640341]},
|
|
699
|
+
{"id": 15, "value": [0.4, 0.3, 0.08909362]},
|
|
700
|
+
{"id": 16, "value": [0.1, 0.7, 0.133640341]},
|
|
701
|
+
{"id": 17, "value": [0.2, 0.9, 0.08909362]},
|
|
702
|
+
{"id": 18, "value": [0.1, 0.2, 1.78e-07]},
|
|
703
|
+
{"id": 19, "value": [0.4, 0.8, 0.044546899]},
|
|
704
|
+
{"id": 20, "value": [0.9, 0.8, 1.78e-07]},
|
|
705
|
+
{"id": 21, "value": [0.3, 0.1, 0.133640341]},
|
|
706
|
+
{"id": 22, "value": [0.0, 0.5, 0.08909362]},
|
|
707
|
+
{"id": 23, "value": [0.8, 0.6, 0.044546899]},
|
|
708
|
+
{"id": 24, "value": [0.2, 0.4, 0.044546899]},
|
|
709
|
+
{"id": 25, "value": [0.6, 0.7, 0.08909362]},
|
|
710
|
+
{"id": 26, "value": [0.5, 0.5, 0.133640341]},
|
|
711
|
+
{"id": 27, "value": [0.3, 0.6, 1.78e-07]},
|
|
712
|
+
],
|
|
713
|
+
"units": "crystal",
|
|
714
|
+
"labels": [
|
|
715
|
+
{"id": 0, "value": 1},
|
|
716
|
+
{"id": 1, "value": 1},
|
|
717
|
+
{"id": 2, "value": 1},
|
|
718
|
+
{"id": 3, "value": 1},
|
|
719
|
+
{"id": 4, "value": 1},
|
|
720
|
+
{"id": 5, "value": 1},
|
|
721
|
+
{"id": 6, "value": 1},
|
|
722
|
+
{"id": 7, "value": 1},
|
|
723
|
+
{"id": 8, "value": 0},
|
|
724
|
+
{"id": 9, "value": 0},
|
|
725
|
+
{"id": 10, "value": 0},
|
|
726
|
+
{"id": 11, "value": 0},
|
|
727
|
+
{"id": 12, "value": 0},
|
|
728
|
+
{"id": 13, "value": 0},
|
|
729
|
+
{"id": 14, "value": 0},
|
|
730
|
+
{"id": 15, "value": 0},
|
|
731
|
+
{"id": 16, "value": 0},
|
|
732
|
+
{"id": 17, "value": 0},
|
|
733
|
+
{"id": 18, "value": 0},
|
|
734
|
+
{"id": 19, "value": 0},
|
|
735
|
+
{"id": 20, "value": 0},
|
|
736
|
+
{"id": 21, "value": 0},
|
|
737
|
+
{"id": 22, "value": 0},
|
|
738
|
+
{"id": 23, "value": 0},
|
|
739
|
+
{"id": 24, "value": 0},
|
|
740
|
+
{"id": 25, "value": 0},
|
|
741
|
+
{"id": 26, "value": 0},
|
|
742
|
+
{"id": 27, "value": 0},
|
|
743
|
+
],
|
|
744
|
+
"constraints": [],
|
|
745
|
+
},
|
|
746
|
+
"lattice": {
|
|
747
|
+
"a": 5.630032184,
|
|
748
|
+
"b": 5.630032184,
|
|
749
|
+
"c": 19.983204895,
|
|
750
|
+
"alpha": 90.0,
|
|
751
|
+
"beta": 90.0,
|
|
752
|
+
"gamma": 90.0,
|
|
753
|
+
"units": {"length": "angstrom", "angle": "degree"},
|
|
754
|
+
"type": "TRI",
|
|
755
|
+
},
|
|
756
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import pytest
|
|
2
3
|
from mat3ra.made.basis import Basis, Coordinates
|
|
3
4
|
from mat3ra.made.lattice import Lattice
|
|
4
5
|
from mat3ra.made.material import Material
|
|
5
6
|
from mat3ra.utils import assertion as assertion_utils
|
|
7
|
+
from unit.fixtures.bulk import BULK_Si_PRIMITIVE
|
|
6
8
|
from unit.fixtures.slab import BULK_Si_CONVENTIONAL
|
|
7
9
|
from unit.utils import assert_two_entities_deep_almost_equal
|
|
8
10
|
|
|
@@ -91,3 +93,28 @@ def test_basis_cell_lattice_sync():
|
|
|
91
93
|
assertion_utils.assert_deep_almost_equal(new_vectors, material.basis.cell.vector_arrays)
|
|
92
94
|
assertion_utils.assert_deep_almost_equal(new_vectors, material.lattice.vector_arrays)
|
|
93
95
|
# Verify basis coordinates are still correct
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize(
|
|
99
|
+
"initial_labels, reset_labels, expected_final",
|
|
100
|
+
[
|
|
101
|
+
# Test resetting with empty list
|
|
102
|
+
([1, 2], [], []),
|
|
103
|
+
# Test resetting with None
|
|
104
|
+
([1, 2], None, []),
|
|
105
|
+
# Test normal behavior with non-empty lists
|
|
106
|
+
([], [1, 2], [1, 2]),
|
|
107
|
+
],
|
|
108
|
+
)
|
|
109
|
+
def test_set_labels_from_list(initial_labels, reset_labels, expected_final):
|
|
110
|
+
material = Material.create(BULK_Si_PRIMITIVE)
|
|
111
|
+
|
|
112
|
+
if initial_labels:
|
|
113
|
+
material.basis.set_labels_from_list(initial_labels)
|
|
114
|
+
assert len(material.basis.labels.values) == len(initial_labels)
|
|
115
|
+
assert material.basis.labels.values == initial_labels
|
|
116
|
+
|
|
117
|
+
material.basis.set_labels_from_list(reset_labels)
|
|
118
|
+
|
|
119
|
+
assert len(material.basis.labels.values) == len(expected_final)
|
|
120
|
+
assert material.basis.labels.values == expected_final
|
|
@@ -97,6 +97,7 @@ def test_lattice_material_analyzer(
|
|
|
97
97
|
):
|
|
98
98
|
primitive_cell = Material.create(primitive_material_config)
|
|
99
99
|
lattice_material_analyzer = LatticeMaterialAnalyzer(material=primitive_cell)
|
|
100
|
+
|
|
100
101
|
conventional_cell = lattice_material_analyzer.material_with_conventional_lattice
|
|
101
102
|
assert_two_entities_deep_almost_equal(conventional_cell, expected_conventional_material_config)
|
|
102
103
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for basis material analysis functionality.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
7
|
+
from mat3ra.made.material import Material
|
|
8
|
+
from mat3ra.made.tools.analyze.basis import BasisMaterialAnalyzer, LayeredFingerprintAlongAxis
|
|
9
|
+
|
|
10
|
+
from .fixtures.bulk import BULK_Si_CONVENTIONAL
|
|
11
|
+
from .fixtures.slab import SLAB_SrTiO3_011_TERMINATION_O2, SLAB_SrTiO3_011_TERMINATION_SrTiO
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.mark.parametrize(
|
|
15
|
+
"original_material_config, another_material_config, is_flipped",
|
|
16
|
+
[
|
|
17
|
+
(SLAB_SrTiO3_011_TERMINATION_O2, SLAB_SrTiO3_011_TERMINATION_O2, False),
|
|
18
|
+
(SLAB_SrTiO3_011_TERMINATION_O2, SLAB_SrTiO3_011_TERMINATION_SrTiO, True),
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
def test_basis_analyzer_fingerprint(original_material_config, another_material_config, is_flipped):
|
|
22
|
+
original_material = Material.create(original_material_config)
|
|
23
|
+
another_material = Material.create(another_material_config)
|
|
24
|
+
analyzer = BasisMaterialAnalyzer(material=original_material)
|
|
25
|
+
|
|
26
|
+
fingerprint = analyzer.get_layer_fingerprint(layer_thickness=1.0)
|
|
27
|
+
assert isinstance(fingerprint, LayeredFingerprintAlongAxis)
|
|
28
|
+
assert len(fingerprint.layers) > 0
|
|
29
|
+
assert fingerprint.axis == AxisEnum.z
|
|
30
|
+
assert fingerprint.layer_thickness == 1.0
|
|
31
|
+
|
|
32
|
+
for layer in fingerprint.layers:
|
|
33
|
+
assert hasattr(layer, "min_coord")
|
|
34
|
+
assert hasattr(layer, "max_coord")
|
|
35
|
+
assert hasattr(layer, "elements")
|
|
36
|
+
assert isinstance(layer.min_coord, float)
|
|
37
|
+
assert isinstance(layer.max_coord, float)
|
|
38
|
+
assert layer.max_coord > layer.min_coord
|
|
39
|
+
assert isinstance(layer.elements, list)
|
|
40
|
+
assert all(isinstance(elem, str) for elem in layer.elements)
|
|
41
|
+
|
|
42
|
+
non_empty_layers = fingerprint.get_non_empty_layers()
|
|
43
|
+
assert isinstance(non_empty_layers, list)
|
|
44
|
+
assert all(isinstance(layer, type(fingerprint.layers[0])) for layer in non_empty_layers)
|
|
45
|
+
|
|
46
|
+
element_sequence = fingerprint.get_element_sequence()
|
|
47
|
+
assert isinstance(element_sequence, list)
|
|
48
|
+
assert all(isinstance(elements, list) for elements in element_sequence)
|
|
49
|
+
|
|
50
|
+
is_flipped = analyzer.is_orientation_flipped(another_material)
|
|
51
|
+
assert is_flipped == is_flipped, "Orientation flipped status does not match expected value."
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_fingerprint_similarity_score():
|
|
55
|
+
"""Test the get_similarity_score method specifically."""
|
|
56
|
+
material = Material.create(BULK_Si_CONVENTIONAL)
|
|
57
|
+
analyzer = BasisMaterialAnalyzer(material=material)
|
|
58
|
+
|
|
59
|
+
fingerprint = analyzer.get_layer_fingerprint(layer_thickness=1.0)
|
|
60
|
+
|
|
61
|
+
# Test self-similarity (should be 1.0)
|
|
62
|
+
self_score = fingerprint.get_similarity_score(fingerprint)
|
|
63
|
+
assert self_score == 1.0, "Self-similarity should be perfect"
|
|
64
|
+
|
|
65
|
+
# Test with different layer thickness (should be different)
|
|
66
|
+
different_fp = analyzer.get_layer_fingerprint(layer_thickness=2.0)
|
|
67
|
+
different_score = fingerprint.get_similarity_score(different_fp)
|
|
68
|
+
assert isinstance(different_score, float)
|
|
69
|
+
assert 0.0 <= different_score <= 1.0
|
|
70
|
+
|
|
71
|
+
# Test with empty fingerprint
|
|
72
|
+
empty_fp = LayeredFingerprintAlongAxis(layers=[], axis=AxisEnum.z, layer_thickness=1.0)
|
|
73
|
+
empty_score = fingerprint.get_similarity_score(empty_fp)
|
|
74
|
+
assert empty_score == 0.0, "Similarity with empty fingerprint should be 0"
|
|
@@ -110,9 +110,6 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
110
110
|
|
|
111
111
|
assert np.allclose(film_sl_vectors[0:2], substrate_sl_vectors[0:2], atol=1e-4)
|
|
112
112
|
|
|
113
|
-
assert np.allclose(substrate_material.lattice.vector_arrays[0:2], substrate_sl_vectors[0:2], atol=1e-4)
|
|
114
|
-
assert np.allclose(film_material.lattice.vector_arrays[0:2], film_sl_vectors[0:2], atol=1e-4)
|
|
115
|
-
|
|
116
113
|
assert np.isclose(substrate_material.lattice.a, film_material.lattice.a, atol=1e-4)
|
|
117
114
|
assert np.isclose(substrate_material.lattice.b, film_material.lattice.b, atol=1e-4)
|
|
118
115
|
|
|
@@ -134,7 +131,7 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
134
131
|
vacuum=0.0,
|
|
135
132
|
),
|
|
136
133
|
{"max_area": 90.0, "max_area_ratio_tol": 0.1, "max_length_tol": 0.1, "max_angle_tol": 0.1},
|
|
137
|
-
{OSPlatform.DARWIN:
|
|
134
|
+
{OSPlatform.DARWIN: 29, OSPlatform.OTHER: 29},
|
|
138
135
|
{
|
|
139
136
|
OSPlatform.DARWIN: {"strain_percentage": 0.474, "match_id": 0},
|
|
140
137
|
OSPlatform.OTHER: {"strain_percentage": 25.122, "match_id": 0},
|
|
@@ -15,9 +15,9 @@ from mat3ra.made.tools.build.defective_structures.two_dimensional.grain_boundary
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
from .fixtures.bulk import BULK_Si_CONVENTIONAL
|
|
18
|
-
from .fixtures.grain_boundary import GRAIN_BOUNDARY_LINEAR_SI, GRAIN_BOUNDARY_SI_001_011
|
|
18
|
+
from .fixtures.grain_boundary import GRAIN_BOUNDARY_LINEAR_SI, GRAIN_BOUNDARY_SI_001_011, GRAIN_BOUNDARY_SI_001_011_GH
|
|
19
19
|
from .fixtures.monolayer import GRAPHENE
|
|
20
|
-
from .utils import assert_two_entities_deep_almost_equal
|
|
20
|
+
from .utils import OSPlatform, assert_two_entities_deep_almost_equal, get_platform_specific_value
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
@pytest.mark.parametrize(
|
|
@@ -29,8 +29,11 @@ from .utils import assert_two_entities_deep_almost_equal
|
|
|
29
29
|
(0, 1, 1),
|
|
30
30
|
2.0,
|
|
31
31
|
[2.0, 1.0],
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
220,
|
|
33
|
+
{
|
|
34
|
+
OSPlatform.DARWIN: GRAIN_BOUNDARY_SI_001_011,
|
|
35
|
+
OSPlatform.OTHER: GRAIN_BOUNDARY_SI_001_011_GH,
|
|
36
|
+
},
|
|
34
37
|
),
|
|
35
38
|
],
|
|
36
39
|
)
|
|
@@ -50,6 +53,7 @@ def test_create_grain_boundary_planar(
|
|
|
50
53
|
max_area=max_area,
|
|
51
54
|
)
|
|
52
55
|
|
|
56
|
+
expected_material_config = get_platform_specific_value(expected_material_config)
|
|
53
57
|
assert_two_entities_deep_almost_equal(grain_boundary, expected_material_config)
|
|
54
58
|
|
|
55
59
|
|
|
@@ -62,8 +66,11 @@ def test_create_grain_boundary_planar(
|
|
|
62
66
|
(0, 1, 1),
|
|
63
67
|
2.0,
|
|
64
68
|
[2.0, 1.0],
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
220,
|
|
70
|
+
{
|
|
71
|
+
OSPlatform.DARWIN: GRAIN_BOUNDARY_SI_001_011,
|
|
72
|
+
OSPlatform.OTHER: GRAIN_BOUNDARY_SI_001_011_GH,
|
|
73
|
+
},
|
|
67
74
|
),
|
|
68
75
|
],
|
|
69
76
|
)
|
|
@@ -95,6 +102,7 @@ def test_grain_boundary_builder(
|
|
|
95
102
|
builder = GrainBoundaryPlanarBuilder()
|
|
96
103
|
grain_boundary = builder.get_material(config)
|
|
97
104
|
|
|
105
|
+
expected_material_config = get_platform_specific_value(expected_material_config)
|
|
98
106
|
assert_two_entities_deep_almost_equal(grain_boundary, expected_material_config)
|
|
99
107
|
|
|
100
108
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from types import SimpleNamespace
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from mat3ra.made.material import Material
|
|
4
5
|
from mat3ra.made.tools.analyze.interface.zsl import ZSLInterfaceAnalyzer
|
|
5
6
|
from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interface.base.builder import InterfaceBuilder
|
|
6
7
|
from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interface.base.configuration import (
|
|
@@ -12,12 +13,14 @@ from mat3ra.made.tools.build.compound_pristine_structures.two_dimensional.interf
|
|
|
12
13
|
)
|
|
13
14
|
from mat3ra.made.tools.build.pristine_structures.two_dimensional.slab import SlabBuilder, SlabConfiguration
|
|
14
15
|
from mat3ra.made.tools.build_components.entities.core.two_dimensional.vacuum.configuration import VacuumConfiguration
|
|
16
|
+
from mat3ra.standata.materials import Materials
|
|
15
17
|
|
|
16
18
|
from .fixtures.bulk import BULK_Ni_PRIMITIVE
|
|
17
19
|
from .fixtures.interface.gr_ni_111_top_hcp import (
|
|
18
20
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP,
|
|
19
21
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP_GH_WF,
|
|
20
22
|
)
|
|
23
|
+
from .fixtures.interface.zsl import DIAMOND_GaAs_INTERFACE, DIAMOND_GaAs_INTERFACE_GH
|
|
21
24
|
from .fixtures.monolayer import GRAPHENE
|
|
22
25
|
from .utils import OSPlatform, assert_two_entities_deep_almost_equal, get_platform_specific_value
|
|
23
26
|
|
|
@@ -43,6 +46,32 @@ GRAPHENE_NICKEL_TEST_CASE = (
|
|
|
43
46
|
},
|
|
44
47
|
)
|
|
45
48
|
|
|
49
|
+
BULK_DIAMOND = Materials.get_by_name_first_match("Diamond")
|
|
50
|
+
BULK_GaAs = Materials.get_by_name_first_match("GaAs")
|
|
51
|
+
|
|
52
|
+
DIAMOND_GAAS_CSL_TEST_CASE = (
|
|
53
|
+
SimpleNamespace(
|
|
54
|
+
bulk_config=BULK_DIAMOND,
|
|
55
|
+
miller_indices=(0, 0, 1),
|
|
56
|
+
number_of_layers=1,
|
|
57
|
+
vacuum=0.0,
|
|
58
|
+
),
|
|
59
|
+
SimpleNamespace(
|
|
60
|
+
bulk_config=BULK_GaAs,
|
|
61
|
+
miller_indices=(0, 0, 1),
|
|
62
|
+
number_of_layers=1,
|
|
63
|
+
vacuum=0.0,
|
|
64
|
+
),
|
|
65
|
+
1.5, # gap between diamond and gaas
|
|
66
|
+
10.0, # vacuum
|
|
67
|
+
70.0, # max area
|
|
68
|
+
{
|
|
69
|
+
OSPlatform.DARWIN: DIAMOND_GaAs_INTERFACE,
|
|
70
|
+
OSPlatform.OTHER: DIAMOND_GaAs_INTERFACE_GH,
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
46
75
|
MAX_AREA_RATIO_TOL = 0.09
|
|
47
76
|
MAX_LENGTH_TOL = 0.05
|
|
48
77
|
MAX_ANGLE_TOL = 0.02
|
|
@@ -50,7 +79,7 @@ MAX_ANGLE_TOL = 0.02
|
|
|
50
79
|
|
|
51
80
|
@pytest.mark.parametrize(
|
|
52
81
|
"substrate, film,gap, vacuum, max_area, expected_interface",
|
|
53
|
-
[GRAPHENE_NICKEL_TEST_CASE],
|
|
82
|
+
[GRAPHENE_NICKEL_TEST_CASE, DIAMOND_GAAS_CSL_TEST_CASE],
|
|
54
83
|
)
|
|
55
84
|
def test_zsl_interface_builder(substrate, film, gap, vacuum, max_area, expected_interface):
|
|
56
85
|
"""Test creating Si/Ge interface using ZSL approach."""
|
|
@@ -90,8 +119,10 @@ def test_zsl_interface_builder(substrate, film, gap, vacuum, max_area, expected_
|
|
|
90
119
|
|
|
91
120
|
# remove metadata
|
|
92
121
|
interface.metadata.build = []
|
|
93
|
-
expected_interface = get_platform_specific_value(expected_interface)
|
|
94
|
-
|
|
122
|
+
expected_interface = Material.create(get_platform_specific_value(expected_interface))
|
|
123
|
+
|
|
124
|
+
assert interface.basis.number_of_atoms == expected_interface.basis.number_of_atoms
|
|
125
|
+
|
|
95
126
|
assert_two_entities_deep_almost_equal(interface, expected_interface)
|
|
96
127
|
|
|
97
128
|
|
|
@@ -122,7 +153,10 @@ def test_create_zsl_interface(substrate, film, gap, vacuum, max_area, expected_i
|
|
|
122
153
|
assert_two_entities_deep_almost_equal(interface, expected_interface)
|
|
123
154
|
|
|
124
155
|
|
|
125
|
-
@pytest.mark.parametrize(
|
|
156
|
+
@pytest.mark.parametrize(
|
|
157
|
+
"substrate, film, gap, vacuum, max_area, expected_interface",
|
|
158
|
+
[GRAPHENE_NICKEL_TEST_CASE, DIAMOND_GAAS_CSL_TEST_CASE],
|
|
159
|
+
)
|
|
126
160
|
def test_create_zsl_interface_between_slabs(substrate, film, gap, vacuum, max_area, expected_interface):
|
|
127
161
|
substrate_slab_config = SlabConfiguration.from_parameters(
|
|
128
162
|
material_or_dict=substrate.bulk_config,
|