Skip to content

Commit

Permalink
Merge pull request #161 from Exabyte-io/feature/SOF-7433
Browse files Browse the repository at this point in the history
feature/SOF-7433 feat: add grain boundary
  • Loading branch information
VsevolodX authored Sep 28, 2024
2 parents 8eca34e + d297b18 commit 575ba87
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 8 deletions.
17 changes: 16 additions & 1 deletion src/py/mat3ra/made/tools/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ def _json(self):
raise NotImplementedError


class BaseSelectorParameters(BaseModel):
default_index: int = 0


class BaseBuilderParameters(BaseModel):
class Config:
arbitrary_types_allowed = True


class BaseBuilder(BaseModel):
"""
Base class for material builders.
Expand All @@ -48,6 +57,9 @@ class BaseBuilder(BaseModel):
- `_PostProcessParametersType`: The data structure model for the post-process parameters.
"""

class Config:
arbitrary_types_allowed = True

build_parameters: Any = None
_BuildParametersType: Any = None
_DefaultBuildParameters: Any = None
Expand All @@ -56,6 +68,7 @@ class BaseBuilder(BaseModel):
_GeneratedItemType: Any = Any
_SelectorParametersType: Any = None
_PostProcessParametersType: Any = None
selector_parameters: Any = BaseSelectorParameters()

def __init__(self, build_parameters: _BuildParametersType = None):
super().__init__(build_parameters=build_parameters)
Expand Down Expand Up @@ -115,7 +128,9 @@ def get_material(
selector_parameters: Optional[_SelectorParametersType] = None,
post_process_parameters: Optional[_PostProcessParametersType] = None,
) -> Material:
return self.get_materials(configuration, selector_parameters, post_process_parameters)[0]
return self.get_materials(configuration, selector_parameters, post_process_parameters)[
self.selector_parameters.default_index
]

def _update_material_name(self, material, configuration) -> Material:
# Do nothing by default
Expand Down
24 changes: 24 additions & 0 deletions src/py/mat3ra/made/tools/build/grain_boundary/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Optional

from mat3ra.made.material import Material

from .builders import SlabGrainBoundaryBuilder
from .configuration import SlabGrainBoundaryConfiguration


def create_grain_boundary(
configuration: SlabGrainBoundaryConfiguration,
builder: Optional[SlabGrainBoundaryBuilder] = None,
) -> Material:
"""
Create a grain boundary according to provided configuration with selected builder.
Args:
configuration (SlabGrainBoundaryConfiguration): The configuration of the grain boundary.
builder (Optional[SlabGrainBoundaryBuilder]): The builder to use for creating the grain boundary.
Returns:
Material: The material with the grain boundary.
"""
if builder is None:
builder = SlabGrainBoundaryBuilder()
return builder.get_material(configuration)
80 changes: 80 additions & 0 deletions src/py/mat3ra/made/tools/build/grain_boundary/builders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import List

import numpy as np
from mat3ra.made.material import Material

from ..slab import SlabConfiguration, get_terminations, create_slab
from ...analyze import get_chemical_formula
from ..interface import ZSLStrainMatchingInterfaceBuilderParameters, InterfaceConfiguration
from ..interface.builders import ZSLStrainMatchingInterfaceBuilder
from ..supercell import create_supercell
from .configuration import SlabGrainBoundaryConfiguration
from ...third_party import PymatgenInterface


class SlabGrainBoundaryBuilderParameters(ZSLStrainMatchingInterfaceBuilderParameters):
default_index: int = 0


class SlabGrainBoundaryBuilder(ZSLStrainMatchingInterfaceBuilder):
"""
A builder for creating grain boundaries.
The grain boundary is created by:
1. creating an interface between two phases,
2. then rotating the interface by 90 degrees.
3. Finally, creating a slab from the rotated interface.
"""

_BuildParametersType: type(SlabGrainBoundaryBuilderParameters) = SlabGrainBoundaryBuilderParameters # type: ignore
_ConfigurationType: type(SlabGrainBoundaryConfiguration) = SlabGrainBoundaryConfiguration # type: ignore
_GeneratedItemType: PymatgenInterface = PymatgenInterface # type: ignore
selector_parameters: type( # type: ignore
SlabGrainBoundaryBuilderParameters
) = SlabGrainBoundaryBuilderParameters() # type: ignore

