Skip to content

Commit

Permalink
Merge pull request #168 from Exabyte-io/feature/SOF-7472
Browse files Browse the repository at this point in the history
Feature/SOF-7472 update: add fractional layers
  • Loading branch information
VsevolodX authored Oct 24, 2024
2 parents 3d938b4 + 9853b80 commit a144671
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 8 deletions.
102 changes: 99 additions & 3 deletions src/py/mat3ra/made/tools/build/defect/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,111 @@ class SlabDefectBuilder(DefectBuilder):
_BuildParametersType = SlabDefectBuilderParameters
_DefaultBuildParameters = SlabDefectBuilderParameters()

def create_material_with_additional_layers(self, material: Material, added_thickness: int = 1) -> Material:
def create_material_with_additional_layers(
self, material: Material, added_thickness: Union[int, float] = 1
) -> Material:
"""
Adds a number of layers to the material.
Args:
material: The original material.
added_thickness: The thickness to add.
Returns:
A new Material instance with the added layers.
"""
if isinstance(added_thickness, int):
return self.create_material_with_additional_layers_int(material, added_thickness)
elif isinstance(added_thickness, float):
return self.create_material_with_additional_layers_float(material, added_thickness)
else:
raise TypeError("added_thickness must be an integer or float for this method.")

def create_material_with_additional_layers_int(self, material: Material, added_thickness: int = 1) -> Material:
"""
Adds an integer number of layers to the material.
Args:
material: The original material.
added_thickness: The number of whole layers to add.
Returns:
A new Material instance with the added layers.
"""

new_material = material.clone()
termination = Termination.from_string(new_material.metadata.get("build").get("termination"))
build_config = new_material.metadata.get("build").get("configuration")

if build_config["type"] != "SlabConfiguration":
raise ValueError("Material is not a slab.")
build_config.pop("type")
build_config["thickness"] = build_config["thickness"] + added_thickness

new_slab_config = SlabConfiguration(**build_config)
material_with_additional_layer = create_slab(new_slab_config, termination)
material_with_additional_layers = create_slab(new_slab_config, termination)

return material_with_additional_layers

def create_material_with_additional_layers_float(
self, material: Material, added_thickness: float = 1.0
) -> Material:
"""
Adds a fractional number of layers to the material.
Args:
material: The original material.
added_thickness: The fractional thickness to add.
Returns:
A new Material instance with the fractional layer added.
"""
whole_layers = int(added_thickness)
fractional_part = added_thickness - whole_layers

if whole_layers > 0:
material_with_additional_layers = self.create_material_with_additional_layers_int(material, whole_layers)
else:
material_with_additional_layers = material.clone()

if fractional_part > 0.0:
material_with_additional_layers = self.add_fractional_layer(
material_with_additional_layers, whole_layers, fractional_part
)

return material_with_additional_layers

def add_fractional_layer(
self,
material: Material,
whole_layers: int,
fractional_thickness: float,
) -> Material:
"""
Adds a fractional layer to the material.
Args:
material: The original material.
fractional_thickness: The fractional thickness to add.
Returns:
A new Material instance with the fractional layer added.
"""
material_with_additional_layers = self.create_material_with_additional_layers_int(material, 1)
new_c = material_with_additional_layers.lattice.c
layer_height = (new_c - material.lattice.c) / (whole_layers + 1)
original_max_z = get_atomic_coordinates_extremum(material, "max", "z", use_cartesian_coordinates=True)
added_layers_max_z = original_max_z + (whole_layers + fractional_thickness) * layer_height
added_layers_max_z_crystal = material_with_additional_layers.basis.cell.convert_point_to_crystal(
[0, 0, added_layers_max_z]
)[2]

material_with_additional_layers = filter_by_box(
material=material_with_additional_layers,
max_coordinate=[1, 1, added_layers_max_z_crystal],
)

return material_with_additional_layer
return material_with_additional_layers

def merge_slab_and_defect(self, material: Material, isolated_defect: Material) -> Material:
new_vacuum = isolated_defect.lattice.c - material.lattice.c
Expand All @@ -137,6 +230,9 @@ def merge_slab_and_defect(self, material: Material, isolated_defect: Material) -
material_name=material.name,
merge_dangerously=True,
)
new_material.to_crystal()
if self.build_parameters.auto_add_vacuum and get_atomic_coordinates_extremum(new_material, "max", "z") > 1:
new_material = add_vacuum(new_material, self.build_parameters.vacuum_thickness)
return new_material


