@mat3ra/made 2024.5.15-0 → 2024.6.3-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": "2024.5.15-0",
3
+ "version": "2024.6.3-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",
@@ -55,3 +55,17 @@ def get_surface_area(atoms: Atoms):
55
55
  matrix = atoms.cell
56
56
  cross_product = np.cross(matrix[0], matrix[1])
57
57
  return np.linalg.norm(cross_product)
58
+
59
+
60
+ @decorator_convert_material_args_kwargs_to_atoms
61
+ def get_chemical_formula(atoms: Atoms):
62
+ """
63
+ Calculate the formula of the atoms structure.
64
+
65
+ Args:
66
+ atoms (ase.Atoms): The Atoms object to calculate the formula of.
67
+
68
+ Returns:
69
+ str: The formula of the atoms.
70
+ """
71
+ return atoms.get_chemical_formula()
@@ -1,80 +1,101 @@
1
+ from typing import List, Optional, Any
2
+
3
+ from pydantic import BaseModel
4
+
1
5
  from ...material import Material
2
- from .interface import InterfaceDataHolder, CoherentInterfaceBuilder, TerminationType
3
- from .interface import InterfaceSettings as Settings
4
- from .interface import interface_init_zsl_builder, interface_patch_with_mean_abs_strain
5
- from ..convert import decorator_convert_material_args_kwargs_to_structure
6
- from ..modify import translate_to_bottom, wrap_to_unit_cell
7
- from typing import Optional
8
-
9
-
10
- @decorator_convert_material_args_kwargs_to_structure
11
- def create_interfaces(
12
- substrate: Optional[Material] = None,
13
- layer: Optional[Material] = None,
14
- settings: Settings = Settings(),
15
- sort_by_strain_and_size: bool = True,
16
- remove_duplicates: bool = True,
17
- is_logging_enabled: bool = True,
18
- interface_builder: Optional[CoherentInterfaceBuilder] = None,
19
- termination: Optional[TerminationType] = None,
20
- ) -> InterfaceDataHolder:
6
+
7
+
8
+ class BaseBuilder(BaseModel):
21
9
  """
22
- Create all interfaces between the substrate and layer structures using ZSL algorithm provided by pymatgen.
23
-
24
- Args:
25
- substrate (Material): The substrate structure.
26
- layer (Material): The layer structure.
27
- settings: The settings for the interface generation.
28
- sort_by_strain_and_size (bool): Whether to sort the interfaces by strain and size.
29
- remove_duplicates (bool): Whether to remove duplicate interfaces.
30
- is_logging_enabled (bool): Whether to enable debug print.
31
- Returns:
32
- InterfaceDataHolder.
10
+ Base class for material builders.
11
+ This class provides an interface for generating materials and getter functions.
12
+ The builder is meant as a description of the process, while its functions require a
13
+ "Configuration" class instance to perform the generation.
14
+
15
+ The class is designed to be subclassed and the subclass should implement the following methods:
16
+
17
+ - `_generate`: Generate the material items, possibly using third-party tools/implementation for items.
18
+ - `_sort`: Sort the items.
19
+ - `_select`: Select a subset of the items.
20
+ - `_post_process`: Post-process the items to convert them to materials (Material class).
21
+ - `_finalize`: Finalize the materials.
22
+
23
+ The subclass should also define the following attributes:
24
+
25
+ - `_BuildParametersType`: The data structure model for the build parameters.
26
+ - `_DefaultBuildParameters`: The default build parameters.
27
+ - `_ConfigurationType`: The data structure model for the Configuration used during the build.
28
+ - `_GeneratedItemType`: The type of the generated item.
29
+ - `_SelectorParametersType`: The data structure model for the selector parameters.
30
+ - `_PostProcessParametersType`: The data structure model for the post-process parameters.
33
31
  """