def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: # type: ignore
interface_config = InterfaceConfiguration(
film_configuration=configuration.phase_1_configuration,
substrate_configuration=configuration.phase_2_configuration,
film_termination=configuration.phase_1_termination,
substrate_termination=configuration.phase_2_termination,
distance_z=configuration.gap,
vacuum=configuration.gap,
)
return super()._generate(interface_config)

def _finalize(self, materials: List[Material], configuration: _ConfigurationType) -> List[Material]:
rot_90_degree_matrix = [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]
rotated_interfaces = [
create_supercell(material, supercell_matrix=rot_90_degree_matrix) for material in materials
]
final_slabs: List[Material] = []
for interface in rotated_interfaces:
supercell_matrix = np.zeros((3, 3))
supercell_matrix[:2, :2] = configuration.slab_configuration.xy_supercell_matrix
supercell_matrix[2, 2] = configuration.slab_configuration.thickness
final_slab_config = SlabConfiguration(
bulk=interface,
vacuum=configuration.slab_configuration.vacuum,
miller_indices=configuration.slab_configuration.miller_indices,
thickness=configuration.slab_configuration.thickness,
use_conventional_cell=False, # Keep false to prevent Pymatgen from simplifying the interface
use_orthogonal_z=True,
)
termination = configuration.slab_termination or get_terminations(final_slab_config)[0]
final_slab = create_slab(final_slab_config, termination)
final_slabs.append(final_slab)

return super()._finalize(final_slabs, configuration)

def _update_material_name(self, material: Material, configuration: _ConfigurationType) -> Material:
phase_1_formula = get_chemical_formula(configuration.phase_1_configuration.bulk)
phase_2_formula = get_chemical_formula(configuration.phase_2_configuration.bulk)
phase_1_miller_indices = "".join([str(i) for i in configuration.phase_1_configuration.miller_indices])
phase_2_miller_indices = "".join([str(i) for i in configuration.phase_2_configuration.miller_indices])
new_name = (
f"{phase_1_formula}({phase_1_miller_indices})-{phase_2_formula}({phase_2_miller_indices}), Grain Boundary"
)
material.name = new_name
return material
40 changes: 40 additions & 0 deletions src/py/mat3ra/made/tools/build/grain_boundary/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from typing import Optional

from .. import BaseConfiguration
from ..slab.configuration import SlabConfiguration
from ..slab.termination import Termination


class SlabGrainBoundaryConfiguration(BaseConfiguration):
"""
Configuration for a grain boundary between two phases with different surfaces facing each other.
Attributes:
phase_1_configuration (SlabConfiguration): The configuration of the first phase.
phase_2_configuration (SlabConfiguration): The configuration of the second phase.
phase_1_termination (Termination): The termination of the first phase.
phase_2_termination (Termination): The termination of the second phase.
gap (float): The gap between the two phases, in Angstroms.
slab_configuration (SlabConfiguration): The configuration of the grain boundary slab.
slab_termination (Optional[Termination]): The termination of the grain boundary slab.
"""

phase_1_configuration: SlabConfiguration
phase_2_configuration: SlabConfiguration
phase_1_termination: Termination
phase_2_termination: Termination
gap: float = 3.0
slab_configuration: SlabConfiguration
slab_termination: Optional[Termination] = None

