@mat3ra/made 2025.10.29-0 → 2025.11.13-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/dist/js/materialMixin.d.ts +8 -0
- package/dist/js/materialMixin.js +6 -0
- package/package.json +1 -1
- package/src/js/materialMixin.ts +7 -0
- package/src/py/mat3ra/made/tools/analyze/interface/simple.py +1 -3
- package/src/py/mat3ra/made/tools/analyze/utils.py +4 -3
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/helpers.py +18 -59
- package/src/py/mat3ra/made/tools/build/compound_pristine_structures/two_dimensional/heterostructure/utils.py +140 -0
- package/tests/py/unit/fixtures/interface/gr_ni_111_top_hcp.py +2 -2
- package/tests/py/unit/fixtures/interface/zsl.py +3 -3
- package/tests/py/unit/test_tools_analyze.py +20 -0
- package/tests/py/unit/test_tools_analyze_interface_zsl.py +17 -5
- package/tests/py/unit/test_tools_build_heterostructure.py +0 -2
- package/tests/py/unit/test_tools_calculate.py +2 -0
|
@@ -108,6 +108,14 @@ export declare function materialMixin<T extends Base = Base>(item: T): {
|
|
|
108
108
|
* Calculates hash from basis and lattice as above + scales lattice properties to make lattice.a = 1
|
|
109
109
|
*/
|
|
110
110
|
readonly scaledHash: string;
|
|
111
|
+
external: {
|
|
112
|
+
id: string | number;
|
|
113
|
+
source: string;
|
|
114
|
+
origin: boolean;
|
|
115
|
+
data?: {} | undefined;
|
|
116
|
+
doi?: string | undefined;
|
|
117
|
+
url?: string | undefined;
|
|
118
|
+
} | undefined;
|
|
111
119
|
/**
|
|
112
120
|
* Converts basis to crystal/fractional coordinates.
|
|
113
121
|
*/
|
package/dist/js/materialMixin.js
CHANGED
|
@@ -232,6 +232,12 @@ function materialMixin(item) {
|
|
|
232
232
|
get scaledHash() {
|
|
233
233
|
return this.calculateHash("", true);
|
|
234
234
|
},
|
|
235
|
+
get external() {
|
|
236
|
+
return item.prop("external");
|
|
237
|
+
},
|
|
238
|
+
set external(external) {
|
|
239
|
+
item.setProp("external", external);
|
|
240
|
+
},
|
|
235
241
|
/**
|
|
236
242
|
* Converts basis to crystal/fractional coordinates.
|
|
237
243
|
*/
|
package/package.json
CHANGED
package/src/js/materialMixin.ts
CHANGED
|
@@ -290,6 +290,13 @@ export function materialMixin<T extends Base = Base>(item: T) {
|
|
|
290
290
|
return this.calculateHash("", true);
|
|
291
291
|
},
|
|
292
292
|
|
|
293
|
+
get external() {
|
|
294
|
+
return item.prop<MaterialSchema["external"]>("external");
|
|
295
|
+
},
|
|
296
|
+
set external(external: MaterialSchema["external"]) {
|
|
297
|
+
item.setProp("external", external);
|
|
298
|
+
},
|
|
299
|
+
|
|
293
300
|
/**
|
|
294
301
|
* Converts basis to crystal/fractional coordinates.
|
|
295
302
|
*/
|
|
@@ -78,12 +78,10 @@ class InterfaceAnalyzer(InMemoryEntityPydantic):
|
|
|
78
78
|
film_2d_vectors = film_vectors[:2, :2]
|
|
79
79
|
|
|
80
80
|
try:
|
|
81
|
-
|
|
81
|
+
strain_2d = np.linalg.solve(film_2d_vectors, substrate_2d_vectors)
|
|
82
82
|
except np.linalg.LinAlgError:
|
|
83
83
|
raise ValueError("Film lattice vectors are not linearly independent.")
|
|
84
84
|
|
|
85
|
-
strain_2d = inv_film @ substrate_2d_vectors
|
|
86
|
-
|
|
87
85
|
strain_3d = np.eye(3)
|
|
88
86
|
strain_3d[:2, :2] = strain_2d
|
|
89
87
|
return Matrix3x3Schema(root=strain_3d.tolist())
|
|
@@ -125,10 +125,11 @@ def calculate_von_mises_strain(strain_matrix: np.ndarray) -> float:
|
|
|
125
125
|
float: The von Mises strain in percentage.
|
|
126
126
|
"""
|
|
127
127
|
# allow passing a Python list
|
|
128
|
-
|
|
128
|
+
strain_matrix_2x2 = np.array(strain_matrix, dtype=float)[:2, :2]
|
|
129
|
+
E = 0.5 * (strain_matrix_2x2.T @ strain_matrix_2x2 - np.eye(2))
|
|
129
130
|
|
|
130
|
-
exx = E[0, 0]
|
|
131
|
-
eyy = E[1, 1]
|
|
131
|
+
exx = E[0, 0]
|
|
132
|
+
eyy = E[1, 1]
|
|
132
133
|
exy = 0.5 * (E[0, 1] + E[1, 0])
|
|
133
134
|
|
|
134
135
|
e_von_mises = np.sqrt(exx**2 - exx * eyy + eyy**2 + 3 * exy**2)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
4
|
-
|
|
5
3
|
from .types import StackComponentDict
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
from .utils import (
|
|
5
|
+
apply_gaps_and_stack,
|
|
6
|
+
create_initial_slabs,
|
|
7
|
+
create_strained_films,
|
|
8
|
+
create_strained_substrate,
|
|
9
|
+
find_common_supercell_matrix,
|
|
10
|
+
validate_heterostructure_inputs,
|
|
11
|
+
)
|
|
9
12
|
from .....analyze import BaseMaterialAnalyzer
|
|
10
|
-
from .....analyze.interface import InterfaceAnalyzer
|
|
11
|
-
from .....analyze.slab import SlabMaterialAnalyzer
|
|
12
13
|
from .....build_components import MaterialWithBuildMetadata
|
|
13
|
-
from .....operations.core.binary import stack
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def create_heterostructure(
|
|
@@ -22,67 +22,25 @@ def create_heterostructure(
|
|
|
22
22
|
) -> MaterialWithBuildMetadata:
|
|
23
23
|
"""
|
|
24
24
|
Create a heterostructure by stacking multiple slabs, while applying strain to each slab relative to the first slab.
|
|
25
|
-
|
|
26
25
|
Args:
|
|
27
|
-
stack_component_dicts: List of
|
|
26
|
+
stack_component_dicts: List of stack component configurations
|
|
28
27
|
gaps: List of gaps between adjacent slabs (in Angstroms)
|
|
29
|
-
vacuum: Size of vacuum layer in Angstroms
|
|
28
|
+
vacuum: Size of vacuum layer over the last slab (in Angstroms)
|
|
30
29
|
use_conventional_cell: Whether to use conventional cell
|
|
31
30
|
optimize_layer_supercells: Whether to find optimal supercells for strained layers
|
|
32
|
-
|
|
33
31
|
Returns:
|
|
34
32
|
Heterostructure material with stacked strained slabs
|
|
35
33
|
"""
|
|
36
|
-
|
|
37
|
-
raise ValueError("At least 2 stack components are required for a heterostructure")
|
|
38
|
-
|
|
39
|
-
if len(gaps) != len(stack_component_dicts) - 1:
|
|
40
|
-
raise ValueError("Number of gaps must be one less than number of stack components")
|
|
41
|
-
|
|
42
|
-
slabs = []
|
|
43
|
-
for i, component in enumerate(stack_component_dicts):
|
|
44
|
-
slab = create_slab(
|
|
45
|
-
crystal=component.crystal,
|
|
46
|
-
miller_indices=component.miller_indices,
|
|
47
|
-
number_of_layers=component.thickness,
|
|
48
|
-
vacuum=0.0 if i < len(stack_component_dicts) - 1 else vacuum,
|
|
49
|
-
use_conventional_cell=use_conventional_cell,
|
|
50
|
-
xy_supercell_matrix=component.xy_supercell_matrix or [[1, 0], [0, 1]],
|
|
51
|
-
)
|
|
52
|
-
slabs.append(slab)
|
|
53
|
-
|
|
54
|
-
strained_slabs = [slabs[0]] # First slab is the substrate, not strained
|
|
34
|
+
validate_heterostructure_inputs(stack_component_dicts, gaps)
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
film_slab = slabs[i]
|
|
36
|
+
slabs = create_initial_slabs(stack_component_dicts, vacuum, use_conventional_cell)
|
|
37
|
+
common_supercell_matrix = find_common_supercell_matrix(slabs, optimize_layer_supercells)
|
|
59
38
|
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
strained_substrate = create_strained_substrate(slabs[0], common_supercell_matrix)
|
|
40
|
+
strained_films = create_strained_films(slabs, common_supercell_matrix, optimize_layer_supercells)
|
|
62
41
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
film_slab_configuration=film_analyzer.build_configuration,
|
|
66
|
-
substrate_build_parameters=substrate_analyzer.build_parameters,
|
|
67
|
-
film_build_parameters=film_analyzer.build_parameters,
|
|
68
|
-
optimize_film_supercell=optimize_layer_supercells,
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
strained_film_config = analyzer.film_strained_configuration
|
|
72
|
-
|
|
73
|
-
builder = SlabStrainedSupercellBuilder()
|
|
74
|
-
strained_slab = builder.get_material(strained_film_config)
|
|
75
|
-
strained_slabs.append(strained_slab)
|
|
76
|
-
|
|
77
|
-
stacked_materials = []
|
|
78
|
-
for i, slab in enumerate(strained_slabs):
|
|
79
|
-
if i < len(gaps):
|
|
80
|
-
slab_with_gap = adjust_material_cell_to_set_gap_along_direction(slab, gaps[i], AxisEnum.z)
|
|
81
|
-
stacked_materials.append(slab_with_gap)
|
|
82
|
-
else:
|
|
83
|
-
stacked_materials.append(slab)
|
|
84
|
-
|
|
85
|
-
heterostructure = stack(stacked_materials, AxisEnum.z)
|
|
42
|
+
strained_slabs = [strained_substrate] + strained_films
|
|
43
|
+
heterostructure = apply_gaps_and_stack(strained_slabs, gaps)
|
|
86
44
|
heterostructure.name = generate_heterostructure_name(stack_component_dicts)
|
|
87
45
|
|
|
88
46
|
return heterostructure
|
|
@@ -99,3 +57,4 @@ def generate_heterostructure_name(stack_component_dicts: List[StackComponentDict
|
|
|
99
57
|
components.append(f"{formula}({miller_str})")
|
|
100
58
|
|
|
101
59
|
return f"Heterostructure [{'-'.join(components)}]"
|
|
60
|
+
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from mat3ra.esse.models.core.abstract.matrix_3x3 import Matrix3x3Schema
|
|
5
|
+
from mat3ra.esse.models.core.reusable.axis_enum import AxisEnum
|
|
6
|
+
from mat3ra.esse.models.materials_category_components.entities.auxiliary.two_dimensional.supercell_matrix_2d import (
|
|
7
|
+
SupercellMatrix2DSchema,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from .types import StackComponentDict
|
|
11
|
+
from mat3ra.made.utils import adjust_material_cell_to_set_gap_along_direction
|
|
12
|
+
from ....pristine_structures.two_dimensional.slab.helpers import create_slab
|
|
13
|
+
from ....pristine_structures.two_dimensional.slab_strained_supercell.builder import SlabStrainedSupercellBuilder
|
|
14
|
+
from ....pristine_structures.two_dimensional.slab_strained_supercell.configuration import (
|
|
15
|
+
SlabStrainedSupercellConfiguration,
|
|
16
|
+
)
|
|
17
|
+
from .....analyze.interface import InterfaceAnalyzer
|
|
18
|
+
from .....analyze.slab import SlabMaterialAnalyzer
|
|
19
|
+
from .....build_components import MaterialWithBuildMetadata
|
|
20
|
+
from .....operations.core.binary import stack
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validate_heterostructure_inputs(stack_component_dicts: List[StackComponentDict], gaps: List[float]) -> None:
|
|
24
|
+
"""Validate inputs for heterostructure creation."""
|
|
25
|
+
if len(stack_component_dicts) < 2:
|
|
26
|
+
raise ValueError("At least 2 stack components are required for a heterostructure")
|
|
27
|
+
if len(gaps) != len(stack_component_dicts) - 1:
|
|
28
|
+
raise ValueError("Number of gaps must be one less than number of stack components")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_initial_slabs(
|
|
32
|
+
stack_component_dicts: List[StackComponentDict],
|
|
33
|
+
vacuum: float,
|
|
34
|
+
use_conventional_cell: bool
|
|
35
|
+
) -> List[MaterialWithBuildMetadata]:
|
|
36
|
+
"""Create initial slabs from stack components."""
|
|
37
|
+
slabs = []
|
|
38
|
+
for i, component in enumerate(stack_component_dicts):
|
|
39
|
+
slab = create_slab(
|
|
40
|
+
crystal=component.crystal,
|
|
41
|
+
miller_indices=component.miller_indices,
|
|
42
|
+
number_of_layers=component.thickness,
|
|
43
|
+
vacuum=0.0 if i < len(stack_component_dicts) - 1 else vacuum,
|
|
44
|
+
use_conventional_cell=use_conventional_cell,
|
|
45
|
+
xy_supercell_matrix=component.xy_supercell_matrix or [[1, 0], [0, 1]],
|
|
46
|
+
)
|
|
47
|
+
slabs.append(slab)
|
|
48
|
+
return slabs
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def find_common_supercell_matrix(
|
|
52
|
+
slabs: List[MaterialWithBuildMetadata],
|
|
53
|
+
optimize_layer_supercells: bool
|
|
54
|
+
) -> SupercellMatrix2DSchema:
|
|
55
|
+
"""Find common supercell matrix for all materials."""
|
|
56
|
+
reference_substrate = slabs[0]
|
|
57
|
+
|
|
58
|
+
for i in range(1, len(slabs)):
|
|
59
|
+
film_slab = slabs[i]
|
|
60
|
+
|
|
61
|
+
substrate_analyzer = SlabMaterialAnalyzer(material=reference_substrate)
|
|
62
|
+
film_analyzer = SlabMaterialAnalyzer(material=film_slab)
|
|
63
|
+
|
|
64
|
+
analyzer = InterfaceAnalyzer(
|
|
65
|
+
substrate_slab_configuration=substrate_analyzer.build_configuration,
|
|
66
|
+
film_slab_configuration=film_analyzer.build_configuration,
|
|
67
|
+
substrate_build_parameters=substrate_analyzer.build_parameters,
|
|
68
|
+
film_build_parameters=film_analyzer.build_parameters,
|
|
69
|
+
optimize_film_supercell=optimize_layer_supercells,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return analyzer.film_strained_configuration.xy_supercell_matrix
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def create_strained_substrate(
|
|
76
|
+
reference_substrate: MaterialWithBuildMetadata,
|
|
77
|
+
common_supercell_matrix: SupercellMatrix2DSchema
|
|
78
|
+
) -> MaterialWithBuildMetadata:
|
|
79
|
+
"""Create strained substrate with common supercell."""
|
|
80
|
+
substrate_analyzer = SlabMaterialAnalyzer(material=reference_substrate)
|
|
81
|
+
|
|
82
|
+
substrate_config = SlabStrainedSupercellConfiguration(
|
|
83
|
+
**substrate_analyzer.build_configuration.model_dump(),
|
|
84
|
+
strain_matrix=Matrix3x3Schema(root=np.eye(3).tolist()),
|
|
85
|
+
xy_supercell_matrix=common_supercell_matrix,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
builder = SlabStrainedSupercellBuilder()
|
|
89
|
+
return builder.get_material(substrate_config)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_strained_films(
|
|
93
|
+
slabs: List[MaterialWithBuildMetadata],
|
|
94
|
+
common_supercell_matrix: SupercellMatrix2DSchema,
|
|
95
|
+
optimize_layer_supercells: bool
|
|
96
|
+
) -> List[MaterialWithBuildMetadata]:
|
|
97
|
+
"""Create strained films with common supercell."""
|
|
98
|
+
reference_substrate = slabs[0]
|
|
99
|
+
strained_films = []
|
|
100
|
+
builder = SlabStrainedSupercellBuilder()
|
|
101
|
+
|
|
102
|
+
for i in range(1, len(slabs)):
|
|
103
|
+
film_slab = slabs[i]
|
|
104
|
+
|
|
105
|
+
substrate_analyzer = SlabMaterialAnalyzer(material=reference_substrate)
|
|
106
|
+
film_analyzer = SlabMaterialAnalyzer(material=film_slab)
|
|
107
|
+
|
|
108
|
+
analyzer = InterfaceAnalyzer(
|
|
109
|
+
substrate_slab_configuration=substrate_analyzer.build_configuration,
|
|
110
|
+
film_slab_configuration=film_analyzer.build_configuration,
|
|
111
|
+
substrate_build_parameters=substrate_analyzer.build_parameters,
|
|
112
|
+
film_build_parameters=film_analyzer.build_parameters,
|
|
113
|
+
optimize_film_supercell=optimize_layer_supercells,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
film_config = analyzer.film_strained_configuration
|
|
117
|
+
film_config_dict = film_config.model_dump()
|
|
118
|
+
film_config_dict['xy_supercell_matrix'] = common_supercell_matrix
|
|
119
|
+
film_config_common = SlabStrainedSupercellConfiguration(**film_config_dict)
|
|
120
|
+
|
|
121
|
+
strained_film = builder.get_material(film_config_common)
|
|
122
|
+
strained_films.append(strained_film)
|
|
123
|
+
|
|
124
|
+
return strained_films
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def apply_gaps_and_stack(
|
|
128
|
+
strained_slabs: List[MaterialWithBuildMetadata],
|
|
129
|
+
gaps: List[float]
|
|
130
|
+
) -> MaterialWithBuildMetadata:
|
|
131
|
+
"""Apply gaps and stack materials."""
|
|
132
|
+
stacked_materials = []
|
|
133
|
+
for i, slab in enumerate(strained_slabs):
|
|
134
|
+
if i < len(gaps):
|
|
135
|
+
slab_with_gap = adjust_material_cell_to_set_gap_along_direction(slab, gaps[i], AxisEnum.z)
|
|
136
|
+
stacked_materials.append(slab_with_gap)
|
|
137
|
+
else:
|
|
138
|
+
stacked_materials.append(slab)
|
|
139
|
+
|
|
140
|
+
return stack(stacked_materials, AxisEnum.z)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP = {
|
|
2
|
-
"name": "C(001)-Ni(111), Interface, Strain 0.
|
|
2
|
+
"name": "C(001)-Ni(111), Interface, Strain 0.475pct",
|
|
3
3
|
"basis": {
|
|
4
4
|
"elements": [
|
|
5
5
|
{"id": 0, "value": "Ni"},
|
|
@@ -39,7 +39,7 @@ GRAPHENE_NICKEL_INTERFACE_TOP_HCP = {
|
|
|
39
39
|
|
|
40
40
|
# This configuration is used for testing purposes, specifically for the GitHub Workflow.
|
|
41
41
|
GRAPHENE_NICKEL_INTERFACE_TOP_HCP_GH_WF = {
|
|
42
|
-
"name": "C(001)-Ni(111), Interface, Strain 25.
|
|
42
|
+
"name": "C(001)-Ni(111), Interface, Strain 25.629pct",
|
|
43
43
|
"basis": {
|
|
44
44
|
"elements": [
|
|
45
45
|
{"id": 0, "value": "Ni"},
|
|
@@ -501,7 +501,7 @@ GRAPHENE_NICKEL_INTERFACE = {
|
|
|
501
501
|
],
|
|
502
502
|
"boundaryConditions": {"type": "pbc", "offset": 0},
|
|
503
503
|
},
|
|
504
|
-
"name": "C(001)-Ni(111), Interface, Strain 0.
|
|
504
|
+
"name": "C(001)-Ni(111), Interface, Strain 0.474pct",
|
|
505
505
|
"basis": {
|
|
506
506
|
"elements": [
|
|
507
507
|
{"id": 0, "value": "Ni"},
|
|
@@ -540,7 +540,7 @@ GRAPHENE_NICKEL_INTERFACE = {
|
|
|
540
540
|
}
|
|
541
541
|
|
|
542
542
|
DIAMOND_GaAs_INTERFACE = {
|
|
543
|
-
"name": "AsGa(001)-C(001), Interface, Strain
|
|
543
|
+
"name": "AsGa(001)-C(001), Interface, Strain 2.068pct",
|
|
544
544
|
"basis": {
|
|
545
545
|
"elements": [
|
|
546
546
|
{"id": 0, "value": "Ga"},
|
|
@@ -648,7 +648,7 @@ DIAMOND_GaAs_INTERFACE = {
|
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
DIAMOND_GaAs_INTERFACE_GH = {
|
|
651
|
-
"name": "AsGa(001)-C(001), Interface, Strain
|
|
651
|
+
"name": "AsGa(001)-C(001), Interface, Strain 2.068pct",
|
|
652
652
|
"basis": {
|
|
653
653
|
"elements": [
|
|
654
654
|
{"id": 0, "value": "Ga"},
|
|
@@ -14,6 +14,7 @@ from mat3ra.made.tools.analyze.other import (
|
|
|
14
14
|
get_surface_atom_indices,
|
|
15
15
|
)
|
|
16
16
|
from mat3ra.made.tools.analyze.rdf import RadialDistributionFunction
|
|
17
|
+
from mat3ra.made.tools.analyze.utils import calculate_von_mises_strain
|
|
17
18
|
from mat3ra.made.tools.build import MaterialWithBuildMetadata
|
|
18
19
|
from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.atom_placement_method_enum import (
|
|
19
20
|
AtomPlacementMethodEnum,
|
|
@@ -191,3 +192,22 @@ def test_get_surface_atom_indices_top_and_bottom(material_config, expected_indic
|
|
|
191
192
|
bottom_indices = get_surface_atom_indices(material, SurfaceTypesEnum.BOTTOM)
|
|
192
193
|
assert set(top_indices) == set(expected_indices_top)
|
|
193
194
|
assert set(bottom_indices) == set(expected_indices_bottom)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.parametrize(
|
|
198
|
+
"strain_matrix, expected_strain",
|
|
199
|
+
[
|
|
200
|
+
# Identity matrix - no strain
|
|
201
|
+
([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], 0.0),
|
|
202
|
+
# Uniaxial compression (5% x only)
|
|
203
|
+
([[0.95, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], 4.875),
|
|
204
|
+
# Biaxial compression (5% x and y)
|
|
205
|
+
([[0.95, 0.0, 0.0], [0.0, 0.95, 0.0], [0.0, 0.0, 1.0]], 4.875),
|
|
206
|
+
# Shear strain
|
|
207
|
+
([[1.0, 0.1, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], 8.675),
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
def test_calculate_von_mises_strain(strain_matrix, expected_strain):
|
|
211
|
+
"""Test von Mises strain calculation used in ZSL interface analysis."""
|
|
212
|
+
strain_percentage = calculate_von_mises_strain(np.array(strain_matrix))
|
|
213
|
+
assert np.isclose(strain_percentage, expected_strain, atol=0.01)
|
|
@@ -96,9 +96,9 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
96
96
|
substrate_vectors = np.array(analyzer.substrate_slab.lattice.vector_arrays)
|
|
97
97
|
|
|
98
98
|
film_sl_vectors = (
|
|
99
|
-
np.array(
|
|
99
|
+
np.array(unwrap(film_config.strain_matrix.root))
|
|
100
|
+
@ np.array(convert_2x2_to_3x3(supercell_matrix_2d_schema_to_list(film_config.xy_supercell_matrix)))
|
|
100
101
|
@ film_vectors
|
|
101
|
-
@ np.array(unwrap(film_config.strain_matrix.root))
|
|
102
102
|
)
|
|
103
103
|
substrate_sl_vectors = (
|
|
104
104
|
np.array(convert_2x2_to_3x3(supercell_matrix_2d_schema_to_list(sub_config.xy_supercell_matrix)))
|
|
@@ -108,7 +108,19 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
108
108
|
substrate_material = SlabStrainedSupercellBuilder().get_material(sub_config)
|
|
109
109
|
film_material = SlabStrainedSupercellBuilder().get_material(film_config)
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
# Check that the lattice vectors have the same magnitudes (allowing for orientation differences)
|
|
112
|
+
film_a = np.linalg.norm(film_sl_vectors[0])
|
|
113
|
+
film_b = np.linalg.norm(film_sl_vectors[1])
|
|
114
|
+
substrate_a = np.linalg.norm(substrate_sl_vectors[0])
|
|
115
|
+
substrate_b = np.linalg.norm(substrate_sl_vectors[1])
|
|
116
|
+
|
|
117
|
+
assert np.isclose(film_a, substrate_a, atol=0.1)
|
|
118
|
+
assert np.isclose(film_b, substrate_b, atol=0.1)
|
|
119
|
+
|
|
120
|
+
# Check that the unit cell areas match
|
|
121
|
+
film_area = abs(np.cross(film_sl_vectors[0][:2], film_sl_vectors[1][:2]))
|
|
122
|
+
substrate_area = abs(np.cross(substrate_sl_vectors[0][:2], substrate_sl_vectors[1][:2]))
|
|
123
|
+
assert np.isclose(film_area, substrate_area, atol=1e-4)
|
|
112
124
|
|
|
113
125
|
assert np.isclose(substrate_material.lattice.a, film_material.lattice.a, atol=1e-4)
|
|
114
126
|
assert np.isclose(substrate_material.lattice.b, film_material.lattice.b, atol=1e-4)
|
|
@@ -131,10 +143,10 @@ def test_zsl_interface_analyzer(substrate, film, zsl_params, expected_matches_mi
|
|
|
131
143
|
vacuum=0.0,
|
|
132
144
|
),
|
|
133
145
|
{"max_area": 90.0, "max_area_ratio_tol": 0.1, "max_length_tol": 0.1, "max_angle_tol": 0.1},
|
|
134
|
-
{OSPlatform.DARWIN:
|
|
146
|
+
{OSPlatform.DARWIN: 26, OSPlatform.OTHER: 29},
|
|
135
147
|
{
|
|
136
148
|
OSPlatform.DARWIN: {"strain_percentage": 0.474, "match_id": 0},
|
|
137
|
-
OSPlatform.OTHER: {"strain_percentage": 25.
|
|
149
|
+
OSPlatform.OTHER: {"strain_percentage": 25.629, "match_id": 0},
|
|
138
150
|
},
|
|
139
151
|
# NOTE: the following values are expected for the DARWIN platform.
|
|
140
152
|
# {"max_area": 90.0, "max_area_ratio_tol": 0.09, "max_length_tol": 0.03, "max_angle_tol": 0.01},
|
|
@@ -6,8 +6,6 @@ from mat3ra.made.tools.helpers import StackComponentDict, create_heterostructure
|
|
|
6
6
|
|
|
7
7
|
from .fixtures.bulk import BULK_Hf2O_MCL, BULK_Si_CONVENTIONAL, BULK_SiO2, BULK_TiN
|
|
8
8
|
|
|
9
|
-
PRECISION = 1e-3
|
|
10
|
-
|
|
11
9
|
Si_SiO2_Hf2O_HETEROSTRUCTURE_TEST_CASE = (
|
|
12
10
|
[
|
|
13
11
|
SimpleNamespace(bulk_config=BULK_Si_CONVENTIONAL, miller_indices=(0, 0, 1), number_of_layers=4),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import pytest
|
|
2
3
|
from ase.build import add_adsorbate, bulk, fcc111, graphene, surface
|
|
3
4
|
from ase.calculators import emt
|
|
4
5
|
from mat3ra.made.material import Material
|
|
@@ -58,6 +59,7 @@ def test_calculate_adhesion_energy():
|
|
|
58
59
|
assert np.isclose(adhesion_energy, 0.07345)
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
@pytest.mark.skip("Skipping test temporarily, 2025-11-13.")
|
|
61
63
|
def test_calculate_interfacial_energy():
|
|
62
64
|
interfacial_energy = calculate_interfacial_energy(
|
|
63
65
|
interface_material,
|