Skip to content

Commit

Permalink
Merge pull request #185 from Exabyte-io/feature/SOF-7525
Browse files Browse the repository at this point in the history
Add voronoi interstitial builder
  • Loading branch information
VsevolodX authored Jan 1, 2025
2 parents fae41f7 + c10aa41 commit 29e806a
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 4 deletions.
6 changes: 4 additions & 2 deletions src/py/mat3ra/made/tools/build/defect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ def create_defect(
Returns:
The material with the defect added.
"""
BuilderClass = DefectBuilderFactory.get_class_by_name(configuration.defect_type)
defect_builder_key = configuration.defect_type.lower()
if configuration.placement_method is not None:
defect_builder_key = f"{defect_builder_key}:{configuration.placement_method.lower()}"
BuilderClass = DefectBuilderFactory.get_class_by_name(defect_builder_key)
builder = BuilderClass(builder_parameters)

return builder.get_material(configuration) if builder else configuration.crystal


Expand Down
47 changes: 45 additions & 2 deletions src/py/mat3ra/made/tools/build/defect/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@


from mat3ra.made.utils import get_center_of_coordinates

from ...third_party import (
PymatgenStructure,
PymatgenPeriodicSite,
PymatgenVacancy,
PymatgenSubstitution,
PymatgenInterstitial,
PymatgenVoronoiInterstitialGenerator,
)

from ...modify import (
Expand All @@ -31,7 +33,11 @@
get_local_extremum_atom_index,
)
from ...analyze.coordination import get_voronoi_nearest_neighbors_atom_indices
from ...utils import transform_coordinate_to_supercell, coordinate as CoordinateCondition
from ...utils import (
transform_coordinate_to_supercell,
coordinate as CoordinateCondition,
get_distance_between_coordinates,
)
from ..utils import merge_materials
from ..slab import SlabConfiguration, create_slab, Termination
from ..supercell import create_supercell
Expand Down Expand Up @@ -62,7 +68,7 @@ class PointDefectBuilder(ConvertGeneratedItemsPymatgenStructureMixin, DefectBuil

_BuildParametersType = PointDefectBuilderParameters
_DefaultBuildParameters = PointDefectBuilderParameters()
_GeneratedItemType: PymatgenStructure = PymatgenStructure
_GeneratedItemType: type(PymatgenStructure) = PymatgenStructure # type: ignore
_ConfigurationType = PointDefectConfiguration
_generator: Callable

Expand Down Expand Up @@ -106,6 +112,43 @@ class InterstitialPointDefectBuilder(PointDefectBuilder):
_generator: PymatgenInterstitial = PymatgenInterstitial


class VoronoiInterstitialPointDefectBuilderParameters(BaseModel):
# According to pymatgen:
# https://github.com/materialsproject/pymatgen-analysis-defects/blob/e2cb285de8be07b38912ae1782285ef1f463a9a9/pymatgen/analysis/defects/generators.py#L343
clustering_tol: float = 0.5 # Clustering tolerance for merging interstitial sites
min_dist: float = 0.9 # Minimum distance between interstitial and nearest atom
ltol: float = 0.2 # Tolerance for lattice matching.
stol: float = 0.3 # Tolerance for structure matching.
angle_tol: float = 5 # Angle tolerance for structure matching.


class VoronoiInterstitialPointDefectBuilder(PointDefectBuilder):
_BuildParametersType: type( # type: ignore
VoronoiInterstitialPointDefectBuilderParameters
) = VoronoiInterstitialPointDefectBuilderParameters # type: ignore
_DefaultBuildParameters = VoronoiInterstitialPointDefectBuilderParameters() # type: ignore

def _generate(
self, configuration: BaseBuilder._ConfigurationType
) -> List[type(PointDefectBuilder._GeneratedItemType)]: # type: ignore
pymatgen_structure = to_pymatgen(configuration.crystal)
voronoi_gen = PymatgenVoronoiInterstitialGenerator(
**self.build_parameters.dict(),
)
interstitials = list(
voronoi_gen.generate(structure=pymatgen_structure, insert_species=[configuration.chemical_element])
)
approximate_coordinate = configuration.coordinate
closest_interstitial = min(
interstitials,
key=lambda interstitial: get_distance_between_coordinates(
interstitial.site.frac_coords, approximate_coordinate
),
)
pymatgen_structure.append(closest_interstitial.site.species, closest_interstitial.site.frac_coords)
return [pymatgen_structure]


class SlabDefectBuilderParameters(BaseModel):
auto_add_vacuum: bool = True
vacuum_thickness: float = 5.0
Expand Down
3 changes: 3 additions & 0 deletions src/py/mat3ra/made/tools/build/defect/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class PointDefectConfiguration(BaseDefectConfiguration, InMemoryEntity):
coordinate: List[float] = [0, 0, 0]
chemical_element: Optional[str] = None
use_cartesian_coordinates: bool = False
placement_method: Optional[AtomPlacementMethodEnum] = None

@classmethod
def from_site_id(
Expand Down Expand Up @@ -107,6 +108,8 @@ def _json(self):
"defect_type": self.defect_type.name,
"coordinate": self.coordinate,
"chemical_element": self.chemical_element,
"use_cartesian_coordinates": self.use_cartesian_coordinates,
"placement_method": self.placement_method.name if self.placement_method else None,
}


Expand Down
2 changes: 2 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,6 +26,8 @@ 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"
# Places the atom at Voronoi site closest to the given coordinate.
VORONOI_SITE = "voronoi_site"


class CoordinatesShapeEnum(str, Enum):
Expand Down
1 change: 1 addition & 0 deletions src/py/mat3ra/made/tools/build/defect/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class DefectBuilderFactory(BaseFactory):
"vacancy": "mat3ra.made.tools.build.defect.builders.VacancyPointDefectBuilder",
"substitution": "mat3ra.made.tools.build.defect.builders.SubstitutionPointDefectBuilder",
"interstitial": "mat3ra.made.tools.build.defect.builders.InterstitialPointDefectBuilder",
"interstitial:voronoi_site": "mat3ra.made.tools.build.defect.builders.VoronoiInterstitialPointDefectBuilder",
"adatom:coordinate": "mat3ra.made.tools.build.defect.builders.AdatomSlabDefectBuilder",
"adatom:crystal_site": "mat3ra.made.tools.build.defect.builders.CrystalSiteAdatomSlabDefectBuilder",
"adatom:equidistant": "mat3ra.made.tools.build.defect.builders.EquidistantAdatomSlabDefectBuilder",
Expand Down
2 changes: 2 additions & 0 deletions src/py/mat3ra/made/tools/third_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from pymatgen.analysis.defects.core import Interstitial as PymatgenInterstitial
from pymatgen.analysis.defects.core import Substitution as PymatgenSubstitution
from pymatgen.analysis.defects.core import Vacancy as PymatgenVacancy
from pymatgen.analysis.defects.generators import VoronoiInterstitialGenerator as PymatgenVoronoiInterstitialGenerator
from pymatgen.analysis.local_env import VoronoiNN as PymatgenVoronoiNN
from pymatgen.core import IStructure as PymatgenIStructure
from pymatgen.core import PeriodicSite as PymatgenPeriodicSite
Expand Down Expand Up @@ -50,6 +51,7 @@
"PymatgenVacancy",
"PymatgenSubstitution",
"PymatgenInterstitial",
"PymatgenVoronoiInterstitialGenerator",
"label_pymatgen_slab_termination",
"ase_make_supercell",
"ase_add_vacuum",
Expand Down
15 changes: 15 additions & 0 deletions tests/py/unit/test_tools_build_defect.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ def test_create_interstitial():
]


def test_create_interstitial_voronoi():
configuration = PointDefectConfiguration(
crystal=clean_material,
defect_type="interstitial",
chemical_element="Ge",
# Voronoi must resolve to [0.5, 0.5, 0.5] for Si structure
coordinate=[0.25, 0.25, 0.5],
placement_method="voronoi_site",
)
defect = create_defect(configuration)

assert defect.basis.elements.values[-1] == "Ge"
assertion_utils.assert_deep_almost_equal([0.5, 0.5, 0.5], defect.basis.coordinates.values[-1])


def test_create_defect_from_site_id():
# Substitution of Ge in place of Si at site_id=1
defect_configuration = PointDefectConfiguration.from_site_id(
Expand Down

0 comments on commit 29e806a

Please sign in to comment.