@property
def _json(self):
return {
"type": self.__class__.__name__,
"phase_1_configuration": self.phase_1_configuration.to_json(),
"phase_2_configuration": self.phase_2_configuration.to_json(),
"phase_1_termination": str(self.phase_1_termination),
"phase_2_termination": str(self.phase_2_termination),
"gap": self.gap,
"slab_configuration": self.slab_configuration.to_json(),
}
6 changes: 3 additions & 3 deletions src/py/mat3ra/made/tools/build/interface/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from ...analyze import get_chemical_formula
from ...convert import to_ase, from_ase, to_pymatgen, PymatgenInterface, ASEAtoms
from ...build import BaseBuilder
from ...build import BaseBuilder, BaseBuilderParameters
from ..nanoribbon import NanoribbonConfiguration, create_nanoribbon
from ..supercell import create_supercell
from ..slab import create_slab, Termination, SlabConfiguration
Expand Down Expand Up @@ -120,7 +120,7 @@ def _post_process(self, items: List[_GeneratedItemType], post_process_parameters
########################################################################################
# Strain Matching Interface Builders #
########################################################################################
class StrainMatchingInterfaceBuilderParameters(BaseModel):
class StrainMatchingInterfaceBuilderParameters(BaseBuilderParameters):
strain_matching_parameters: Optional[Any] = None


Expand All @@ -144,7 +144,7 @@ class ZSLStrainMatchingParameters(BaseModel):


class ZSLStrainMatchingInterfaceBuilderParameters(StrainMatchingInterfaceBuilderParameters):
strain_matching_parameters: ZSLStrainMatchingParameters
strain_matching_parameters: ZSLStrainMatchingParameters = ZSLStrainMatchingParameters()


class ZSLStrainMatchingInterfaceBuilder(ConvertGeneratedItemsPymatgenStructureMixin, StrainMatchingInterfaceBuilder):
Expand Down
7 changes: 3 additions & 4 deletions src/py/mat3ra/made/tools/build/slab/configuration.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from typing import List, Tuple, Any

import numpy as np
from pydantic import BaseModel

from mat3ra.code.entity import InMemoryEntity

from mat3ra.made.material import Material

from .. import BaseConfiguration
from ...third_party import PymatgenSpacegroupAnalyzer
from ...convert import to_pymatgen, from_pymatgen


class SlabConfiguration(BaseModel, InMemoryEntity):
class SlabConfiguration(BaseConfiguration):
"""
Configuration for building a slab.
Expand Down
59 changes: 59 additions & 0 deletions tests/py/unit/test_tools_build_grain_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from mat3ra.made.material import Material
from mat3ra.made.tools.build.grain_boundary import (
SlabGrainBoundaryBuilder,
SlabGrainBoundaryConfiguration,
create_grain_boundary,
)
from mat3ra.made.tools.build.interface import ZSLStrainMatchingInterfaceBuilderParameters
from mat3ra.made.tools.build.slab import SlabConfiguration, get_terminations
from mat3ra.utils import assertion as assertion_utils


def test_slab_grain_boundary_builder():
material = Material(Material.default_config)
phase_1_configuration = SlabConfiguration(
bulk=material,
vacuum=0,
thickness=2,
miller_indices=(0, 0, 1),
)

phase_2_configuration = SlabConfiguration(
bulk=material,
vacuum=0,
thickness=2,
miller_indices=(0, 0, 1),
)

termination1 = get_terminations(phase_1_configuration)[0]
termination2 = get_terminations(phase_2_configuration)[0]

slab_config = SlabConfiguration(
vacuum=1,
miller_indices=(0, 0, 1),
thickness=2,
xy_supercell_matrix=[[1, 0], [0, 1]],
)

config = SlabGrainBoundaryConfiguration(
phase_1_configuration=phase_1_configuration,
phase_2_configuration=phase_2_configuration,
phase_1_termination=termination1,
phase_2_termination=termination2,
gap=3.0,
slab_configuration=slab_config,
)

builder_params = ZSLStrainMatchingInterfaceBuilderParameters(max_area=50)
builder = SlabGrainBoundaryBuilder(build_parameters=builder_params)
gb = create_grain_boundary(config, builder)
expected_lattice_vectors = [
[25.140673461, 0.0, 0.0],
[0.0, 3.867, 0.0],
[0.0, 0.0, 11.601],
]
expected_coordinate_15 = [0.777190818, 0.0, 0.083333333]

assert len(gb.basis.elements.values) == 32
assertion_utils.assert_deep_almost_equal(expected_coordinate_15, gb.basis.coordinates.values[15])
assertion_utils.assert_deep_almost_equal(expected_lattice_vectors, gb.lattice.vector_arrays)

0 comments on commit 575ba87

Please sign in to comment.