@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/made",
3
- "version": "2024.9.25-0",
3
+ "version": "2024.9.28-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",
@@ -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)[0]
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(BaseModel):
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(BaseModel, InMemoryEntity):
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)