@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/made",
3
- "version": "2025.8.9-0",
3
+ "version": "2025.8.23-0",
4
4
  "description": "MAterials DEsign library",
5
5
  "scripts": {
6
6
  "lint": "eslint --cache src/js tests/js && prettier --write src/js tests/js",
@@ -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,4 @@
1
+ from .analyzer import BasisMaterialAnalyzer
2
+ from .fingerprint import LayeredFingerprintAlongAxis
3
+
4
+ __all__ = ["BasisMaterialAnalyzer", "LayeredFingerprintAlongAxis"]
@@ -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
- a_colinear = are_vectors_colinear(pymatgen_film_sl_vectors[0], pymatgen_substrate_sl_vectors[0])
103
- b_colinear = are_vectors_colinear(pymatgen_film_sl_vectors[1], pymatgen_substrate_sl_vectors[1])
104
- if not (a_colinear and b_colinear):
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 = np.rint(np.linalg.solve(film_slab_vectors, pymatgen_film_sl_vectors)).astype(int)
108
- substrate_supercell_matrix = np.rint(
109
- np.linalg.solve(substrate_slab_vectors, pymatgen_substrate_sl_vectors)
110
- ).astype(int)
111
-
112
- area = match_pymatgen.match_area
113
- if self.reduce_result_cell:
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 = np.linalg.solve(film_sl_supercell_vectors, substrate_sl_supercell_vectors)
130
-
131
- real_strain_matrix = convert_2x2_to_3x3(real_strain_matrix.tolist())
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
- material_with_primitive_lattice = analyzer.material_with_primitive_lattice
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
- # TODO: check that this doesn't warp material or flip it -- otherwise raise and skip
86
- primitive_material = get_material_with_primitive_lattice(material, return_original_if_not_reduced=True)
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: 32, OSPlatform.OTHER: 33},
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
- 250,
33
- GRAIN_BOUNDARY_SI_001_011,
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
- 250,
66
- GRAIN_BOUNDARY_SI_001_011,
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
- print(interface.to_dict())
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("substrate, film, gap, vacuum, max_area, expected_interface", [GRAPHENE_NICKEL_TEST_CASE])
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,