@mat3ra/made 2024.9.28-0 → 2024.10.1-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/py/mat3ra/made/tools/build/grain_boundary/__init__.py +8 -5
- package/src/py/mat3ra/made/tools/build/grain_boundary/builders.py +54 -4
- package/src/py/mat3ra/made/tools/build/grain_boundary/configuration.py +23 -1
- package/src/py/mat3ra/made/tools/build/interface/builders.py +2 -2
- package/src/py/mat3ra/made/tools/build/interface/configuration.py +4 -2
- package/src/py/mat3ra/made/tools/build/utils.py +91 -0
- package/tests/py/unit/test_tools_build_grain_boundary.py +34 -2
- package/tests/py/unit/test_tools_build_interface.py +4 -4
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, Union
|
|
2
2
|
|
|
3
3
|
from mat3ra.made.material import Material
|
|
4
4
|
|
|
5
|
-
from .builders import SlabGrainBoundaryBuilder
|
|
6
|
-
from .configuration import
|
|
5
|
+
from .builders import SlabGrainBoundaryBuilder, SurfaceGrainBoundaryBuilder, SurfaceGrainBoundaryBuilderParameters
|
|
6
|
+
from .configuration import (
|
|
7
|
+
SlabGrainBoundaryConfiguration,
|
|
8
|
+
SurfaceGrainBoundaryConfiguration,
|
|
9
|
+
)
|
|
7
10
|
|
|
8
11
|
|
|
9
12
|
def create_grain_boundary(
|
|
10
|
-
configuration: SlabGrainBoundaryConfiguration,
|
|
11
|
-
builder:
|
|
13
|
+
configuration: Union[SlabGrainBoundaryConfiguration, SurfaceGrainBoundaryConfiguration],
|
|
14
|
+
builder: Union[SlabGrainBoundaryBuilder, SurfaceGrainBoundaryBuilder, None] = None,
|
|
12
15
|
) -> Material:
|
|
13
16
|
"""
|
|
14
17
|
Create a grain boundary according to provided configuration with selected builder.
|
|
@@ -3,13 +3,19 @@ from typing import List
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
from mat3ra.made.material import Material
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from ...third_party import PymatgenInterface
|
|
7
7
|
from ...analyze import get_chemical_formula
|
|
8
|
+
from ..slab import SlabConfiguration, get_terminations, create_slab
|
|
8
9
|
from ..interface import ZSLStrainMatchingInterfaceBuilderParameters, InterfaceConfiguration
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
from ..interface.builders import (
|
|
12
|
+
ZSLStrainMatchingInterfaceBuilder,
|
|
13
|
+
CommensurateLatticeTwistedInterfaceBuilder,
|
|
14
|
+
CommensurateLatticeTwistedInterfaceBuilderParameters,
|
|
15
|
+
)
|
|
10
16
|
from ..supercell import create_supercell
|
|
11
|
-
from
|
|
12
|
-
from
|
|
17
|
+
from ..utils import stack_two_materials_xy
|
|
18
|
+
from .configuration import SurfaceGrainBoundaryConfiguration, SlabGrainBoundaryConfiguration
|
|
13
19
|
|
|
14
20
|
|
|
15
21
|
class SlabGrainBoundaryBuilderParameters(ZSLStrainMatchingInterfaceBuilderParameters):
|
|
@@ -27,6 +33,7 @@ class SlabGrainBoundaryBuilder(ZSLStrainMatchingInterfaceBuilder):
|
|
|
27
33
|
"""
|
|
28
34
|
|
|
29
35
|
_BuildParametersType: type(SlabGrainBoundaryBuilderParameters) = SlabGrainBoundaryBuilderParameters # type: ignore
|
|
36
|
+
_DefaultBuildParameters = SlabGrainBoundaryBuilderParameters()
|
|
30
37
|
_ConfigurationType: type(SlabGrainBoundaryConfiguration) = SlabGrainBoundaryConfiguration # type: ignore
|
|
31
38
|
_GeneratedItemType: PymatgenInterface = PymatgenInterface # type: ignore
|
|
32
39
|
selector_parameters: type( # type: ignore
|
|
@@ -78,3 +85,46 @@ class SlabGrainBoundaryBuilder(ZSLStrainMatchingInterfaceBuilder):
|
|
|
78
85
|
)
|
|
79
86
|
material.name = new_name
|
|
80
87
|
return material
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class SurfaceGrainBoundaryBuilderParameters(CommensurateLatticeTwistedInterfaceBuilderParameters):
|
|
91
|
+
"""
|
|
92
|
+
Parameters for creating a grain boundary between two surface phases.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
edge_inclusion_tolerance (float): The tolerance to include atoms on the edge of each phase, in angstroms.
|
|
96
|
+
distance_tolerance (float): The distance tolerance to remove atoms that are too close, in angstroms.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
edge_inclusion_tolerance: float = 1.0
|
|
100
|
+
distance_tolerance: float = 1.0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SurfaceGrainBoundaryBuilder(CommensurateLatticeTwistedInterfaceBuilder):
|
|
104
|
+
_ConfigurationType: type(SurfaceGrainBoundaryConfiguration) = SurfaceGrainBoundaryConfiguration # type: ignore
|
|
105
|
+
_BuildParametersType = SurfaceGrainBoundaryBuilderParameters
|
|
106
|
+
_DefaultBuildParameters = SurfaceGrainBoundaryBuilderParameters()
|
|
107
|
+
|
|
108
|
+
def _post_process(self, items: List[Material], post_process_parameters=None) -> List[Material]:
|
|
109
|
+
grain_boundaries = []
|
|
110
|
+
for item in items:
|
|
111
|
+
matrix1 = np.dot(np.array(item.configuration.xy_supercell_matrix), item.matrix1)
|
|
112
|
+
matrix2 = np.dot(np.array(item.configuration.xy_supercell_matrix), item.matrix2)
|
|
113
|
+
phase_1_material_initial = create_supercell(item.configuration.film, matrix1.tolist())
|
|
114
|
+
phase_2_material_initial = create_supercell(item.configuration.film, matrix2.tolist())
|
|
115
|
+
|
|
116
|
+
interface = stack_two_materials_xy(
|
|
117
|
+
phase_1_material_initial,
|
|
118
|
+
phase_2_material_initial,
|
|
119
|
+
gap=item.configuration.gap,
|
|
120
|
+
edge_inclusion_tolerance=self.build_parameters.edge_inclusion_tolerance,
|
|
121
|
+
distance_tolerance=self.build_parameters.distance_tolerance,
|
|
122
|
+
)
|
|
123
|
+
grain_boundaries.append(interface)
|
|
124
|
+
|
|
125
|
+
return grain_boundaries
|
|
126
|
+
|
|
127
|
+
def _update_material_name(self, material: Material, configuration: SurfaceGrainBoundaryConfiguration) -> Material:
|
|
128
|
+
new_name = f"{configuration.film.name}, Grain Boundary ({configuration.twist_angle:.2f}°)"
|
|
129
|
+
material.name = new_name
|
|
130
|
+
return material
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Optional, List
|
|
2
2
|
|
|
3
3
|
from .. import BaseConfiguration
|
|
4
4
|
from ..slab.configuration import SlabConfiguration
|
|
5
5
|
from ..slab.termination import Termination
|
|
6
|
+
from ..interface.configuration import TwistedInterfaceConfiguration
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class SlabGrainBoundaryConfiguration(BaseConfiguration):
|
|
@@ -38,3 +39,24 @@ class SlabGrainBoundaryConfiguration(BaseConfiguration):
|
|
|
38
39
|
"gap": self.gap,
|
|
39
40
|
"slab_configuration": self.slab_configuration.to_json(),
|
|
40
41
|
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SurfaceGrainBoundaryConfiguration(TwistedInterfaceConfiguration):
|
|
45
|
+
"""
|
|
46
|
+
Configuration for creating a surface grain boundary.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
gap (float): The gap between the two phases.
|
|
50
|
+
xy_supercell_matrix (List[List[int]]): The supercell matrix to apply for both phases.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
gap: float = 0.0
|
|
54
|
+
xy_supercell_matrix: List[List[int]] = [[1, 0], [0, 1]]
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def _json(self):
|
|
58
|
+
return {
|
|
59
|
+
"type": self.get_cls_name(),
|
|
60
|
+
"gap": self.gap,
|
|
61
|
+
"xy_supercell_matrix": self.xy_supercell_matrix,
|
|
62
|
+
}
|
|
@@ -247,7 +247,7 @@ class NanoRibbonTwistedInterfaceBuilder(BaseBuilder):
|
|
|
247
247
|
return material
|
|
248
248
|
|
|
249
249
|
|
|
250
|
-
class
|
|
250
|
+
class CommensurateLatticeTwistedInterfaceBuilderParameters(BaseModel):
|
|
251
251
|
"""
|
|
252
252
|
Parameters for the commensurate lattice interface builder.
|
|
253
253
|
|
|
@@ -263,7 +263,7 @@ class CommensurateLatticeInterfaceBuilderParameters(BaseModel):
|
|
|
263
263
|
return_first_match: bool = False
|
|
264
264
|
|
|
265
265
|
|
|
266
|
-
class
|
|
266
|
+
class CommensurateLatticeTwistedInterfaceBuilder(BaseBuilder):
|
|
267
267
|
_GeneratedItemType: type(CommensurateLatticePair) = CommensurateLatticePair # type: ignore
|
|
268
268
|
_ConfigurationType: type(TwistedInterfaceConfiguration) = TwistedInterfaceConfiguration # type: ignore
|
|
269
269
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
1
3
|
from mat3ra.code.entity import InMemoryEntity
|
|
2
4
|
from pydantic import BaseModel
|
|
3
5
|
|
|
@@ -35,7 +37,7 @@ class InterfaceConfiguration(BaseModel, InMemoryEntity):
|
|
|
35
37
|
|
|
36
38
|
class TwistedInterfaceConfiguration(BaseConfiguration):
|
|
37
39
|
film: Material
|
|
38
|
-
substrate: Material
|
|
40
|
+
substrate: Optional[Material] = None
|
|
39
41
|
twist_angle: float = 0.0
|
|
40
42
|
distance_z: float = 3.0
|
|
41
43
|
|
|
@@ -44,7 +46,7 @@ class TwistedInterfaceConfiguration(BaseConfiguration):
|
|
|
44
46
|
return {
|
|
45
47
|
"type": self.get_cls_name(),
|
|
46
48
|
"film": self.film.to_json(),
|
|
47
|
-
"substrate": self.substrate.to_json(),
|
|
49
|
+
"substrate": self.substrate.to_json() if self.substrate else None,
|
|
48
50
|
"twist_angle": self.twist_angle,
|
|
49
51
|
"distance_z": self.distance_z,
|
|
50
52
|
}
|
|
@@ -3,6 +3,9 @@ from scipy.spatial import cKDTree
|
|
|
3
3
|
from typing import List, Optional
|
|
4
4
|
from mat3ra.made.basis import Basis
|
|
5
5
|
from mat3ra.made.material import Material
|
|
6
|
+
|
|
7
|
+
from .supercell import create_supercell
|
|
8
|
+
from ..modify import filter_by_box, translate_by_vector
|
|
6
9
|
from ...utils import ArrayWithIds
|
|
7
10
|
|
|
8
11
|
|
|
@@ -78,6 +81,20 @@ def merge_materials(
|
|
|
78
81
|
distance_tolerance: float = 0.01,
|
|
79
82
|
merge_dangerously=False,
|
|
80
83
|
) -> Material:
|
|
84
|
+
"""
|
|
85
|
+
Merge multiple materials into a single material.
|
|
86
|
+
|
|
87
|
+
If some of the atoms are considered too close within a tolerance, only the last atom is kept.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
materials (List[Material]): List of materials to merge.
|
|
91
|
+
material_name (Optional[str]): Name of the merged material.
|
|
92
|
+
distance_tolerance (float): The tolerance to replace atoms that are considered too close with respect
|
|
93
|
+
to the coordinates in the last material in the list, in angstroms.
|
|
94
|
+
merge_dangerously (bool): If True, the lattices are merged "as is" with no sanity checks.
|
|
95
|
+
Returns:
|
|
96
|
+
Material: The merged material.
|
|
97
|
+
"""
|
|
81
98
|
merged_material = materials[0]
|
|
82
99
|
for material in materials[1:]:
|
|
83
100
|
merged_material = merge_two_materials(
|
|
@@ -85,3 +102,77 @@ def merge_materials(
|
|
|
85
102
|
)
|
|
86
103
|
|
|
87
104
|
return merged_material
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def double_and_filter_material(material: Material, start: List[float], end: List[float]) -> Material:
|
|
108
|
+
"""
|
|
109
|
+
Double the material and filter it by a box defined by the start and end coordinates.
|
|
110
|
+
Args:
|
|
111
|
+
material (Material): The material to double and filter.
|
|
112
|
+
start (List[float]): The start coordinates of the box.
|
|
113
|
+
end (List[float]): The end coordinates of the box.
|
|
114
|
+
Returns:
|
|
115
|
+
Material: The filtered material.
|
|
116
|
+
"""
|
|
117
|
+
material_doubled = create_supercell(material, scaling_factor=[2, 1, 1])
|
|
118
|
+
return filter_by_box(material_doubled, start, end)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def expand_lattice_vectors(material: Material, gap: float, direction: int = 0) -> Material:
|
|
122
|
+
"""
|
|
123
|
+
Expand the lattice vectors of the material in the specified direction by the given gap.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
material (Material): The material whose lattice vectors are to be expanded.
|
|
127
|
+
gap (float): The gap by which to expand the lattice vector.
|
|
128
|
+
direction (int): The index of the lattice vector to expand (0, 1, or 2).
|
|
129
|
+
"""
|
|
130
|
+
new_lattice_vectors = material.lattice.vector_arrays
|
|
131
|
+
new_lattice_vectors[direction][direction] += gap
|
|
132
|
+
material.set_new_lattice_vectors(
|
|
133
|
+
lattice_vector1=new_lattice_vectors[0],
|
|
134
|
+
lattice_vector2=new_lattice_vectors[1],
|
|
135
|
+
lattice_vector3=new_lattice_vectors[2],
|
|
136
|
+
)
|
|
137
|
+
return material
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def stack_two_materials_xy(
|
|
141
|
+
phase_1_material: Material,
|
|
142
|
+
phase_2_material: Material,
|
|
143
|
+
gap: float,
|
|
144
|
+
edge_inclusion_tolerance: Optional[float] = 1.0,
|
|
145
|
+
distance_tolerance: float = 1.0,
|
|
146
|
+
) -> Material:
|
|
147
|
+
"""
|
|
148
|
+
Stack two materials laterally with translation along x-axis with a gap between them.
|
|
149
|
+
|
|
150
|
+
Works correctly only for materials with the same lattice vectors (commensurate lattices).
|
|
151
|
+
Args:
|
|
152
|
+
phase_1_material (Material): The first material.
|
|
153
|
+
phase_2_material (Material): The second material.
|
|
154
|
+
gap (float): The gap between the two materials, in angstroms.
|
|
155
|
+
edge_inclusion_tolerance (float): The tolerance to include atoms on the edge of the phase, in angstroms.
|
|
156
|
+
distance_tolerance (float): The distance tolerance to remove atoms that are too close, in angstroms.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Material: The merged material.
|
|
160
|
+
"""
|
|
161
|
+
edge_inclusion_tolerance_crystal = abs(
|
|
162
|
+
phase_1_material.basis.cell.convert_point_to_crystal([edge_inclusion_tolerance, 0, 0])[0]
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
phase_1_material = double_and_filter_material(
|
|
166
|
+
phase_1_material, [0 - edge_inclusion_tolerance_crystal, 0, 0], [0.5 + edge_inclusion_tolerance_crystal, 1, 1]
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
phase_2_material = double_and_filter_material(
|
|
170
|
+
phase_2_material, [0.5 - edge_inclusion_tolerance_crystal, 0, 0], [1 + edge_inclusion_tolerance_crystal, 1, 1]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
phase_1_material = expand_lattice_vectors(phase_1_material, gap)
|
|
174
|
+
phase_2_material = expand_lattice_vectors(phase_2_material, gap)
|
|
175
|
+
|
|
176
|
+
phase_2_material = translate_by_vector(phase_2_material, [gap / 2, 0, 0], use_cartesian_coordinates=True)
|
|
177
|
+
interface = merge_materials([phase_1_material, phase_2_material], distance_tolerance=distance_tolerance)
|
|
178
|
+
return interface
|
|
@@ -2,12 +2,17 @@ from mat3ra.made.material import Material
|
|
|
2
2
|
from mat3ra.made.tools.build.grain_boundary import (
|
|
3
3
|
SlabGrainBoundaryBuilder,
|
|
4
4
|
SlabGrainBoundaryConfiguration,
|
|
5
|
+
SurfaceGrainBoundaryBuilder,
|
|
6
|
+
SurfaceGrainBoundaryBuilderParameters,
|
|
7
|
+
SurfaceGrainBoundaryConfiguration,
|
|
5
8
|
create_grain_boundary,
|
|
6
9
|
)
|
|
7
|
-
from mat3ra.made.tools.build.
|
|
10
|
+
from mat3ra.made.tools.build.grain_boundary.builders import SlabGrainBoundaryBuilderParameters
|
|
8
11
|
from mat3ra.made.tools.build.slab import SlabConfiguration, get_terminations
|
|
9
12
|
from mat3ra.utils import assertion as assertion_utils
|
|
10
13
|
|
|
14
|
+
from .fixtures import GRAPHENE
|
|
15
|
+
|
|
11
16
|
|
|
12
17
|
def test_slab_grain_boundary_builder():
|
|
13
18
|
material = Material(Material.default_config)
|
|
@@ -44,7 +49,7 @@ def test_slab_grain_boundary_builder():
|
|
|
44
49
|
slab_configuration=slab_config,
|
|
45
50
|
)
|
|
46
51
|
|
|
47
|
-
builder_params =
|
|
52
|
+
builder_params = SlabGrainBoundaryBuilderParameters()
|
|
48
53
|
builder = SlabGrainBoundaryBuilder(build_parameters=builder_params)
|
|
49
54
|
gb = create_grain_boundary(config, builder)
|
|
50
55
|
expected_lattice_vectors = [
|
|
@@ -57,3 +62,30 @@ def test_slab_grain_boundary_builder():
|
|
|
57
62
|
assert len(gb.basis.elements.values) == 32
|
|
58
63
|
assertion_utils.assert_deep_almost_equal(expected_coordinate_15, gb.basis.coordinates.values[15])
|
|
59
64
|
assertion_utils.assert_deep_almost_equal(expected_lattice_vectors, gb.lattice.vector_arrays)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_create_surface_grain_boundary():
|
|
68
|
+
config = SurfaceGrainBoundaryConfiguration(
|
|
69
|
+
film=Material(GRAPHENE),
|
|
70
|
+
twist_angle=13.0,
|
|
71
|
+
gap=2.0,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
builder_params = SurfaceGrainBoundaryBuilderParameters(
|
|
75
|
+
max_repetition_int=5,
|
|
76
|
+
angle_tolerance=0.5,
|
|
77
|
+
return_first_match=True,
|
|
78
|
+
distance_tolerance=1.0,
|
|
79
|
+
)
|
|
80
|
+
builder = SurfaceGrainBoundaryBuilder(build_parameters=builder_params)
|
|
81
|
+
|
|
82
|
+
gb = builder.get_materials(config)
|
|
83
|
+
|
|
84
|
+
expected_cell_vectors = [
|
|
85
|
+
[23.509344266, 0.0, 0.0],
|
|
86
|
+
[5.377336066500001, 9.313819276550575, 0.0],
|
|
87
|
+
[0.0, 0.0, 20.0],
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
assert len(gb) == 1
|
|
91
|
+
assertion_utils.assert_deep_almost_equal(expected_cell_vectors, gb[0].basis.cell.vectors_as_array)
|
|
@@ -9,8 +9,8 @@ from mat3ra.made.tools.build.interface import (
|
|
|
9
9
|
create_interfaces,
|
|
10
10
|
)
|
|
11
11
|
from mat3ra.made.tools.build.interface.builders import (
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
CommensurateLatticeTwistedInterfaceBuilder,
|
|
13
|
+
CommensurateLatticeTwistedInterfaceBuilderParameters,
|
|
14
14
|
NanoRibbonTwistedInterfaceBuilder,
|
|
15
15
|
NanoRibbonTwistedInterfaceConfiguration,
|
|
16
16
|
TwistedInterfaceConfiguration,
|
|
@@ -72,10 +72,10 @@ def test_create_commensurate_supercell_twisted_interface():
|
|
|
72
72
|
film = Material(GRAPHENE)
|
|
73
73
|
substrate = Material(GRAPHENE)
|
|
74
74
|
config = TwistedInterfaceConfiguration(film=film, substrate=substrate, twist_angle=13, distance_z=3.0)
|
|
75
|
-
params =
|
|
75
|
+
params = CommensurateLatticeTwistedInterfaceBuilderParameters(
|
|
76
76
|
max_repetition_int=5, angle_tolerance=0.5, return_first_match=True
|
|
77
77
|
)
|
|
78
|
-
builder =
|
|
78
|
+
builder = CommensurateLatticeTwistedInterfaceBuilder(build_parameters=params)
|
|
79
79
|
interfaces = builder.get_materials(config, post_process_parameters=config)
|
|
80
80
|
assert len(interfaces) == 1
|
|
81
81
|
interface = interfaces[0]
|