@mat3ra/made 2024.9.25-0 → 2024.9.28-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/__init__.py +16 -1
- package/src/py/mat3ra/made/tools/build/grain_boundary/__init__.py +24 -0
- package/src/py/mat3ra/made/tools/build/grain_boundary/builders.py +80 -0
- package/src/py/mat3ra/made/tools/build/grain_boundary/configuration.py +40 -0
- package/src/py/mat3ra/made/tools/build/interface/builders.py +3 -3
- package/src/py/mat3ra/made/tools/build/slab/configuration.py +3 -4
- package/tests/py/unit/test_tools_build_grain_boundary.py +59 -0
package/package.json
CHANGED
|
@@ -23,6 +23,15 @@ class BaseConfiguration(BaseModel, InMemoryEntity):
|
|
|
23
23
|
raise NotImplementedError
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
class BaseSelectorParameters(BaseModel):
|
|
27
|
+
default_index: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseBuilderParameters(BaseModel):
|
|
31
|
+
class Config:
|
|
32
|
+
arbitrary_types_allowed = True
|
|
33
|
+
|
|
34
|
+
|
|
26
35
|
class BaseBuilder(BaseModel):
|
|
27
36
|
"""
|
|
28
37
|
Base class for material builders.
|
|
@@ -48,6 +57,9 @@ class BaseBuilder(BaseModel):
|
|
|
48
57
|
- `_PostProcessParametersType`: The data structure model for the post-process parameters.
|
|
49
58
|
"""
|
|
50
59
|
|
|
60
|
+
class Config:
|
|
61
|
+
arbitrary_types_allowed = True
|
|
62
|
+
|
|
51
63
|
build_parameters: Any = None
|
|
52
64
|
_BuildParametersType: Any = None
|
|
53
65
|
_DefaultBuildParameters: Any = None
|
|
@@ -56,6 +68,7 @@ class BaseBuilder(BaseModel):
|
|
|
56
68
|
_GeneratedItemType: Any = Any
|
|
57
69
|
_SelectorParametersType: Any = None
|
|
58
70
|
_PostProcessParametersType: Any = None
|
|
71
|
+
selector_parameters: Any = BaseSelectorParameters()
|
|
59
72
|
|
|
60
73
|
def __init__(self, build_parameters: _BuildParametersType = None):
|
|
61
74
|
super().__init__(build_parameters=build_parameters)
|
|
@@ -115,7 +128,9 @@ class BaseBuilder(BaseModel):
|
|
|
115
128
|
selector_parameters: Optional[_SelectorParametersType] = None,
|
|
116
129
|
post_process_parameters: Optional[_PostProcessParametersType] = None,
|
|
117
130
|
) -> Material:
|
|
118
|
-
return self.get_materials(configuration, selector_parameters, post_process_parameters)[
|
|
131
|
+
return self.get_materials(configuration, selector_parameters, post_process_parameters)[
|
|
132
|
+
self.selector_parameters.default_index
|
|
133
|
+
]
|
|
119
134
|
|
|
120
135
|
def _update_material_name(self, material, configuration) -> Material:
|
|
121
136
|
# Do nothing by default
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from mat3ra.made.material import Material
|
|
4
|
+
|
|
5
|
+
from .builders import SlabGrainBoundaryBuilder
|
|
6
|
+
from .configuration import SlabGrainBoundaryConfiguration
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_grain_boundary(
|
|
10
|
+
configuration: SlabGrainBoundaryConfiguration,
|
|
11
|
+
builder: Optional[SlabGrainBoundaryBuilder] = None,
|
|
12
|
+
) -> Material:
|
|
13
|
+
"""
|
|
14
|
+
Create a grain boundary according to provided configuration with selected builder.
|
|
15
|
+
Args:
|
|
16
|
+
configuration (SlabGrainBoundaryConfiguration): The configuration of the grain boundary.
|
|
17
|
+
builder (Optional[SlabGrainBoundaryBuilder]): The builder to use for creating the grain boundary.
|
|
18
|
+
Returns:
|
|
19
|
+
Material: The material with the grain boundary.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
if builder is None:
|
|
23
|
+
builder = SlabGrainBoundaryBuilder()
|
|
24
|
+
return builder.get_material(configuration)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from mat3ra.made.material import Material
|
|
5
|
+
|
|
6
|
+
from ..slab import SlabConfiguration, get_terminations, create_slab
|
|
7
|
+
from ...analyze import get_chemical_formula
|
|
8
|
+
from ..interface import ZSLStrainMatchingInterfaceBuilderParameters, InterfaceConfiguration
|
|
9
|
+
from ..interface.builders import ZSLStrainMatchingInterfaceBuilder
|
|
10
|
+
from ..supercell import create_supercell
|
|
11
|
+
from .configuration import SlabGrainBoundaryConfiguration
|
|
12
|
+
from ...third_party import PymatgenInterface
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SlabGrainBoundaryBuilderParameters(ZSLStrainMatchingInterfaceBuilderParameters):
|
|
16
|
+
default_index: int = 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SlabGrainBoundaryBuilder(ZSLStrainMatchingInterfaceBuilder):
|
|
20
|
+
"""
|
|
21
|
+
A builder for creating grain boundaries.
|
|
22
|
+
|
|
23
|
+
The grain boundary is created by:
|
|
24
|
+
1. creating an interface between two phases,
|
|
25
|
+
2. then rotating the interface by 90 degrees.
|
|
26
|
+
3. Finally, creating a slab from the rotated interface.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
_BuildParametersType: type(SlabGrainBoundaryBuilderParameters) = SlabGrainBoundaryBuilderParameters # type: ignore
|
|
30
|
+
_ConfigurationType: type(SlabGrainBoundaryConfiguration) = SlabGrainBoundaryConfiguration # type: ignore
|
|
31
|
+
_GeneratedItemType: PymatgenInterface = PymatgenInterface # type: ignore
|
|
32
|
+
selector_parameters: type( # type: ignore
|
|
33
|
+
SlabGrainBoundaryBuilderParameters
|
|
34
|
+
) = SlabGrainBoundaryBuilderParameters() # type: ignore
|
|
35
|
+
|
|
36
|
+
def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: # type: ignore
|
|
37
|
+
interface_config = InterfaceConfiguration(
|
|
38
|
+
film_configuration=configuration.phase_1_configuration,
|
|
39
|
+
substrate_configuration=configuration.phase_2_configuration,
|
|
40
|
+
film_termination=configuration.phase_1_termination,
|
|
41
|
+
substrate_termination=configuration.phase_2_termination,
|
|
42
|
+
distance_z=configuration.gap,
|
|
43
|
+
vacuum=configuration.gap,
|
|
44
|
+
)
|
|
45
|
+
return super()._generate(interface_config)
|
|
46
|
+
|
|
47
|
+
def _finalize(self, materials: List[Material], configuration: _ConfigurationType) -> List[Material]:
|
|
48
|
+
rot_90_degree_matrix = [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]
|
|
49
|
+
rotated_interfaces = [
|
|
50
|
+
create_supercell(material, supercell_matrix=rot_90_degree_matrix) for material in materials
|
|
51
|
+
]
|
|
52
|
+
final_slabs: List[Material] = []
|
|
53
|
+
for interface in rotated_interfaces:
|
|
54
|
+
supercell_matrix = np.zeros((3, 3))
|
|
55
|
+
supercell_matrix[:2, :2] = configuration.slab_configuration.xy_supercell_matrix
|
|
56
|
+
supercell_matrix[2, 2] = configuration.slab_configuration.thickness
|
|
57
|
+
final_slab_config = SlabConfiguration(
|
|
58
|
+
bulk=interface,
|
|
59
|
+
vacuum=configuration.slab_configuration.vacuum,
|
|
60
|
+
miller_indices=configuration.slab_configuration.miller_indices,
|
|
61
|
+
thickness=configuration.slab_configuration.thickness,
|
|
62
|
+
use_conventional_cell=False, # Keep false to prevent Pymatgen from simplifying the interface
|
|
63
|
+
use_orthogonal_z=True,
|
|
64
|
+
)
|
|
65
|
+
termination = configuration.slab_termination or get_terminations(final_slab_config)[0]
|
|
66
|
+
final_slab = create_slab(final_slab_config, termination)
|
|
67
|
+
final_slabs.append(final_slab)
|
|
68
|
+
|
|
69
|
+
return super()._finalize(final_slabs, configuration)
|
|
70
|
+
|
|
71
|
+
def _update_material_name(self, material: Material, configuration: _ConfigurationType) -> Material:
|
|
72
|
+
phase_1_formula = get_chemical_formula(configuration.phase_1_configuration.bulk)
|
|
73
|
+
phase_2_formula = get_chemical_formula(configuration.phase_2_configuration.bulk)
|
|
74
|
+
phase_1_miller_indices = "".join([str(i) for i in configuration.phase_1_configuration.miller_indices])
|
|
75
|
+
phase_2_miller_indices = "".join([str(i) for i in configuration.phase_2_configuration.miller_indices])
|
|
76
|
+
new_name = (
|
|
77
|
+
f"{phase_1_formula}({phase_1_miller_indices})-{phase_2_formula}({phase_2_miller_indices}), Grain Boundary"
|
|
78
|
+
)
|
|
79
|
+
material.name = new_name
|
|
80
|
+
return material
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .. import BaseConfiguration
|
|
4
|
+
from ..slab.configuration import SlabConfiguration
|
|
5
|
+
from ..slab.termination import Termination
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SlabGrainBoundaryConfiguration(BaseConfiguration):
|
|
9
|
+
"""
|
|
10
|
+
Configuration for a grain boundary between two phases with different surfaces facing each other.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
phase_1_configuration (SlabConfiguration): The configuration of the first phase.
|
|
14
|
+
phase_2_configuration (SlabConfiguration): The configuration of the second phase.
|
|
15
|
+
phase_1_termination (Termination): The termination of the first phase.
|
|
16
|
+
phase_2_termination (Termination): The termination of the second phase.
|
|
17
|
+
gap (float): The gap between the two phases, in Angstroms.
|
|
18
|
+
slab_configuration (SlabConfiguration): The configuration of the grain boundary slab.
|
|
19
|
+
slab_termination (Optional[Termination]): The termination of the grain boundary slab.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
phase_1_configuration: SlabConfiguration
|
|
23
|
+
phase_2_configuration: SlabConfiguration
|
|
24
|
+
phase_1_termination: Termination
|
|
25
|
+
phase_2_termination: Termination
|
|
26
|
+
gap: float = 3.0
|
|
27
|
+
slab_configuration: SlabConfiguration
|
|
28
|
+
slab_termination: Optional[Termination] = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def _json(self):
|
|
32
|
+
return {
|
|
33
|
+
"type": self.__class__.__name__,
|
|
34
|
+
"phase_1_configuration": self.phase_1_configuration.to_json(),
|
|
35
|
+
"phase_2_configuration": self.phase_2_configuration.to_json(),
|
|
36
|
+
"phase_1_termination": str(self.phase_1_termination),
|
|
37
|
+
"phase_2_termination": str(self.phase_2_termination),
|
|
38
|
+
"gap": self.gap,
|
|
39
|
+
"slab_configuration": self.slab_configuration.to_json(),
|
|
40
|
+
}
|
|
@@ -18,7 +18,7 @@ from ...modify import (
|
|
|
18
18
|
)
|
|
19
19
|
from ...analyze import get_chemical_formula
|
|
20
20
|
from ...convert import to_ase, from_ase, to_pymatgen, PymatgenInterface, ASEAtoms
|
|
21
|
-
from ...build import BaseBuilder
|
|
21
|
+
from ...build import BaseBuilder, BaseBuilderParameters
|
|
22
22
|
from ..nanoribbon import NanoribbonConfiguration, create_nanoribbon
|
|
23
23
|
from ..supercell import create_supercell
|
|
24
24
|
from ..slab import create_slab, Termination, SlabConfiguration
|
|
@@ -120,7 +120,7 @@ class SimpleInterfaceBuilder(ConvertGeneratedItemsASEAtomsMixin, InterfaceBuilde
|
|
|
120
120
|
########################################################################################
|
|
121
121
|
# Strain Matching Interface Builders #
|
|
122
122
|
########################################################################################
|
|
123
|
-
class StrainMatchingInterfaceBuilderParameters(
|
|
123
|
+
class StrainMatchingInterfaceBuilderParameters(BaseBuilderParameters):
|
|
124
124
|
strain_matching_parameters: Optional[Any] = None
|
|
125
125
|
|
|
126
126
|
|
|
@@ -144,7 +144,7 @@ class ZSLStrainMatchingParameters(BaseModel):
|
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
class ZSLStrainMatchingInterfaceBuilderParameters(StrainMatchingInterfaceBuilderParameters):
|
|
147
|
-
strain_matching_parameters: ZSLStrainMatchingParameters
|
|
147
|
+
strain_matching_parameters: ZSLStrainMatchingParameters = ZSLStrainMatchingParameters()
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
class ZSLStrainMatchingInterfaceBuilder(ConvertGeneratedItemsPymatgenStructureMixin, StrainMatchingInterfaceBuilder):
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
from typing import List, Tuple, Any
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
from mat3ra.code.entity import InMemoryEntity
|
|
7
4
|
|
|
8
5
|
from mat3ra.made.material import Material
|
|
6
|
+
|
|
7
|
+
from .. import BaseConfiguration
|
|
9
8
|
from ...third_party import PymatgenSpacegroupAnalyzer
|
|
10
9
|
from ...convert import to_pymatgen, from_pymatgen
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
class SlabConfiguration(
|
|
12
|
+
class SlabConfiguration(BaseConfiguration):
|
|
14
13
|
"""
|
|
15
14
|
Configuration for building a slab.
|
|
16
15
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from mat3ra.made.material import Material
|
|
2
|
+
from mat3ra.made.tools.build.grain_boundary import (
|
|
3
|
+
SlabGrainBoundaryBuilder,
|
|
4
|
+
SlabGrainBoundaryConfiguration,
|
|
5
|
+
create_grain_boundary,
|
|
6
|
+
)
|
|
7
|
+
from mat3ra.made.tools.build.interface import ZSLStrainMatchingInterfaceBuilderParameters
|
|
8
|
+
from mat3ra.made.tools.build.slab import SlabConfiguration, get_terminations
|
|
9
|
+
from mat3ra.utils import assertion as assertion_utils
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_slab_grain_boundary_builder():
|
|
13
|
+
material = Material(Material.default_config)
|
|
14
|
+
phase_1_configuration = SlabConfiguration(
|
|
15
|
+
bulk=material,
|
|
16
|
+
vacuum=0,
|
|
17
|
+
thickness=2,
|
|
18
|
+
miller_indices=(0, 0, 1),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
phase_2_configuration = SlabConfiguration(
|
|
22
|
+
bulk=material,
|
|
23
|
+
vacuum=0,
|
|
24
|
+
thickness=2,
|
|
25
|
+
miller_indices=(0, 0, 1),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
termination1 = get_terminations(phase_1_configuration)[0]
|
|
29
|
+
termination2 = get_terminations(phase_2_configuration)[0]
|
|
30
|
+
|
|
31
|
+
slab_config = SlabConfiguration(
|
|
32
|
+
vacuum=1,
|
|
33
|
+
miller_indices=(0, 0, 1),
|
|
34
|
+
thickness=2,
|
|
35
|
+
xy_supercell_matrix=[[1, 0], [0, 1]],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
config = SlabGrainBoundaryConfiguration(
|
|
39
|
+
phase_1_configuration=phase_1_configuration,
|
|
40
|
+
phase_2_configuration=phase_2_configuration,
|
|
41
|
+
phase_1_termination=termination1,
|
|
42
|
+
phase_2_termination=termination2,
|
|
43
|
+
gap=3.0,
|
|
44
|
+
slab_configuration=slab_config,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
builder_params = ZSLStrainMatchingInterfaceBuilderParameters(max_area=50)
|
|
48
|
+
builder = SlabGrainBoundaryBuilder(build_parameters=builder_params)
|
|
49
|
+
gb = create_grain_boundary(config, builder)
|
|
50
|
+
expected_lattice_vectors = [
|
|
51
|
+
[25.140673461, 0.0, 0.0],
|
|
52
|
+
[0.0, 3.867, 0.0],
|
|
53
|
+
[0.0, 0.0, 11.601],
|
|
54
|
+
]
|
|
55
|
+
expected_coordinate_15 = [0.777190818, 0.0, 0.083333333]
|
|
56
|
+
|
|
57
|
+
assert len(gb.basis.elements.values) == 32
|
|
58
|
+
assertion_utils.assert_deep_almost_equal(expected_coordinate_15, gb.basis.coordinates.values[15])
|
|
59
|
+
assertion_utils.assert_deep_almost_equal(expected_lattice_vectors, gb.lattice.vector_arrays)
|