34
- if is_logging_enabled:
35
- print("Creating interfaces...")
36
-
37
- builder = interface_builder or init_interface_builder(substrate, layer, settings)
38
- interfaces_data = InterfaceDataHolder()
39
-
40
- if termination is not None:
41
- builder.terminations = [termination]
42
-
43
- for termination in builder.terminations:
44
- all_interfaces_for_termination = builder.get_interfaces(
45
- termination,
46
- gap=settings.distance_z,
47
- film_thickness=settings.LayerParameters.thickness,
48
- substrate_thickness=settings.SubstrateParameters.thickness,
49
- in_layers=True,
50
- )
51
-
52
- all_interfaces_for_termination_patched_wrapped = list(
53
- map(
54
- lambda i: wrap_to_unit_cell(interface_patch_with_mean_abs_strain(i)),
55
- all_interfaces_for_termination,
56
- )
57
- )
58
-
59
- interfaces_data.add_data_entries(
60
- all_interfaces_for_termination_patched_wrapped,
61
- sort_interfaces_by_strain_and_size=sort_by_strain_and_size,
62
- remove_duplicates=remove_duplicates,
63
- )
64
-
65
- if is_logging_enabled:
66
- unique_str = "unique" if remove_duplicates else ""
67
- print(f"Found {len(interfaces_data.get_interfaces_for_termination(0))} {unique_str} interfaces.")
68
-
69
- return interfaces_data
70
-
71
-
72
- @decorator_convert_material_args_kwargs_to_structure
73
- def init_interface_builder(
74
- substrate: Material,
75
- layer: Material,
76
- settings: Settings,
77
- ) -> CoherentInterfaceBuilder:
78
- substrate = translate_to_bottom(substrate, settings.use_conventional_cell)
79
- layer = translate_to_bottom(layer, settings.use_conventional_cell)
80
- return interface_init_zsl_builder(substrate, layer, settings)
32
+
33
+ build_parameters: Any = None
34
+ _BuildParametersType: Any = None
35
+ _DefaultBuildParameters: Any = None
36
+
37
+ _ConfigurationType: Any = Any
38
+ _GeneratedItemType: Any = Any
39
+ _SelectorParametersType: Any = None
40
+ _PostProcessParametersType: Any = None
41
+
42
+ def __init__(self, build_parameters: _BuildParametersType = None):
43
+ super().__init__(build_parameters=build_parameters)
44
+ self.build_parameters = build_parameters or self._DefaultBuildParameters
45
+ self.__generated_items: List[List[BaseBuilder._GeneratedItemType]] = []
46
+ self.__configurations: List[BaseBuilder._ConfigurationType] = []
47
+
48
+ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]:
49
+ return []
50
+
51
+ def _generate_or_get_from_cache(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]:
52
+ if configuration not in self.__configurations:
53
+ self.__configurations.append(configuration)
54
+ self.__generated_items.append(self._generate(configuration))
55
+ return self.__generated_items[self.__configurations.index(configuration)]
56
+
57
+ def _sort(self, items: List[_GeneratedItemType]) -> List[_GeneratedItemType]:
58
+ return items
59
+
60
+ def _select(
61
+ self, items: List[_GeneratedItemType], selector_parameters: Optional[_SelectorParametersType]
62
+ ) -> List[_GeneratedItemType]:
63
+ return items
64
+
65
+ def _post_process(
66
+ self, items: List[_GeneratedItemType], post_process_parameters: Optional[_PostProcessParametersType]
67
+ ) -> List[Material]:
68
+ return [Material(self._convert_generated_item(item)) for item in items]
69
+
70
+ @staticmethod
71
+ def _convert_generated_item(item: _GeneratedItemType):
72
+ material_config = item
73
+ return material_config
74
+
75
+ def _finalize(self, materials: List[Material], configuration: _ConfigurationType) -> List[Material]:
76
+ return [self._update_material_name(material, configuration) for material in materials]
77
+
78
+ def get_materials(
79
+ self,
80
+ configuration: _ConfigurationType,
81
+ selector_parameters: Optional[_SelectorParametersType] = None,
82
+ post_process_parameters: Optional[_PostProcessParametersType] = None,
83
+ ) -> List[Material]:
84
+ generated_items = self._generate_or_get_from_cache(configuration)
85
+ sorted_items = self._sort(generated_items)
86
+ selected_items = self._select(sorted_items, selector_parameters)
87
+ materials = self._post_process(selected_items, post_process_parameters)
88
+ finalized_materials = self._finalize(materials, configuration)
89
+ return finalized_materials
90
+
91
+ def get_material(
92
+ self,
93
+ configuration: _ConfigurationType,
94
+ selector_parameters: Optional[_SelectorParametersType] = None,
95
+ post_process_parameters: Optional[_PostProcessParametersType] = None,
96
+ ) -> Material:
97
+ return self.get_materials(configuration, selector_parameters, post_process_parameters)[0]
98
+
99
+ def _update_material_name(self, material, configuration):
100
+ # Do nothing by default
101
+ return material
@@ -0,0 +1,26 @@
1
+ from typing import Union, List, Optional
2
+
3
+ from mat3ra.made.material import Material
4
+ from .builders import (
5
+ SimpleInterfaceBuilder,
6
+ SimpleInterfaceBuilderParameters,
7
+ ZSLStrainMatchingParameters,
8
+ ZSLStrainMatchingInterfaceBuilder,
9
+ ZSLStrainMatchingInterfaceBuilderParameters,
10
+ )
11
+ from .configuration import InterfaceConfiguration
12
+
13
+
14
+ def create_interfaces(
15
+ builder: Union[SimpleInterfaceBuilder, ZSLStrainMatchingInterfaceBuilder], configuration: InterfaceConfiguration
16
+ ) -> List[Material]:
17
+ return builder.get_materials(configuration)
18
+
19
+
20
+ def create_interface(
21
+ configuration: InterfaceConfiguration,
22
+ builder: Optional[Union[SimpleInterfaceBuilder, ZSLStrainMatchingInterfaceBuilder]] = None,
23
+ ) -> Material:
24
+ if builder is None:
25
+ builder = SimpleInterfaceBuilder(build_parameters=SimpleInterfaceBuilderParameters())
26
+ return builder.get_material(configuration)
@@ -0,0 +1,185 @@
1
+ from typing import Any, List, Optional
2
+
3
+ import numpy as np
4
+ from pydantic import BaseModel
5
+ from ase.build.tools import niggli_reduce
6
+ from pymatgen.analysis.interfaces.coherent_interfaces import (
7
+ CoherentInterfaceBuilder,
8
+ ZSLGenerator,
9
+ )
10
+
11
+ from mat3ra.made.material import Material
12
+ from .enums import StrainModes
13
+ from .configuration import InterfaceConfiguration
14
+ from .termination_pair import TerminationPair, safely_select_termination_pair
15
+ from .utils import interface_patch_with_mean_abs_strain, remove_duplicate_interfaces
16
+ from ..mixins import (
17
+ ConvertGeneratedItemsASEAtomsMixin,
18
+ ConvertGeneratedItemsPymatgenStructureMixin,
19
+ )
20
+ from ..slab import create_slab, Termination
21
+ from ..slab.configuration import SlabConfiguration
22
+ from ...analyze import get_chemical_formula
23
+ from ...convert import to_ase, from_ase, to_pymatgen, PymatgenInterface, ASEAtoms
24
+ from ...build import BaseBuilder
25
+
26
+
27
+ class InterfaceBuilderParameters(BaseModel):
28
+ pass
29
+
30
+
31
+ class InterfaceBuilder(BaseBuilder):
32
+ _BuildParametersType = InterfaceBuilderParameters
33
+ _ConfigurationType: type(InterfaceConfiguration) = InterfaceConfiguration # type: ignore
34
+
35
+ def _update_material_name(self, material: Material, configuration: InterfaceConfiguration) -> Material:
36
+ film_formula = get_chemical_formula(configuration.film_configuration.bulk)
37
+ substrate_formula = get_chemical_formula(configuration.substrate_configuration.bulk)
38
+ film_miller_indices = "".join([str(i) for i in configuration.film_configuration.miller_indices])
39
+ substrate_miller_indices = "".join([str(i) for i in configuration.substrate_configuration.miller_indices])
40
+ new_name = f"{film_formula}({film_miller_indices})-{substrate_formula}({substrate_miller_indices}), Interface"
41
+ material.name = new_name
42
+ return material
43
+
44
+
45
+ ########################################################################################
46
+ # Simple Interface Builder #
47
+ ########################################################################################
48
+ class SimpleInterfaceBuilderParameters(InterfaceBuilderParameters):
49
+ scale_film: bool = True
50
+
51
+
52
+ class SimpleInterfaceBuilder(ConvertGeneratedItemsASEAtomsMixin, InterfaceBuilder):
53
+ """
54
+ Creates matching interface between substrate and film by straining the film to match the substrate.
55
+ """
56
+
57
+ _BuildParametersType = Optional[SimpleInterfaceBuilderParameters]
58
+ _DefaultBuildParameters = SimpleInterfaceBuilderParameters(scale_film=True)
59
+ _GeneratedItemType: type(ASEAtoms) = ASEAtoms # type: ignore
60
+
61
+ @staticmethod
62
+ def __preprocess_slab_configuration(configuration: SlabConfiguration, termination: Termination):
63
+ slab = create_slab(configuration, termination)
64
+ ase_slab = to_ase(slab)
65
+ niggli_reduce(ase_slab)
66
+ return ase_slab
67
+
68
+ @staticmethod
69
+ def __combine_two_slabs_ase(substrate_slab_ase: ASEAtoms, film_slab_ase: ASEAtoms, distance_z: float) -> ASEAtoms:
70
+ max_z_substrate = max(substrate_slab_ase.positions[:, 2])
71
+ min_z_film = min(film_slab_ase.positions[:, 2])
72
+ shift_z = max_z_substrate - min_z_film + distance_z
73
+
74
+ film_slab_ase.translate([0, 0, shift_z])
75
+
76
+ return substrate_slab_ase + film_slab_ase
77
+
78
+ @staticmethod
79
+ def __add_vacuum_along_c_ase(interface_ase: ASEAtoms, vacuum: float) -> ASEAtoms:
80
+ cell_c_with_vacuum = max(interface_ase.positions[:, 2]) + vacuum
81
+ interface_ase.cell[2, 2] = cell_c_with_vacuum
82
+ return interface_ase
83
+
84
+ def _generate(self, configuration: InterfaceBuilder._ConfigurationType) -> List[_GeneratedItemType]: # type: ignore
85
+ film_slab_ase = self.__preprocess_slab_configuration(
86
+ configuration.film_configuration, configuration.film_termination
87
+ )
88
+ substrate_slab_ase = self.__preprocess_slab_configuration(
89
+ configuration.substrate_configuration, configuration.substrate_termination
90
+ )
91
+
92
+ if self.build_parameters.scale_film:
93
+ film_slab_ase.set_cell(substrate_slab_ase.cell, scale_atoms=True)
94
+ film_slab_ase.wrap()
95
+
96
+ interface_ase = self.__combine_two_slabs_ase(substrate_slab_ase, film_slab_ase, configuration.distance_z)
97
+ interface_ase_with_vacuum = self.__add_vacuum_along_c_ase(interface_ase, configuration.vacuum)
98
+
99
+ return [interface_ase_with_vacuum]
100
+
101
+ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters=None) -> List[Material]:
102
+ return [Material(from_ase(slab)) for slab in items]
103
+
104
+
105
+ ########################################################################################
106
+ # Strain Matching Interface Builders #
107
+ ########################################################################################
108
+ class StrainMatchingInterfaceBuilderParameters(BaseModel):
109
+ strain_matching_parameters: Optional[Any] = None
110
+
111
+
112
+ class StrainMatchingInterfaceBuilder(InterfaceBuilder):
113
+ _BuildParametersType = StrainMatchingInterfaceBuilderParameters # type: ignore
114
+
115
+ def _update_material_name(self, material: Material, configuration: InterfaceConfiguration) -> Material:
116
+ updated_material = super()._update_material_name(material, configuration)
117
+ if StrainModes.mean_abs_strain in material.metadata:
118
+ strain = material.metadata[StrainModes.mean_abs_strain]
119
+ new_name = f"{updated_material.name}, Strain {strain*100:.3f}%"
120
+ updated_material.name = new_name
121
+ return material
122
+
123
+
124
+ class ZSLStrainMatchingParameters(BaseModel):
125
+ max_area: float = 50.0
126
+ max_area_ratio_tol: float = 0.09
127
+ max_length_tol: float = 0.03
128
+ max_angle_tol: float = 0.01
129
+
130
+
131
+ class ZSLStrainMatchingInterfaceBuilderParameters(StrainMatchingInterfaceBuilderParameters):
132
+ strain_matching_parameters: ZSLStrainMatchingParameters
133
+
134
+
135
+ class ZSLStrainMatchingInterfaceBuilder(ConvertGeneratedItemsPymatgenStructureMixin, StrainMatchingInterfaceBuilder):
136
+ """
137
+ Creates matching interface between substrate and film using the ZSL algorithm.
138
+ """
139
+
140
+ _BuildParametersType: type( # type: ignore
141
+ ZSLStrainMatchingInterfaceBuilderParameters
142
+ ) = ZSLStrainMatchingInterfaceBuilderParameters # type: ignore
143
+ _GeneratedItemType: PymatgenInterface = PymatgenInterface # type: ignore
144
+
145
+ def _generate(self, configuration: InterfaceConfiguration) -> List[PymatgenInterface]:
146
+ generator = ZSLGenerator(**self.build_parameters.strain_matching_parameters.dict())
147
+ builder = CoherentInterfaceBuilder(
148
+ substrate_structure=to_pymatgen(configuration.substrate_configuration.bulk),
149
+ film_structure=to_pymatgen(configuration.film_configuration.bulk),
150
+ substrate_miller=configuration.substrate_configuration.miller_indices,
151
+ film_miller=configuration.film_configuration.miller_indices,
152
+ zslgen=generator,
153
+ )
154
+
155
+ generated_termination_pairs = [
156
+ TerminationPair.from_pymatgen(pymatgen_termination) for pymatgen_termination in builder.terminations
157
+ ]
158
+ termination_pair = safely_select_termination_pair(configuration.termination_pair, generated_termination_pairs)
159
+ interfaces = builder.get_interfaces(
160
+ termination=termination_pair.to_pymatgen(),
161
+ gap=configuration.distance_z,
162
+ film_thickness=configuration.film_configuration.thickness,
163
+ substrate_thickness=configuration.substrate_configuration.thickness,
164
+ in_layers=True,
165
+ )
166
+
167
+ return list([interface_patch_with_mean_abs_strain(interface) for interface in interfaces])
168
+
169
+ def _sort(self, items: List[_GeneratedItemType]):
170
+ sorted_by_num_sites = sorted(items, key=lambda x: x.num_sites)
171
+ sorted_by_num_sites_and_strain = sorted(
172
+ sorted_by_num_sites, key=lambda x: np.mean(x.interface_properties[StrainModes.mean_abs_strain])
173
+ )
174
+ unique_sorted_interfaces = remove_duplicate_interfaces(
175
+ sorted_by_num_sites_and_strain, strain_mode=StrainModes.mean_abs_strain
176
+ )
177
+ return unique_sorted_interfaces
178
+
179
+ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters=None) -> List[Material]:
180
+ materials = super()._post_process(items, post_process_parameters)
181
+ strains = [interface.interface_properties[StrainModes.mean_abs_strain] for interface in items]
182
+
183
+ for material, strain in zip(materials, strains):
184
+ material.metadata["mean_abs_strain"] = strain
185
+ return materials
@@ -0,0 +1,18 @@
1
+ from pydantic import BaseModel
2
+
3
+ from .termination_pair import TerminationPair
4
+ from ..slab import Termination
5
+ from ..slab.configuration import SlabConfiguration
6
+
7
+
8
+ class InterfaceConfiguration(BaseModel):
9
+ film_configuration: SlabConfiguration
10
+ substrate_configuration: SlabConfiguration
11
+ film_termination: Termination
12
+ substrate_termination: Termination
13
+ distance_z: float = 3.0
14
+ vacuum: float = 5.0
15
+
16
+ @property
17
+ def termination_pair(self):
18
+ return TerminationPair(self.film_termination, self.substrate_termination)
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class StrainModes(str, Enum):
5
+ strain = "strain"
6
+ von_mises_strain = "von_mises_strain"
7
+ mean_abs_strain = "mean_abs_strain"
@@ -0,0 +1,59 @@
1
+ from typing import Tuple, List
2
+ from pydantic import BaseModel
3
+
4
+ from ..slab.termination import Termination
5
+
6
+
7
+ class TerminationPair(BaseModel):
8
+ film_termination: Termination
9
+ substrate_termination: Termination
10
+
11
+ def __str__(self):
12
+ # return str(self.to_tuple_of_str())
13
+ return f"({self.film_termination}, {self.substrate_termination})"
14
+
15
+ def __repr__(self):
16
+ return self.__str__()
17
+
18
+ def __init__(self, film_termination: Termination, substrate_termination: Termination):
19
+ super().__init__(film_termination=film_termination, substrate_termination=substrate_termination)
20
+
21
+ @classmethod
22
+ def from_tuple_of_str(cls, termination_pair: Tuple[str, str]):
23
+ film_termination = Termination.from_string(termination_pair[0])
24
+ substrate_termination = Termination.from_string(termination_pair[1])
25
+ return cls(film_termination=film_termination, substrate_termination=substrate_termination)
26
+
27
+ def to_tuple_of_str(self) -> Tuple[str, str]:
28
+ return str(self.film_termination), str(self.substrate_termination)
29
+
30
+ @classmethod
31
+ def from_pymatgen(cls, termination_pair: Tuple[str, str]):
32
+ return cls.from_tuple_of_str(termination_pair)
33
+
34
+ def to_pymatgen(self) -> Tuple[str, str]:
35
+ return self.to_tuple_of_str()
36
+
37
+
38
+ def safely_select_termination_pair(
39
+ provided_termination_pair: TerminationPair, generated_termination_pairs: List[TerminationPair]
40
+ ) -> TerminationPair:
41
+ """
42
+ Attempt finding provided in generated terminations to find a complete match,
43
+ if match isn't found, get terminations with equivalent chemical elements.
44
+ """
45
+ provided_film_termination = provided_termination_pair.film_termination
46
+ provided_substrate_termination = provided_termination_pair.substrate_termination
47
+ hotfix_termination_pair = provided_termination_pair
48
+ if provided_termination_pair not in generated_termination_pairs:
49
+ for termination_pair in generated_termination_pairs:
50
+ generated_film_termination = termination_pair.film_termination
51
+ generated_substrate_termination = termination_pair.substrate_termination
52
+ if (
53
+ generated_film_termination.chemical_elements == provided_film_termination.chemical_elements
54
+ and generated_substrate_termination.chemical_elements
55
+ == provided_substrate_termination.chemical_elements
56
+ ):
57
+ hotfix_termination_pair = termination_pair
58
+ print("Interface will be built with terminations: ", hotfix_termination_pair)
59
+ return hotfix_termination_pair
@@ -0,0 +1,36 @@
1
+ import types
2
+ from typing import List
3
+ import numpy as np
4
+ from .enums import StrainModes
5
+ from ...convert import PymatgenInterface
6
+
7
+
8
+ def interface_patch_with_mean_abs_strain(target: PymatgenInterface, tolerance: float = 10e-6):
9
+ def get_mean_abs_strain(target):
10
+ return target.interface_properties[StrainModes.mean_abs_strain]
11
+
12
+ target.get_mean_abs_strain = types.MethodType(get_mean_abs_strain, target)
13
+ target.interface_properties[StrainModes.mean_abs_strain] = (
14
+ round(np.mean(np.abs(target.interface_properties["strain"])) / tolerance) * tolerance
15
+ )
16
+ return target
17
+
18
+
19
+ def remove_duplicate_interfaces(
20
+ interfaces: List[PymatgenInterface], strain_mode: StrainModes = StrainModes.mean_abs_strain
21
+ ):
22
+ def are_interfaces_duplicate(interface1: PymatgenInterface, interface2: PymatgenInterface):
23
+ are_sizes_equivalent = interface1.num_sites == interface2.num_sites and np.allclose(
24
+ interface1.interface_properties[strain_mode], interface2.interface_properties[strain_mode]
25
+ )
26
+ are_strains_equivalent = np.allclose(
27
+ interface1.interface_properties[strain_mode], interface2.interface_properties[strain_mode]
28
+ )
29
+ return are_sizes_equivalent and are_strains_equivalent
30
+
31
+ filtered_interfaces = [interfaces[0]] if interfaces else []
32
+
33
+ for interface in interfaces[1:]:
34
+ if not any(are_interfaces_duplicate(interface, unique_interface) for unique_interface in filtered_interfaces):
35
+ filtered_interfaces.append(interface)
36
+ return filtered_interfaces
@@ -0,0 +1,13 @@
1
+ from ..convert import from_ase, from_pymatgen, ASEAtoms, PymatgenStructure
2
+
3
+
4
+ class ConvertGeneratedItemsASEAtomsMixin:
5
+ @staticmethod
6
+ def _convert_generated_item(item: ASEAtoms):
7
+ return from_ase(item)
8
+
9
+
10
+ class ConvertGeneratedItemsPymatgenStructureMixin:
11
+ @staticmethod
12
+ def _convert_generated_item(item: PymatgenStructure):
13
+ return from_pymatgen(item)
@@ -0,0 +1,15 @@
1
+ from typing import List
2
+
3
+ from mat3ra.made.material import Material
4
+ from .builders import SlabBuilder, SlabSelectorParameters
5
+ from .configuration import SlabConfiguration
6
+ from .termination import Termination
7
+
8
+
9
+ def get_terminations(configuration: SlabConfiguration) -> List[Termination]:
10
+ return SlabBuilder().get_terminations(configuration)
11
+
12
+
13
+ def create_slab(configuration: SlabConfiguration, termination: Termination) -> Material:
14
+ builder = SlabBuilder()
15
+ return builder.get_material(configuration, selector_parameters=SlabSelectorParameters(termination=termination))
@@ -0,0 +1,75 @@
1
+ from pymatgen.core.surface import SlabGenerator as PymatgenSlabGenerator
2
+ from ...convert import label_pymatgen_slab_termination
3
+ from typing import List
4
+ from pydantic import BaseModel
5
+
6
+
7
+ from mat3ra.made.material import Material
8
+
9
+ from .termination import Termination
10
+ from ...analyze import get_chemical_formula
11
+ from ...convert import to_pymatgen, PymatgenSlab
12
+ from ...build import BaseBuilder
13
+ from ...build.mixins import ConvertGeneratedItemsPymatgenStructureMixin
14
+ from ..supercell import create_supercell
15
+ from .configuration import SlabConfiguration
16
+
17
+
18
+ class SlabSelectorParameters(BaseModel):
19
+ termination: Termination
20
+
21
+
22
+ class SlabBuilder(ConvertGeneratedItemsPymatgenStructureMixin, BaseBuilder):
23
+ _ConfigurationType: type(SlabConfiguration) = SlabConfiguration # type: ignore
24
+ _GeneratedItemType: PymatgenSlab = PymatgenSlab # type: ignore
25
+ _SelectorParametersType: type(SlabSelectorParameters) = SlabSelectorParameters # type: ignore
26
+ __configuration: SlabConfiguration
27
+
28
+ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: # type: ignore
29
+ generator = PymatgenSlabGenerator(
30
+ initial_structure=to_pymatgen(configuration.bulk),
31
+ miller_index=configuration.miller_indices,
32
+ min_slab_size=configuration.thickness,
33
+ min_vacuum_size=configuration.vacuum,
34
+ in_unit_planes=True,
35
+ reorient_lattice=True,
36
+ )
37
+ raw_slabs = generator.get_slabs()
38
+ self.__configuration = configuration
39
+
40
+ return [self.__conditionally_convert_slab_to_orthogonal_pymatgen(slab) for slab in raw_slabs]
41
+
42
+ def _select(
43
+ self, items: List[_GeneratedItemType], selector_parameters: _SelectorParametersType
44
+ ) -> List[_GeneratedItemType]:
45
+ termination = selector_parameters.termination
46
+ return [slab for slab in items if self.__create_termination_from_slab_pymatgen(slab) == termination]
47
+
48
+ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters=None) -> List[Material]:
49
+ materials = super()._post_process(items, post_process_parameters)
50
+ materials = [create_supercell(material, self.__configuration.xy_supercell_matrix) for material in materials]
51
+ for idx, material in enumerate(materials):
52
+ material.metadata["termination"] = label_pymatgen_slab_termination(items[idx])
53
+
54
+ return materials
55
+
56
+ def get_terminations(self, configuration: _ConfigurationType) -> List[Termination]:
57
+ return [
58
+ self.__create_termination_from_slab_pymatgen(slab)
59
+ for slab in self._generate_or_get_from_cache(configuration)
60
+ ]
61
+
62
+ def __conditionally_convert_slab_to_orthogonal_pymatgen(self, slab: PymatgenSlab) -> PymatgenSlab:
63
+ return slab.get_orthogonal_c_slab() if self.__configuration.use_orthogonal_z else slab
64
+
65
+ def __create_termination_from_slab_pymatgen(self, slab: PymatgenSlab) -> Termination:
66
+ return Termination.from_string(label_pymatgen_slab_termination(slab))
67
+
68
+ def _update_material_name(self, material: Material, configuration: SlabConfiguration) -> Material:
69
+ formula = get_chemical_formula(configuration.bulk)
70
+ miller_indices = "".join([str(i) for i in configuration.miller_indices])
71
+ termination = material.metadata["termination"]
72
+ # for example: "Si8(001), termination Si_P4/mmm_1, Slab"
73
+ new_name = f"{formula}({miller_indices}), termination {termination}, Slab"
74
+ material.name = new_name
75
+ return material