Expand Down
72 changes: 67 additions & 5 deletions src/py/mat3ra/made/tools/build/defect/configuration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, List, Union
from typing import Optional, List, Union, Generic, TypeVar
from pydantic import BaseModel

from mat3ra.code.entity import InMemoryEntity
Expand All @@ -11,8 +11,15 @@
BoxCoordinateCondition,
TriangularPrismCoordinateCondition,
PlaneCoordinateCondition,
CoordinateCondition,
)
from .enums import (
PointDefectTypeEnum,
SlabDefectTypeEnum,
AtomPlacementMethodEnum,
ComplexDefectTypeEnum,
CoordinatesShapeEnum,
)
from .enums import PointDefectTypeEnum, SlabDefectTypeEnum, AtomPlacementMethodEnum, ComplexDefectTypeEnum


class BaseDefectConfiguration(BaseModel):
Expand Down Expand Up @@ -109,10 +116,10 @@ class SlabDefectConfiguration(BaseDefectConfiguration, InMemoryEntity):
Args:
crystal (Material): The Material object.
number_of_added_layers (int): The number of added layers.
number_of_added_layers (Union[int, float]): The number of added layers to the slab.
"""

number_of_added_layers: int = 1
number_of_added_layers: Union[int, float] = 1

@property
def _json(self):
Expand Down Expand Up @@ -188,7 +195,10 @@ def _json(self):
}


class IslandSlabDefectConfiguration(SlabDefectConfiguration):
CoordinateConditionType = TypeVar("CoordinateConditionType", bound=CoordinateCondition)


class IslandSlabDefectConfiguration(SlabDefectConfiguration, Generic[CoordinateConditionType]):
"""
Configuration for an island slab defect.
Expand All @@ -207,8 +217,60 @@ class IslandSlabDefectConfiguration(SlabDefectConfiguration):
BoxCoordinateCondition,
TriangularPrismCoordinateCondition,
PlaneCoordinateCondition,
CoordinateConditionType,
] = CylinderCoordinateCondition()

@classmethod
def from_dict(cls, crystal: Material, condition: dict, **kwargs):
"""
Creates an IslandSlabDefectConfiguration instance from a dictionary.
Args:
crystal (Material): The material object.
condition (dict): The dictionary with shape and other parameters for the condition.
kwargs: Other configuration parameters (like number_of_added_layers).
"""
condition_obj = cls.get_coordinate_condition(shape=condition["shape"], dict_params=condition)
return cls(crystal=crystal, condition=condition_obj, **kwargs)

@staticmethod
def get_coordinate_condition(shape: CoordinatesShapeEnum, dict_params: dict):
"""
Returns the appropriate coordinate condition based on the shape provided.
Args:
shape (CoordinatesShapeEnum): Shape of the island (e.g., cylinder, box, etc.).
dict_params (dict): Parameters for the shape condition.
Returns:
CoordinateCondition: The appropriate condition object.
"""
if shape == "cylinder":
return CylinderCoordinateCondition(
center_position=dict_params.get("center_position", [0.5, 0.5]),
radius=dict_params["radius"],
min_z=dict_params["min_z"],
max_z=dict_params["max_z"],
)
elif shape == "sphere":
return SphereCoordinateCondition(
center_position=dict_params.get("center_position", [0.5, 0.5]), radius=dict_params["radius"]
)
elif shape == "box":
return BoxCoordinateCondition(
min_coordinate=dict_params["min_coordinate"], max_coordinate=dict_params["max_coordinate"]
)
elif shape == "triangular_prism":
return TriangularPrismCoordinateCondition(
position_on_surface_1=dict_params["position_on_surface_1"],
position_on_surface_2=dict_params["position_on_surface_2"],
position_on_surface_3=dict_params["position_on_surface_3"],
min_z=dict_params["min_z"],
max_z=dict_params["max_z"],
)
else:
raise ValueError(f"Unsupported island shape: {shape}")

@property
def _json(self):
return {
Expand Down
7 changes: 7 additions & 0 deletions src/py/mat3ra/made/tools/build/defect/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,10 @@ class AtomPlacementMethodEnum(str, Enum):
EQUIDISTANT = "equidistant"
# Places the atom at the existing or extrapolated crystal site closest to the given coordinate.
CRYSTAL_SITE = "crystal_site"


class CoordinatesShapeEnum(str, Enum):
SPHERE = "sphere"
CYLINDER = "cylinder"
BOX = "rectangle"
TRIANGULAR_PRISM = "triangular_prism"

0 comments on commit a144671

Please sign in to comment.