From 6cda719aa393c68f7da9c922ec4cb5c518ed29a7 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 2 Jul 2024 11:52:34 -0400 Subject: [PATCH 01/35] feat: add neigboring atoms function --- src/py/mat3ra/made/tools/analyze.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index ea988646..9cc98755 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -1,6 +1,7 @@ from typing import Callable, List, Optional import numpy as np +from pymatgen.analysis.local_env import VoronoiNN from ..material import Material from .convert import decorator_convert_material_args_kwargs_to_atoms, to_pymatgen @@ -229,3 +230,29 @@ def get_atom_indices_with_condition_on_coordinates( selected_indices.append(coord.id) return selected_indices + + +def get_neighboring_atoms_indices(material: Material, position: List[float] = [0, 0, 0]) -> Optional[List[int]]: + """ + Returns the indices of direct neighboring atoms to a specified position in the material using Voronoi tessellation. + + Args: + material (Material): The material object to find neighbors in. + position (List[float]): The position to find neighbors for. + + Returns: + List[int]: A list of indices of neighboring atoms, or an empty list if no neighbors are found. + """ + structure = to_pymatgen(material) + + voronoi_nn = VoronoiNN(tol=0.5) + structure.append("X", position, validate_proximity=False) + neighbors = voronoi_nn.get_nn_info(structure, len(structure.sites) - 1) + neighboring_atoms_pymatgen_ids = [n["site_index"] for n in neighbors] + structure.remove_sites([-1]) + + all_coordinates = material.basis.coordinates + all_coordinates.filter_by_indices(neighboring_atoms_pymatgen_ids) + neighboring_atoms_ids = all_coordinates.ids + + return neighboring_atoms_ids From 5409ac6e76ef8928de3466e875c8b053cada3e32 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 2 Jul 2024 14:02:34 -0400 Subject: [PATCH 02/35] update: add setup for slab defects --- src/py/mat3ra/made/tools/build/defect/builders.py | 10 ++++++++++ .../made/tools/build/defect/configuration.py | 15 ++++++++++++--- src/py/mat3ra/made/tools/build/defect/enums.py | 4 ++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index e1582f28..7902ebfb 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -67,3 +67,13 @@ class SubstitutionPointDefectBuilder(PointDefectBuilder): class InterstitialPointDefectBuilder(PointDefectBuilder): _generator: PymatgenInterstitial = PymatgenInterstitial + + +class SlabDefectBuilderParameters(BaseModel): + add_vacuum: bool = True + min_vacuum_thickness: float = 5.0 + + +class SlabDefectBuilder(BaseBuilder): + _BuildParametersType = SlabDefectBuilderParameters + _DefaultBuildParameters = SlabDefectBuilderParameters() diff --git a/src/py/mat3ra/made/tools/build/defect/configuration.py b/src/py/mat3ra/made/tools/build/defect/configuration.py index c0897a92..ad057e46 100644 --- a/src/py/mat3ra/made/tools/build/defect/configuration.py +++ b/src/py/mat3ra/made/tools/build/defect/configuration.py @@ -5,7 +5,7 @@ from mat3ra.made.material import Material from ...analyze import get_closest_site_id_from_position -from .enums import PointDefectTypeEnum +from .enums import PointDefectTypeEnum, SlabDefectTypeEnum class BaseDefectConfiguration(BaseModel): @@ -23,7 +23,7 @@ def from_site_id( cls, crystal: Material, defect_type: PointDefectTypeEnum, site_id: int, chemical_element: Optional[str] = None ): if not crystal: - RuntimeError("Crystal is not defined") + raise RuntimeError("Crystal is not defined") position = crystal.coordinates_array[site_id] return cls(crystal=crystal, defect_type=defect_type, position=position, chemical_element=chemical_element) @@ -36,7 +36,7 @@ def from_approximate_position( chemical_element: Optional[str] = None, ): if not crystal: - RuntimeError("Crystal is not defined") + raise RuntimeError("Crystal is not defined") closest_site_id = get_closest_site_id_from_position(crystal, approximate_position) return cls.from_site_id( crystal=crystal, defect_type=defect_type, site_id=closest_site_id, chemical_element=chemical_element @@ -50,3 +50,12 @@ def _json(self): "position": self.position, "chemical_element": self.chemical_element, } + + +class SlabDefectConfiguration(BaseDefectConfiguration, InMemoryEntity): + defect_type: SlabDefectTypeEnum + + +class AdatomSlabDefectConfiguration(SlabDefectConfiguration): + position: List[float] = [0.5, 0.5, 0.5] + chemical_element: str = "Si" diff --git a/src/py/mat3ra/made/tools/build/defect/enums.py b/src/py/mat3ra/made/tools/build/defect/enums.py index 8e9c092f..da3b92fd 100644 --- a/src/py/mat3ra/made/tools/build/defect/enums.py +++ b/src/py/mat3ra/made/tools/build/defect/enums.py @@ -5,3 +5,7 @@ class PointDefectTypeEnum(str, Enum): VACANCY = "vacancy" SUBSTITUTION = "substitution" INTERSTITIAL = "interstitial" + + +class SlabDefectTypeEnum(str, Enum): + ADATOM = "adatom" From 6a43b9400b4cf5c2b43b3e413cc3d75e6096ace5 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Tue, 2 Jul 2024 14:11:00 -0400 Subject: [PATCH 03/35] update: add adatom and equidistant --- .../made/tools/build/defect/builders.py | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 7902ebfb..13462bb2 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -1,7 +1,8 @@ from typing import List, Callable -from mat3ra.made.material import Material from pydantic import BaseModel +from mat3ra.made.material import Material + from ...third_party import ( PymatgenStructure, @@ -14,6 +15,7 @@ from ...convert import to_pymatgen from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin from .configuration import PointDefectConfiguration +from ...analyze import get_neighboring_atoms_indices class PointDefectBuilderParameters(BaseModel): @@ -77,3 +79,64 @@ class SlabDefectBuilderParameters(BaseModel): class SlabDefectBuilder(BaseBuilder): _BuildParametersType = SlabDefectBuilderParameters _DefaultBuildParameters = SlabDefectBuilderParameters() + + +class AdatomSlabDefectBuilder(SlabDefectBuilder): + _GeneratedItemType: Material = Material + + def add_adatom( + material: Material, chemical_element: str = "Si", position: List[float] = [0.5, 0.5, 0.5] + ) -> Material: + material_copy = material.clone() + basis = material_copy.basis + basis.add_atom(chemical_element, position) + material_copy.basis = basis + return [material_copy] + + _generator = add_adatom + + +class EquidistantAdatomSlabDefectBuilder(SlabDefectBuilder): + _GeneratedItemType: Material = Material + + def add_adatom_equdistant( + material: Material, + chemical_element: str = "Si", + approximate_position: List[float] = [0.5, 0.5, 0.5], + distance: float = 2.0, + ) -> Material: + """ + Adds an atom to the material at a position that is equidistant to the nearest atoms + (that are found within proximity to the approx position) with the specified distance. + """ + material_copy: Material = material.clone() + basis = material_copy.basis + + distance = distance / material_copy.lattice.c + max_z = max([coordinate[2] for coordinate in basis.coordinates.values]) + + # Move the appx position to the top of the slab + adatom_position = approximate_position.copy() + adatom_position[2] = max_z + distance + + neighboring_atoms_ids = get_neighboring_atoms_indices(material, adatom_position) + + # Find equidistant position from heighboring atoms with the set z + if neighboring_atoms_ids is not None: + neighboring_atoms_coordinates = [basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] + + equidistant_position = [ + sum([coordinate[i] for coordinate in neighboring_atoms_coordinates]) / len(neighboring_atoms_coordinates) + for i in range(3) + ] + equidistant_position[2] = adatom_position[2] + + # Check for valid position (inside the cell) + if equidistant_position[2] > basis.cell.vectors_as_nested_array[2][2]: + raise ValueError("The adatom position is outside the cell.") + + basis.add_atom(chemical_element, equidistant_position) + material_copy.basis = basis + return [material_copy] + + _generator = add_adatom_equdistant From 528487974a7cf8bf13e10db9848bff020f78d780 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Wed, 3 Jul 2024 20:01:03 -0400 Subject: [PATCH 04/35] update: imports --- src/py/mat3ra/made/tools/analyze.py | 5 ++--- .../mat3ra/made/tools/build/defect/configuration.py | 11 +---------- src/py/mat3ra/made/tools/third_party.py | 2 ++ 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 9cc98755..38730cd0 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -1,11 +1,10 @@ from typing import Callable, List, Optional import numpy as np -from pymatgen.analysis.local_env import VoronoiNN from ..material import Material from .convert import decorator_convert_material_args_kwargs_to_atoms, to_pymatgen -from .third_party import ASEAtoms, PymatgenIStructure +from .third_party import ASEAtoms, PymatgenIStructure, PymatgenVoronoiNN @decorator_convert_material_args_kwargs_to_atoms @@ -245,7 +244,7 @@ def get_neighboring_atoms_indices(material: Material, position: List[float] = [0 """ structure = to_pymatgen(material) - voronoi_nn = VoronoiNN(tol=0.5) + voronoi_nn = PymatgenVoronoiNN(tol=0.5) structure.append("X", position, validate_proximity=False) neighbors = voronoi_nn.get_nn_info(structure, len(structure.sites) - 1) neighboring_atoms_pymatgen_ids = [n["site_index"] for n in neighbors] diff --git a/src/py/mat3ra/made/tools/build/defect/configuration.py b/src/py/mat3ra/made/tools/build/defect/configuration.py index ad057e46..eda9f804 100644 --- a/src/py/mat3ra/made/tools/build/defect/configuration.py +++ b/src/py/mat3ra/made/tools/build/defect/configuration.py @@ -5,7 +5,7 @@ from mat3ra.made.material import Material from ...analyze import get_closest_site_id_from_position -from .enums import PointDefectTypeEnum, SlabDefectTypeEnum +from .enums import PointDefectTypeEnum class BaseDefectConfiguration(BaseModel): @@ -50,12 +50,3 @@ def _json(self): "position": self.position, "chemical_element": self.chemical_element, } - - -class SlabDefectConfiguration(BaseDefectConfiguration, InMemoryEntity): - defect_type: SlabDefectTypeEnum - - -class AdatomSlabDefectConfiguration(SlabDefectConfiguration): - position: List[float] = [0.5, 0.5, 0.5] - chemical_element: str = "Si" diff --git a/src/py/mat3ra/made/tools/third_party.py b/src/py/mat3ra/made/tools/third_party.py index d6329e75..226627d3 100644 --- a/src/py/mat3ra/made/tools/third_party.py +++ b/src/py/mat3ra/made/tools/third_party.py @@ -5,6 +5,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.local_env import VoronoiNN as PymatgenVoronoiNN from pymatgen.core import IStructure as PymatgenIStructure from pymatgen.core import PeriodicSite as PymatgenPeriodicSite from pymatgen.core.interface import Interface as PymatgenInterface @@ -38,4 +39,5 @@ "ase_make_supercell", "PymatgenAseAtomsAdaptor", "PymatgenPoscar", + "PymatgenVoronoiNN", ] From b3a184166ac88030e785b44abb4569d7b5794ef2 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Fri, 5 Jul 2024 21:14:41 -0700 Subject: [PATCH 05/35] update: address pr comments --- src/py/mat3ra/made/tools/analyze.py | 17 +++++- .../made/tools/build/defect/builders.py | 61 +++++++++++-------- 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 38730cd0..9024ca85 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -211,7 +211,7 @@ def get_atom_indices_with_condition_on_coordinates( Args: material (Material): Material object condition (Callable[List[float], bool]): Function that checks if coordinates satisfy the condition. - use_cartesian (bool): Whether to use Cartesian coordinates for the condition evaluation. + use_cartesian_coordinates (bool): Whether to use Cartesian coordinates for the condition evaluation. Returns: List[int]: List of indices of atoms whose coordinates satisfy the condition. @@ -231,7 +231,7 @@ def get_atom_indices_with_condition_on_coordinates( return selected_indices -def get_neighboring_atoms_indices(material: Material, position: List[float] = [0, 0, 0]) -> Optional[List[int]]: +def get_nearest_neighbors_atom_indices(material: Material, position: List[float] = [0, 0, 0]) -> Optional[List[int]]: """ Returns the indices of direct neighboring atoms to a specified position in the material using Voronoi tessellation. @@ -255,3 +255,16 @@ def get_neighboring_atoms_indices(material: Material, position: List[float] = [0 neighboring_atoms_ids = all_coordinates.ids return neighboring_atoms_ids + + +def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: + """ + Calculate the center of the coordinates. + + Args: + coordinates (List[List[float]]): The list of coordinates. + + Returns: + List[float]: The center of the coordinates. + """ + return [sum([coordinate[i] for coordinate in coordinates]) / len(coordinates) for i in range(3)] diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 13462bb2..de41076f 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -15,7 +15,7 @@ from ...convert import to_pymatgen from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin from .configuration import PointDefectConfiguration -from ...analyze import get_neighboring_atoms_indices +from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates class PointDefectBuilderParameters(BaseModel): @@ -72,8 +72,8 @@ class InterstitialPointDefectBuilder(PointDefectBuilder): class SlabDefectBuilderParameters(BaseModel): - add_vacuum: bool = True - min_vacuum_thickness: float = 5.0 + auto_add_vacuum: bool = True + vacuum_thickness: float = 5.0 class SlabDefectBuilder(BaseBuilder): @@ -85,10 +85,18 @@ class AdatomSlabDefectBuilder(SlabDefectBuilder): _GeneratedItemType: Material = Material def add_adatom( - material: Material, chemical_element: str = "Si", position: List[float] = [0.5, 0.5, 0.5] - ) -> Material: + self, + material: Material, + chemical_element: str = "Si", + position_on_surface: List[float] = [0.5, 0.5], + distance_z: float = 2.0, + ) -> List[Material]: material_copy = material.clone() basis = material_copy.basis + distance_in_crystal_units = distance_z / material_copy.lattice.c + max_z = max([coordinate[2] for coordinate in basis.coordinates.values]) + position = position_on_surface.copy() + position[2] = max_z + distance_in_crystal_units basis.add_atom(chemical_element, position) material_copy.basis = basis return [material_copy] @@ -100,38 +108,39 @@ class EquidistantAdatomSlabDefectBuilder(SlabDefectBuilder): _GeneratedItemType: Material = Material def add_adatom_equdistant( + self, material: Material, chemical_element: str = "Si", - approximate_position: List[float] = [0.5, 0.5, 0.5], - distance: float = 2.0, - ) -> Material: + approximate_position_on_surface: List[float] = [0.5, 0.5], + distance_z: float = 2.0, + ) -> List[Material]: """ - Adds an atom to the material at a position that is equidistant to the nearest atoms + Add an atom to the material at a position that is equidistant to the nearest atoms (that are found within proximity to the approx position) with the specified distance. + + Args: + material (Material): The material to add the adatom to. + chemical_element (str): The chemical element of the adatom. + approximate_position_on_surface (List[float]): The approximate position of the adatom on the surface. + distance_z (float): The distance from the nearest atoms to the adatom. + + Returns: + List[Material]: A list containing the material with the adatom added. """ material_copy: Material = material.clone() basis = material_copy.basis - - distance = distance / material_copy.lattice.c + distance_in_crystal_units = distance_z / material_copy.lattice.c max_z = max([coordinate[2] for coordinate in basis.coordinates.values]) + adatom_position = approximate_position_on_surface.copy() + adatom_position[2] = max_z + distance_in_crystal_units - # Move the appx position to the top of the slab - adatom_position = approximate_position.copy() - adatom_position[2] = max_z + distance - - neighboring_atoms_ids = get_neighboring_atoms_indices(material, adatom_position) + neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) + if not neighboring_atoms_ids: + raise ValueError("No neighboring atoms found.") + neighboring_atoms_coordinates = [basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] - # Find equidistant position from heighboring atoms with the set z - if neighboring_atoms_ids is not None: - neighboring_atoms_coordinates = [basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] - - equidistant_position = [ - sum([coordinate[i] for coordinate in neighboring_atoms_coordinates]) / len(neighboring_atoms_coordinates) - for i in range(3) - ] + equidistant_position = get_center_of_coordinates(neighboring_atoms_coordinates) equidistant_position[2] = adatom_position[2] - - # Check for valid position (inside the cell) if equidistant_position[2] > basis.cell.vectors_as_nested_array[2][2]: raise ValueError("The adatom position is outside the cell.") From 04403ad4ff01369811ace2c35da56d8e9c424f65 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Fri, 5 Jul 2024 21:19:07 -0700 Subject: [PATCH 06/35] chore: cleanup --- src/py/mat3ra/made/tools/build/defect/builders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index de41076f..93054f73 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -13,9 +13,9 @@ ) from ...build import BaseBuilder from ...convert import to_pymatgen +from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates, get_atomic_coordinates_max_z from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin from .configuration import PointDefectConfiguration -from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates class PointDefectBuilderParameters(BaseModel): @@ -130,7 +130,7 @@ def add_adatom_equdistant( material_copy: Material = material.clone() basis = material_copy.basis distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = max([coordinate[2] for coordinate in basis.coordinates.values]) + max_z = get_atomic_coordinates_max_z(material_copy) adatom_position = approximate_position_on_surface.copy() adatom_position[2] = max_z + distance_in_crystal_units From 864ece08582c7ba5e3cc812f4b0f4730cad223d1 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:09:05 -0700 Subject: [PATCH 07/35] update: add adatom config --- .../mat3ra/made/tools/build/defect/configuration.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/build/defect/configuration.py b/src/py/mat3ra/made/tools/build/defect/configuration.py index eda9f804..a6e946bb 100644 --- a/src/py/mat3ra/made/tools/build/defect/configuration.py +++ b/src/py/mat3ra/made/tools/build/defect/configuration.py @@ -5,7 +5,7 @@ from mat3ra.made.material import Material from ...analyze import get_closest_site_id_from_position -from .enums import PointDefectTypeEnum +from .enums import PointDefectTypeEnum, SlabDefectTypeEnum class BaseDefectConfiguration(BaseModel): @@ -50,3 +50,14 @@ def _json(self): "position": self.position, "chemical_element": self.chemical_element, } + + +class SlabDefectConfiguration(BaseDefectConfiguration): + pass + + +class AdatomSlabDefectConfiguration(SlabDefectConfiguration): + defect_type: SlabDefectTypeEnum = SlabDefectTypeEnum.ADATOM + position_on_surface: List[float] = [0.5, 0.5] + distance_z: float = 2.0 + chemical_element: str = "Si" From a3bec27843fe257c3ef11dc7e1db5bab0be769de Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:09:41 -0700 Subject: [PATCH 08/35] update: adjust adatom generation --- .../mat3ra/made/tools/build/defect/__init__.py | 13 +++++++------ .../mat3ra/made/tools/build/defect/builders.py | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/__init__.py b/src/py/mat3ra/made/tools/build/defect/__init__.py index d447e72c..b8f766fb 100644 --- a/src/py/mat3ra/made/tools/build/defect/__init__.py +++ b/src/py/mat3ra/made/tools/build/defect/__init__.py @@ -1,11 +1,11 @@ -from typing import Optional +from typing import Optional, Union from mat3ra.utils.factory import BaseFactory from mat3ra.made.material import Material -from .builders import PointDefectBuilderParameters -from .configuration import PointDefectConfiguration -from .enums import PointDefectTypeEnum +from .builders import PointDefectBuilderParameters, SlabDefectBuilderParameters +from .configuration import PointDefectConfiguration, AdatomSlabDefectConfiguration +from .enums import PointDefectTypeEnum, SlabDefectTypeEnum class DefectBuilderFactory(BaseFactory): @@ -13,12 +13,13 @@ class DefectBuilderFactory(BaseFactory): PointDefectTypeEnum.VACANCY: "mat3ra.made.tools.build.defect.builders.VacancyPointDefectBuilder", PointDefectTypeEnum.SUBSTITUTION: "mat3ra.made.tools.build.defect.builders.SubstitutionPointDefectBuilder", PointDefectTypeEnum.INTERSTITIAL: "mat3ra.made.tools.build.defect.builders.InterstitialPointDefectBuilder", + SlabDefectTypeEnum.ADATOM: "mat3ra.made.tools.build.defect.builders.AdatomSlabDefectBuilder", } def create_defect( - configuration: PointDefectConfiguration, - builder_parameters: Optional[PointDefectBuilderParameters] = None, + configuration: Union[PointDefectConfiguration, AdatomSlabDefectConfiguration], + builder_parameters: Union[PointDefectBuilderParameters, SlabDefectBuilderParameters, None] = None, ) -> Material: """ Return a material with a selected defect added. diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 93054f73..16d0dfd7 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -15,7 +15,7 @@ from ...convert import to_pymatgen from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates, get_atomic_coordinates_max_z from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin -from .configuration import PointDefectConfiguration +from .configuration import PointDefectConfiguration, AdatomSlabDefectConfiguration class PointDefectBuilderParameters(BaseModel): @@ -82,6 +82,7 @@ class SlabDefectBuilder(BaseBuilder): class AdatomSlabDefectBuilder(SlabDefectBuilder): + _ConfigurationType: type(AdatomSlabDefectConfiguration) = AdatomSlabDefectConfiguration # type: ignore _GeneratedItemType: Material = Material def add_adatom( @@ -94,14 +95,20 @@ def add_adatom( material_copy = material.clone() basis = material_copy.basis distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = max([coordinate[2] for coordinate in basis.coordinates.values]) + max_z = get_atomic_coordinates_max_z(material_copy) position = position_on_surface.copy() - position[2] = max_z + distance_in_crystal_units + position.append(max_z + distance_in_crystal_units) basis.add_atom(chemical_element, position) material_copy.basis = basis return [material_copy] - _generator = add_adatom + def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: + return self.add_adatom( + material=configuration.crystal, + chemical_element=configuration.chemical_element, + position_on_surface=configuration.position_on_surface, + distance_z=configuration.distance_z, + ) class EquidistantAdatomSlabDefectBuilder(SlabDefectBuilder): @@ -148,4 +155,4 @@ def add_adatom_equdistant( material_copy.basis = basis return [material_copy] - _generator = add_adatom_equdistant + _generate = add_adatom_equdistant From 73dd8b4478397bc5e77c4faf030fd0c89d42fbb4 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:15:44 -0700 Subject: [PATCH 09/35] chore: cleanup --- src/py/mat3ra/made/tools/analyze.py | 24 +------------------ .../made/tools/build/defect/builders.py | 15 ++++++++---- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 14081e18..541f7a5c 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -1,4 +1,4 @@ -from typing import Callable, List, Optional, Literal +from typing import Callable, List, Literal, Optional import numpy as np @@ -270,28 +270,6 @@ def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: return [sum([coordinate[i] for coordinate in coordinates]) / len(coordinates) for i in range(3)] -def get_atomic_coordinates_min_z( - material: Material, - use_cartesian_coordinates: bool = False, -) -> float: - """ - Return minimum of Z coordinates in crystal or cartesian units. - - Args: - material (Material): Material object. - use_cartesian_coordinates (bool): Whether to use Cartesian coordinates - Returns: - float: Minimum of Z coordinates. - """ - new_material = material.clone() - if use_cartesian_coordinates: - new_basis = new_material.basis - new_basis.to_cartesian() - new_material.basis = new_basis - coordinates = new_material.basis.coordinates.to_array_of_values_with_ids() - return min([coord.value[2] for coord in coordinates]) - - def get_atomic_coordinates_extremum( material: Material, extremum: Literal["max", "min"] = "max", diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 16d0dfd7..cb9f3a9a 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -13,7 +13,7 @@ ) from ...build import BaseBuilder from ...convert import to_pymatgen -from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates, get_atomic_coordinates_max_z +from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates, get_atomic_coordinates_extremum from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin from .configuration import PointDefectConfiguration, AdatomSlabDefectConfiguration @@ -95,7 +95,7 @@ def add_adatom( material_copy = material.clone() basis = material_copy.basis distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = get_atomic_coordinates_max_z(material_copy) + max_z = get_atomic_coordinates_extremum(material_copy) position = position_on_surface.copy() position.append(max_z + distance_in_crystal_units) basis.add_atom(chemical_element, position) @@ -112,6 +112,7 @@ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemTyp class EquidistantAdatomSlabDefectBuilder(SlabDefectBuilder): + _ConfigurationType: type(AdatomSlabDefectConfiguration) = AdatomSlabDefectConfiguration # type: ignore _GeneratedItemType: Material = Material def add_adatom_equdistant( @@ -137,7 +138,7 @@ def add_adatom_equdistant( material_copy: Material = material.clone() basis = material_copy.basis distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = get_atomic_coordinates_max_z(material_copy) + max_z = get_atomic_coordinates_extremum(material_copy) adatom_position = approximate_position_on_surface.copy() adatom_position[2] = max_z + distance_in_crystal_units @@ -155,4 +156,10 @@ def add_adatom_equdistant( material_copy.basis = basis return [material_copy] - _generate = add_adatom_equdistant + def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: + return self.add_adatom_equdistant( + material=configuration.crystal, + chemical_element=configuration.chemical_element, + approximate_position_on_surface=configuration.position_on_surface, + distance_z=configuration.distance_z, + ) From d2b77b50a36c2bd94de48c9b80aabd0fa3598a75 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sat, 6 Jul 2024 19:30:31 -0700 Subject: [PATCH 10/35] update: make work --- src/py/mat3ra/made/tools/build/__init__.py | 9 ++++++++- .../mat3ra/made/tools/build/defect/configuration.py | 12 +++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/__init__.py b/src/py/mat3ra/made/tools/build/__init__.py index 99e10373..52de9f31 100644 --- a/src/py/mat3ra/made/tools/build/__init__.py +++ b/src/py/mat3ra/made/tools/build/__init__.py @@ -65,7 +65,14 @@ def _select( def _post_process( self, items: List[_GeneratedItemType], post_process_parameters: Optional[_PostProcessParametersType] ) -> List[Material]: - return [Material(self._convert_generated_item(item)) for item in items] + return [ + ( + self._convert_generated_item(item) + if isinstance(item, Material) + else Material(self._convert_generated_item(item)) + ) + for item in items + ] @staticmethod def _convert_generated_item(item: _GeneratedItemType): diff --git a/src/py/mat3ra/made/tools/build/defect/configuration.py b/src/py/mat3ra/made/tools/build/defect/configuration.py index a6e946bb..2deff393 100644 --- a/src/py/mat3ra/made/tools/build/defect/configuration.py +++ b/src/py/mat3ra/made/tools/build/defect/configuration.py @@ -52,7 +52,7 @@ def _json(self): } -class SlabDefectConfiguration(BaseDefectConfiguration): +class SlabDefectConfiguration(BaseDefectConfiguration, InMemoryEntity): pass @@ -61,3 +61,13 @@ class AdatomSlabDefectConfiguration(SlabDefectConfiguration): position_on_surface: List[float] = [0.5, 0.5] distance_z: float = 2.0 chemical_element: str = "Si" + + @property + def _json(self): + return { + "type": "AdatomSlabDefectConfiguration", + "defect_type": self.defect_type.name, + "position_on_surface": self.position_on_surface, + "distance_z": self.distance_z, + "chemical_element": self.chemical_element, + } From 9df4ca61ce813907ac4c410e8a588e727fb6383c Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:14:41 -0700 Subject: [PATCH 11/35] update: use OOP --- .../made/tools/build/defect/builders.py | 64 ++++++------------- 1 file changed, 18 insertions(+), 46 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index cb9f3a9a..dcc28049 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -85,25 +85,25 @@ class AdatomSlabDefectBuilder(SlabDefectBuilder): _ConfigurationType: type(AdatomSlabDefectConfiguration) = AdatomSlabDefectConfiguration # type: ignore _GeneratedItemType: Material = Material - def add_adatom( + def create_adatom( self, material: Material, chemical_element: str = "Si", position_on_surface: List[float] = [0.5, 0.5], distance_z: float = 2.0, ) -> List[Material]: - material_copy = material.clone() - basis = material_copy.basis - distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = get_atomic_coordinates_extremum(material_copy) + new_material = material.clone() + basis = new_material.basis + distance_in_crystal_units = distance_z / new_material.lattice.c + max_z = get_atomic_coordinates_extremum(new_material) position = position_on_surface.copy() position.append(max_z + distance_in_crystal_units) basis.add_atom(chemical_element, position) - material_copy.basis = basis - return [material_copy] + new_material.basis = basis + return [new_material] def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: - return self.add_adatom( + return self.create_adatom( material=configuration.crystal, chemical_element=configuration.chemical_element, position_on_surface=configuration.position_on_surface, @@ -111,55 +111,27 @@ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemTyp ) -class EquidistantAdatomSlabDefectBuilder(SlabDefectBuilder): - _ConfigurationType: type(AdatomSlabDefectConfiguration) = AdatomSlabDefectConfiguration # type: ignore - _GeneratedItemType: Material = Material - - def add_adatom_equdistant( +class EquidistantAdatomSlabDefectBuilder(AdatomSlabDefectConfiguration): + def create_adatom( self, material: Material, chemical_element: str = "Si", - approximate_position_on_surface: List[float] = [0.5, 0.5], + position_on_surface: List[float] = [0.5, 0.5], distance_z: float = 2.0, ) -> List[Material]: - """ - Add an atom to the material at a position that is equidistant to the nearest atoms - (that are found within proximity to the approx position) with the specified distance. - - Args: - material (Material): The material to add the adatom to. - chemical_element (str): The chemical element of the adatom. - approximate_position_on_surface (List[float]): The approximate position of the adatom on the surface. - distance_z (float): The distance from the nearest atoms to the adatom. - - Returns: - List[Material]: A list containing the material with the adatom added. - """ - material_copy: Material = material.clone() - basis = material_copy.basis - distance_in_crystal_units = distance_z / material_copy.lattice.c - max_z = get_atomic_coordinates_extremum(material_copy) - adatom_position = approximate_position_on_surface.copy() - adatom_position[2] = max_z + distance_in_crystal_units + equidistant_position = self.get_equidistant_position(material, position_on_surface) + return super().create_adatom(material, chemical_element, equidistant_position, distance_z) + def get_equidistant_position(self, material: Material, adatom_position: List[float]) -> List[float]: + new_basis = material.basis neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) if not neighboring_atoms_ids: raise ValueError("No neighboring atoms found.") - neighboring_atoms_coordinates = [basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] + neighboring_atoms_coordinates = [new_basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] equidistant_position = get_center_of_coordinates(neighboring_atoms_coordinates) equidistant_position[2] = adatom_position[2] - if equidistant_position[2] > basis.cell.vectors_as_nested_array[2][2]: + if equidistant_position[2] > new_basis.cell.vectors_as_nested_array[2][2]: raise ValueError("The adatom position is outside the cell.") - basis.add_atom(chemical_element, equidistant_position) - material_copy.basis = basis - return [material_copy] - - def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: - return self.add_adatom_equdistant( - material=configuration.crystal, - chemical_element=configuration.chemical_element, - approximate_position_on_surface=configuration.position_on_surface, - distance_z=configuration.distance_z, - ) + return equidistant_position From 46bf993a2bbda6ad637b0dd94ba4b37768c43ad2 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:15:41 -0700 Subject: [PATCH 12/35] update: fix conversion in base class --- src/py/mat3ra/made/tools/build/__init__.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/__init__.py b/src/py/mat3ra/made/tools/build/__init__.py index 52de9f31..c1e5f2c9 100644 --- a/src/py/mat3ra/made/tools/build/__init__.py +++ b/src/py/mat3ra/made/tools/build/__init__.py @@ -65,14 +65,9 @@ def _select( def _post_process( self, items: List[_GeneratedItemType], post_process_parameters: Optional[_PostProcessParametersType] ) -> List[Material]: - return [ - ( - self._convert_generated_item(item) - if isinstance(item, Material) - else Material(self._convert_generated_item(item)) - ) - for item in items - ] + if self._GeneratedItemType == Material: + return items + return [Material(self._convert_generated_item(item)) for item in items] @staticmethod def _convert_generated_item(item: _GeneratedItemType): From 81bbc0f91ac876d02fed7f7cc128df2908513cc8 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:18:26 -0700 Subject: [PATCH 13/35] chore: add docstring --- .../made/tools/build/defect/builders.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index dcc28049..44998981 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -92,6 +92,18 @@ def create_adatom( position_on_surface: List[float] = [0.5, 0.5], distance_z: float = 2.0, ) -> List[Material]: + """ + Create an adatom at the specified position on the surface of the material. + + Args: + material: The material to add the adatom to. + chemical_element: The chemical element of the adatom. + position_on_surface: The position on the surface of the material. + distance_z: The distance of the adatom from the surface. + + Returns: + The material with the adatom added. + """ new_material = material.clone() basis = new_material.basis distance_in_crystal_units = distance_z / new_material.lattice.c @@ -119,6 +131,18 @@ def create_adatom( position_on_surface: List[float] = [0.5, 0.5], distance_z: float = 2.0, ) -> List[Material]: + """ + Create an adatom with an equidistant XY position among the nearest neighbors at the given distance from the surface. + + Args: + material: The material to add the adatom to. + chemical_element: The chemical element of the adatom. + position_on_surface: The position on the surface of the material. + distance_z: The distance of the adatom from the surface. + + Returns: + The material with the adatom added. + """ equidistant_position = self.get_equidistant_position(material, position_on_surface) return super().create_adatom(material, chemical_element, equidistant_position, distance_z) From e8a1320aaab3da87a07b5139f54697d10b720b8e Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:21:10 -0700 Subject: [PATCH 14/35] chore: fix lint --- src/py/mat3ra/made/tools/build/defect/builders.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 44998981..5a1f4c84 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -132,7 +132,8 @@ def create_adatom( distance_z: float = 2.0, ) -> List[Material]: """ - Create an adatom with an equidistant XY position among the nearest neighbors at the given distance from the surface. + Create an adatom with an equidistant XY position among the nearest neighbors + at the given distance from the surface. Args: material: The material to add the adatom to. From b1f1e48fa5ecdcfca59ce21dd85413addc4cfae6 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:32:18 -0700 Subject: [PATCH 15/35] update: cleanups --- src/py/mat3ra/made/tools/analyze.py | 2 +- .../mat3ra/made/tools/build/defect/__init__.py | 17 +++++++++++++++-- .../mat3ra/made/tools/build/defect/builders.py | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 541f7a5c..281113ee 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -267,7 +267,7 @@ def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: Returns: List[float]: The center of the coordinates. """ - return [sum([coordinate[i] for coordinate in coordinates]) / len(coordinates) for i in range(3)] + return np.mean(np.array(coordinates), axis=0) def get_atomic_coordinates_extremum( diff --git a/src/py/mat3ra/made/tools/build/defect/__init__.py b/src/py/mat3ra/made/tools/build/defect/__init__.py index b8f766fb..8d9211cf 100644 --- a/src/py/mat3ra/made/tools/build/defect/__init__.py +++ b/src/py/mat3ra/made/tools/build/defect/__init__.py @@ -3,7 +3,12 @@ from mat3ra.utils.factory import BaseFactory from mat3ra.made.material import Material -from .builders import PointDefectBuilderParameters, SlabDefectBuilderParameters +from .builders import ( + PointDefectBuilderParameters, + SlabDefectBuilderParameters, + AdatomSlabDefectBuilder, + EquidistantAdatomSlabDefectBuilder, +) from .configuration import PointDefectConfiguration, AdatomSlabDefectConfiguration from .enums import PointDefectTypeEnum, SlabDefectTypeEnum @@ -13,7 +18,6 @@ class DefectBuilderFactory(BaseFactory): PointDefectTypeEnum.VACANCY: "mat3ra.made.tools.build.defect.builders.VacancyPointDefectBuilder", PointDefectTypeEnum.SUBSTITUTION: "mat3ra.made.tools.build.defect.builders.SubstitutionPointDefectBuilder", PointDefectTypeEnum.INTERSTITIAL: "mat3ra.made.tools.build.defect.builders.InterstitialPointDefectBuilder", - SlabDefectTypeEnum.ADATOM: "mat3ra.made.tools.build.defect.builders.AdatomSlabDefectBuilder", } @@ -35,3 +39,12 @@ def create_defect( builder = BuilderClass(builder_parameters) return builder.get_material(configuration) if builder else configuration.crystal + + +def create_feature( + configuration: Union[AdatomSlabDefectConfiguration], + builder: Optional[Union[AdatomSlabDefectBuilder, EquidistantAdatomSlabDefectBuilder]] = None, +) -> Material: + if builder is None: + builder = AdatomSlabDefectBuilder(build_parameters=SlabDefectBuilderParameters()) + return builder.get_material(configuration) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 5a1f4c84..2d28c8ba 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -123,7 +123,7 @@ def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemTyp ) -class EquidistantAdatomSlabDefectBuilder(AdatomSlabDefectConfiguration): +class EquidistantAdatomSlabDefectBuilder(AdatomSlabDefectBuilder): def create_adatom( self, material: Material, From 7778b99a3f3c72b1a82b07e150ec859a5dd1583d Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 16:49:14 -0700 Subject: [PATCH 16/35] update: rename + docstring --- src/py/mat3ra/made/tools/build/defect/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/build/defect/__init__.py b/src/py/mat3ra/made/tools/build/defect/__init__.py index 8d9211cf..3cc599fd 100644 --- a/src/py/mat3ra/made/tools/build/defect/__init__.py +++ b/src/py/mat3ra/made/tools/build/defect/__init__.py @@ -41,10 +41,20 @@ def create_defect( return builder.get_material(configuration) if builder else configuration.crystal -def create_feature( +def create_slab_defect( configuration: Union[AdatomSlabDefectConfiguration], builder: Optional[Union[AdatomSlabDefectBuilder, EquidistantAdatomSlabDefectBuilder]] = None, ) -> Material: + """ + Return a material with a selected slab defect added. + + Args: + configuration: The configuration of the defect to be added. + builder: The builder to be used to create the defect. + + Returns: + The material with the defect added. + """ if builder is None: builder = AdatomSlabDefectBuilder(build_parameters=SlabDefectBuilderParameters()) return builder.get_material(configuration) From fd36d9d7531328978a2503dfe415bcc664d1a716 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:38:54 -0700 Subject: [PATCH 17/35] update: fix arrays logic --- src/py/mat3ra/made/tools/analyze.py | 2 +- .../made/tools/build/defect/builders.py | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 281113ee..52e7c8f4 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -267,7 +267,7 @@ def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: Returns: List[float]: The center of the coordinates. """ - return np.mean(np.array(coordinates), axis=0) + return list(np.mean(np.array(coordinates), axis=0)) def get_atomic_coordinates_extremum( diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 2d28c8ba..e0382ccb 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -1,4 +1,4 @@ -from typing import List, Callable +from typing import List, Callable, Optional from pydantic import BaseModel from mat3ra.made.material import Material @@ -89,7 +89,7 @@ def create_adatom( self, material: Material, chemical_element: str = "Si", - position_on_surface: List[float] = [0.5, 0.5], + position_on_surface: Optional[List[float]] = None, distance_z: float = 2.0, ) -> List[Material]: """ @@ -104,6 +104,9 @@ def create_adatom( Returns: The material with the adatom added. """ + if position_on_surface is None: + position_on_surface = [0.5, 0.5] + position_on_surface = position_on_surface[:2] new_material = material.clone() basis = new_material.basis distance_in_crystal_units = distance_z / new_material.lattice.c @@ -128,7 +131,7 @@ def create_adatom( self, material: Material, chemical_element: str = "Si", - position_on_surface: List[float] = [0.5, 0.5], + position_on_surface: Optional[List[float]] = None, distance_z: float = 2.0, ) -> List[Material]: """ @@ -144,11 +147,18 @@ def create_adatom( Returns: The material with the adatom added. """ - equidistant_position = self.get_equidistant_position(material, position_on_surface) + if position_on_surface is None: + position_on_surface = [0.5, 0.5] + equidistant_position = self.get_equidistant_position(material, position_on_surface, distance_z) return super().create_adatom(material, chemical_element, equidistant_position, distance_z) - def get_equidistant_position(self, material: Material, adatom_position: List[float]) -> List[float]: + def get_equidistant_position( + self, material: Material, position_on_surface: List[float], distance_z: float = 2.0 + ) -> List[float]: new_basis = material.basis + adatom_position = position_on_surface.copy() + distance_z_crystal = distance_z / material.lattice.c + adatom_position.append(get_atomic_coordinates_extremum(material) + distance_z_crystal) neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) if not neighboring_atoms_ids: raise ValueError("No neighboring atoms found.") From c9f768ff3b478092379d9dabcff9114f4223ff7a Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 17:58:22 -0700 Subject: [PATCH 18/35] update: optimize --- .../made/tools/build/defect/builders.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index e0382ccb..c929c2b1 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -108,14 +108,21 @@ def create_adatom( position_on_surface = [0.5, 0.5] position_on_surface = position_on_surface[:2] new_material = material.clone() - basis = new_material.basis - distance_in_crystal_units = distance_z / new_material.lattice.c - max_z = get_atomic_coordinates_extremum(new_material) + new_basis = new_material.basis + adatom_position = self._calculate_position_from_2d(material, position_on_surface, distance_z) + new_basis.add_atom(chemical_element, adatom_position) + new_material.basis = new_basis + return [new_material] + + def _calculate_position_from_2d( + self, material: Material, position_on_surface: List[float], distance_z: float + ) -> List[float]: + max_z = get_atomic_coordinates_extremum(material) + distance_z = distance_z + distance_in_crystal_units = distance_z / material.lattice.c position = position_on_surface.copy() position.append(max_z + distance_in_crystal_units) - basis.add_atom(chemical_element, position) - new_material.basis = basis - return [new_material] + return position def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]: return self.create_adatom( @@ -156,9 +163,7 @@ def get_equidistant_position( self, material: Material, position_on_surface: List[float], distance_z: float = 2.0 ) -> List[float]: new_basis = material.basis - adatom_position = position_on_surface.copy() - distance_z_crystal = distance_z / material.lattice.c - adatom_position.append(get_atomic_coordinates_extremum(material) + distance_z_crystal) + adatom_position = self._calculate_position_from_2d(material, position_on_surface, distance_z) neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) if not neighboring_atoms_ids: raise ValueError("No neighboring atoms found.") From bc3596ab27bbab21fcd09dbd39b2080b0f4effe0 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:15:31 -0700 Subject: [PATCH 19/35] update: add tests for adatom --- .../made/tools/build/defect/builders.py | 2 +- tests/py/unit/test_tools_build_defect.py | 44 ++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index c929c2b1..c463e0f8 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -106,7 +106,6 @@ def create_adatom( """ if position_on_surface is None: position_on_surface = [0.5, 0.5] - position_on_surface = position_on_surface[:2] new_material = material.clone() new_basis = new_material.basis adatom_position = self._calculate_position_from_2d(material, position_on_surface, distance_z) @@ -121,6 +120,7 @@ def _calculate_position_from_2d( distance_z = distance_z distance_in_crystal_units = distance_z / material.lattice.c position = position_on_surface.copy() + position = position[:2] position.append(max_z + distance_in_crystal_units) return position diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index bb8423e2..dc4a4c63 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -1,5 +1,16 @@ from mat3ra.made.material import Material -from mat3ra.made.tools.build.defect import PointDefectBuilderParameters, PointDefectConfiguration, create_defect +from mat3ra.made.tools.build.defect import ( + PointDefectBuilderParameters, + PointDefectConfiguration, + create_defect, + AdatomSlabDefectConfiguration, + create_slab_defect, + EquidistantAdatomSlabDefectBuilder, +) +from mat3ra.made.tools.build.slab import create_slab, SlabConfiguration + +from mat3ra.made.tools.build.slab import get_terminations +from mat3ra.utils import assertion as assertion_utils clean_material = Material.create(Material.default_config) @@ -49,3 +60,34 @@ def test_create_defect_from_site_id(): {"id": 0, "value": "Si"}, {"id": 1, "value": "Ge"}, ] + + +def test_create_adatom(): + # Adatom of Si at 0.5, 0.5 position + material = Material.create(Material.default_config) + slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) + t = get_terminations(slab_config)[0] + slab = create_slab(slab_config, t) + configuration = AdatomSlabDefectConfiguration( + crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + ) + defect = create_slab_defect(configuration=configuration, builder=None) + + assert defect.basis.elements.values[-1] == "Si" + assertion_utils.assert_deep_almost_equal([0.5, 0.5, 0.389826], defect.basis.coordinates.values[-1]) + + +def test_create_adatom_equidistant(): + # Adatom of Si at approximate 0.5, 0.5 position + material = Material.create(Material.default_config) + slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) + t = get_terminations(slab_config)[0] + slab = create_slab(slab_config, t) + configuration = AdatomSlabDefectConfiguration( + crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + ) + defect = create_slab_defect(configuration=configuration, builder=EquidistantAdatomSlabDefectBuilder()) + + assert defect.basis.elements.values[-1] == "Si" + # We expect adatom to shift from provided position + assertion_utils.assert_deep_almost_equal(defect.basis.coordinates.values[-1], [0.8333333, 0.4166666, 0.389826]) From 4223f28ef73fe7446af3fefd3cd5797bfa6abd48 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:15:48 -0700 Subject: [PATCH 20/35] update: add tests for adatom --- tests/py/unit/test_tools_build_defect.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index dc4a4c63..bad25ba8 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -1,15 +1,13 @@ from mat3ra.made.material import Material from mat3ra.made.tools.build.defect import ( + AdatomSlabDefectConfiguration, + EquidistantAdatomSlabDefectBuilder, PointDefectBuilderParameters, PointDefectConfiguration, create_defect, - AdatomSlabDefectConfiguration, create_slab_defect, - EquidistantAdatomSlabDefectBuilder, ) -from mat3ra.made.tools.build.slab import create_slab, SlabConfiguration - -from mat3ra.made.tools.build.slab import get_terminations +from mat3ra.made.tools.build.slab import SlabConfiguration, create_slab, get_terminations from mat3ra.utils import assertion as assertion_utils clean_material = Material.create(Material.default_config) From 2935658f3eff4e3d0b8232c9a56242b11e008527 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:49:42 -0700 Subject: [PATCH 21/35] update: move func to utils --- src/py/mat3ra/made/tools/analyze.py | 13 ------------- src/py/mat3ra/made/tools/build/defect/builders.py | 3 ++- src/py/mat3ra/made/utils.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 52e7c8f4..068e4c13 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -257,19 +257,6 @@ def get_nearest_neighbors_atom_indices(material: Material, position: List[float] return neighboring_atoms_ids -def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: - """ - Calculate the center of the coordinates. - - Args: - coordinates (List[List[float]]): The list of coordinates. - - Returns: - List[float]: The center of the coordinates. - """ - return list(np.mean(np.array(coordinates), axis=0)) - - def get_atomic_coordinates_extremum( material: Material, extremum: Literal["max", "min"] = "max", diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index c463e0f8..413255e0 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -13,7 +13,8 @@ ) from ...build import BaseBuilder from ...convert import to_pymatgen -from ...analyze import get_nearest_neighbors_atom_indices, get_center_of_coordinates, get_atomic_coordinates_extremum +from ...analyze import get_nearest_neighbors_atom_indices, get_atomic_coordinates_extremum +from ....utils import get_center_of_coordinates from ..mixins import ConvertGeneratedItemsPymatgenStructureMixin from .configuration import PointDefectConfiguration, AdatomSlabDefectConfiguration diff --git a/src/py/mat3ra/made/utils.py b/src/py/mat3ra/made/utils.py index f5d5c1fa..9e002125 100644 --- a/src/py/mat3ra/made/utils.py +++ b/src/py/mat3ra/made/utils.py @@ -1,6 +1,7 @@ import json from typing import Any, Callable, Dict, List, Optional, Union +import numpy as np from mat3ra.utils.array import convert_to_array_if_not from mat3ra.utils.mixins import RoundNumericValuesMixin from pydantic import BaseModel @@ -44,6 +45,19 @@ def are_arrays_equal_by_id_value(array1: List[Dict[str, Any]], array2: List[Dict return map_array_with_id_value_to_array(array1) == map_array_with_id_value_to_array(array2) +def get_center_of_coordinates(coordinates: List[List[float]]) -> List[float]: + """ + Calculate the center of the coordinates. + + Args: + coordinates (List[List[float]]): The list of coordinates. + + Returns: + List[float]: The center of the coordinates. + """ + return list(np.mean(np.array(coordinates), axis=0)) + + class ValueWithId(RoundNumericValuesMixin, BaseModel): id: int = 0 value: Any = None From b9890e235face6d27626ff781044d0f0f117773f Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:49:55 -0700 Subject: [PATCH 22/35] try: fix test --- tests/py/unit/test_tools_build_defect.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index bad25ba8..04eb8082 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -12,6 +12,11 @@ clean_material = Material.create(Material.default_config) +material = Material.create(Material.default_config) +slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) +t = get_terminations(slab_config)[0] +slab = create_slab(slab_config, t) + def test_create_vacancy(): # vacancy in place of 0 element @@ -61,13 +66,10 @@ def test_create_defect_from_site_id(): def test_create_adatom(): + clean_slab = slab.clone() # Adatom of Si at 0.5, 0.5 position - material = Material.create(Material.default_config) - slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) - t = get_terminations(slab_config)[0] - slab = create_slab(slab_config, t) configuration = AdatomSlabDefectConfiguration( - crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + crystal=clean_slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" ) defect = create_slab_defect(configuration=configuration, builder=None) @@ -76,16 +78,13 @@ def test_create_adatom(): def test_create_adatom_equidistant(): + clean_slab = slab.clone() # Adatom of Si at approximate 0.5, 0.5 position - material = Material.create(Material.default_config) - slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) - t = get_terminations(slab_config)[0] - slab = create_slab(slab_config, t) configuration = AdatomSlabDefectConfiguration( - crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + crystal=clean_slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" ) defect = create_slab_defect(configuration=configuration, builder=EquidistantAdatomSlabDefectBuilder()) assert defect.basis.elements.values[-1] == "Si" # We expect adatom to shift from provided position - assertion_utils.assert_deep_almost_equal(defect.basis.coordinates.values[-1], [0.8333333, 0.4166666, 0.389826]) + assertion_utils.assert_deep_almost_equal([0.8333333, 0.4166666, 0.389826], defect.basis.coordinates.values[-1]) From e16e90a9227695e0d451947abeed66599e7b0a7f Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:00:46 -0700 Subject: [PATCH 23/35] try: fix test 2 --- src/py/mat3ra/made/tools/analyze.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index 068e4c13..a2f65bf1 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -231,7 +231,9 @@ def get_atom_indices_with_condition_on_coordinates( return selected_indices -def get_nearest_neighbors_atom_indices(material: Material, position: List[float] = [0, 0, 0]) -> Optional[List[int]]: +def get_nearest_neighbors_atom_indices( + material: Material, position: Optional[List[float]] = None +) -> Optional[List[int]]: """ Returns the indices of direct neighboring atoms to a specified position in the material using Voronoi tessellation. @@ -242,6 +244,8 @@ def get_nearest_neighbors_atom_indices(material: Material, position: List[float] Returns: List[int]: A list of indices of neighboring atoms, or an empty list if no neighbors are found. """ + if position is None: + position = [0, 0, 0] structure = to_pymatgen(material) voronoi_nn = PymatgenVoronoiNN(tol=0.5) From d8da1e256fccb96cd4963daafc8c39ffdea0249c Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:16:16 -0700 Subject: [PATCH 24/35] update: manage vacuum --- src/py/mat3ra/made/tools/build/defect/builders.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 413255e0..c0cf20a6 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -1,5 +1,6 @@ from typing import List, Callable, Optional +from mat3ra.made.tools.modify import add_vacuum from pydantic import BaseModel from mat3ra.made.material import Material @@ -158,7 +159,15 @@ def create_adatom( if position_on_surface is None: position_on_surface = [0.5, 0.5] equidistant_position = self.get_equidistant_position(material, position_on_surface, distance_z) - return super().create_adatom(material, chemical_element, equidistant_position, distance_z) + new_material = material.clone() + if equidistant_position[2] > 1: + if self.build_parameters.auto_add_vacuum: + new_material = add_vacuum(material, self.build_parameters.vacuum_thickness) + equidistant_position = self.get_equidistant_position(new_material, position_on_surface, distance_z) + else: + raise ValueError("Not enough vacuum space to place the adatom.") + + return super().create_adatom(new_material, chemical_element, equidistant_position, distance_z) def get_equidistant_position( self, material: Material, position_on_surface: List[float], distance_z: float = 2.0 @@ -172,7 +181,5 @@ def get_equidistant_position( equidistant_position = get_center_of_coordinates(neighboring_atoms_coordinates) equidistant_position[2] = adatom_position[2] - if equidistant_position[2] > new_basis.cell.vectors_as_nested_array[2][2]: - raise ValueError("The adatom position is outside the cell.") return equidistant_position From b5c17514f22234885c74de2ac4fae63245c77c1d Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:21:42 -0700 Subject: [PATCH 25/35] chore: cleanup --- tests/py/unit/test_tools_build_defect.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index 04eb8082..0d53a09e 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -12,8 +12,7 @@ clean_material = Material.create(Material.default_config) -material = Material.create(Material.default_config) -slab_config = SlabConfiguration(material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) +slab_config = SlabConfiguration(clean_material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) t = get_terminations(slab_config)[0] slab = create_slab(slab_config, t) @@ -66,10 +65,9 @@ def test_create_defect_from_site_id(): def test_create_adatom(): - clean_slab = slab.clone() # Adatom of Si at 0.5, 0.5 position configuration = AdatomSlabDefectConfiguration( - crystal=clean_slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" ) defect = create_slab_defect(configuration=configuration, builder=None) @@ -78,10 +76,9 @@ def test_create_adatom(): def test_create_adatom_equidistant(): - clean_slab = slab.clone() # Adatom of Si at approximate 0.5, 0.5 position configuration = AdatomSlabDefectConfiguration( - crystal=clean_slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" + crystal=slab, position_on_surface=[0.51, 0.5], distance_z=2, chemical_element="Si" ) defect = create_slab_defect(configuration=configuration, builder=EquidistantAdatomSlabDefectBuilder()) From 9a975e1c5aa545ba51c5d441ff8de59bad01abd6 Mon Sep 17 00:00:00 2001 From: VsevolodX Date: Sun, 7 Jul 2024 20:50:29 -0700 Subject: [PATCH 26/35] try: set pymatgen to v used in AX --- pyproject.toml | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 995b93ec..efc61add 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,8 @@ dynamic = ["version"] description = "MAterials DEfinitions and/or MAterials DEsign library." readme = "README.md" requires-python = ">=3.8" -license = {file = "LICENSE.md"} -authors = [ - {name = "Exabyte Inc.", email = "info@mat3ra.com"} -] +license = { file = "LICENSE.md" } +authors = [{ name = "Exabyte Inc.", email = "info@mat3ra.com" }] classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", @@ -25,19 +23,8 @@ dependencies = [ [project.optional-dependencies] # tracking separately the deps required to use the tools module -tools = [ - "pymatgen", - "ase", - "pymatgen-analysis-defects" -] -dev = [ - "pre-commit", - "black", - "ruff", - "isort", - "mypy", - "pip-tools", -] +tools = ["pymatgen==2024.4.13", "ase", "pymatgen-analysis-defects"] +dev = ["pre-commit", "black", "ruff", "isort", "mypy", "pip-tools"] tests = [ "coverage[toml]>=5.3", "pytest", @@ -47,10 +34,7 @@ tests = [ "pydantic", "mat3ra-made[tools]", ] -all = [ - "mat3ra-made[tests]", - "mat3ra-made[dev]", -] +all = ["mat3ra-made[tests]", "mat3ra-made[dev]"] # Entrypoint scripts can be defined here, see examples below. [project.scripts] @@ -58,10 +42,7 @@ all = [ [build-system] -requires = [ - "setuptools>=42", - "setuptools-scm[toml]>=3.4" -] +requires = ["setuptools>=42", "setuptools-scm[toml]>=3.4"] build-backend = "setuptools.build_meta" [tool.setuptools_scm] @@ -84,10 +65,7 @@ extend-exclude = ''' [tool.ruff] # Exclude a variety of commonly ignored directories. -extend-exclude = [ - "src/js", - "tests/fixtures" -] +extend-exclude = ["src/js", "tests/fixtures"] line-length = 120 target-version = "py38" From b4177c8d2f6fc4ff6d019b0ac1682a5a10836122 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:44:49 -0700 Subject: [PATCH 27/35] temp: disable some py versions --- .github/workflows/cicd.yml | 4 ++-- tests/py/unit/test_tools_build_defect.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 8b74d0da..44312987 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -37,8 +37,8 @@ jobs: strategy: matrix: python-version: - - 3.8.x - - 3.9.x +# - 3.8.x +# - 3.9.x - 3.10.x - 3.11.x diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index 0d53a09e..d966547e 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -78,7 +78,7 @@ def test_create_adatom(): def test_create_adatom_equidistant(): # Adatom of Si at approximate 0.5, 0.5 position configuration = AdatomSlabDefectConfiguration( - crystal=slab, position_on_surface=[0.51, 0.5], distance_z=2, chemical_element="Si" + crystal=slab, position_on_surface=[0.5, 0.5], distance_z=2, chemical_element="Si" ) defect = create_slab_defect(configuration=configuration, builder=EquidistantAdatomSlabDefectBuilder()) From 5def0e9fb68bac4e487f94a766e7ed7c4b1baabf Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:46:36 -0700 Subject: [PATCH 28/35] chore: unfix pymatgen version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index efc61add..f4e41947 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ [project.optional-dependencies] # tracking separately the deps required to use the tools module -tools = ["pymatgen==2024.4.13", "ase", "pymatgen-analysis-defects"] +tools = ["pymatgen", "ase", "pymatgen-analysis-defects"] dev = ["pre-commit", "black", "ruff", "isort", "mypy", "pip-tools"] tests = [ "coverage[toml]>=5.3", From 9d017f722161243400a6952aae7cd9345da0baa9 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:37:50 -0700 Subject: [PATCH 29/35] update: check for meaningfull adatom --- src/py/mat3ra/made/tools/build/defect/builders.py | 13 ++++++++++++- tests/py/unit/test_tools_build_defect.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index c0cf20a6..b3c5b9e5 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -1,5 +1,6 @@ from typing import List, Callable, Optional +from mat3ra.made.tools.build.supercell import create_supercell from mat3ra.made.tools.modify import add_vacuum from pydantic import BaseModel from mat3ra.made.material import Material @@ -175,8 +176,18 @@ def get_equidistant_position( new_basis = material.basis adatom_position = self._calculate_position_from_2d(material, position_on_surface, distance_z) neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) - if not neighboring_atoms_ids: + # check if neighboring atoms number is the same in the 3x3x1 supercell + supercell_material = create_supercell(material, [[3, 0, 0], [0, 3, 0], [0, 0, 1]]) + # Move the coordinate to the central unit cell of the supercell (crystal coordinates) + + supercell_adatom_position = [1 / 3 + adatom_position[0] / 3, 1 / 3 + adatom_position[1] / 3, adatom_position[2]] + supercell_neighboring_atoms_ids = get_nearest_neighbors_atom_indices( + supercell_material, supercell_adatom_position + ) + if not (neighboring_atoms_ids and supercell_neighboring_atoms_ids): raise ValueError("No neighboring atoms found.") + if len(supercell_neighboring_atoms_ids) != len(neighboring_atoms_ids): + raise ValueError("The supercell is too small to add a meaningful equidistant adatom.") neighboring_atoms_coordinates = [new_basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] equidistant_position = get_center_of_coordinates(neighboring_atoms_coordinates) diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index d966547e..dcde2d1f 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -12,7 +12,14 @@ clean_material = Material.create(Material.default_config) -slab_config = SlabConfiguration(clean_material, (1, 1, 1), thickness=3, vacuum=6, use_orthogonal_z=True) +slab_config = SlabConfiguration( + clean_material, + (1, 1, 1), + thickness=3, + vacuum=6, + use_orthogonal_z=True, + xy_supercell_matrix=[[2, 0, 0], [0, 2, 0], [0, 0, 1]], +) t = get_terminations(slab_config)[0] slab = create_slab(slab_config, t) @@ -84,4 +91,4 @@ def test_create_adatom_equidistant(): assert defect.basis.elements.values[-1] == "Si" # We expect adatom to shift from provided position - assertion_utils.assert_deep_almost_equal([0.8333333, 0.4166666, 0.389826], defect.basis.coordinates.values[-1]) + assertion_utils.assert_deep_almost_equal([0.58333333, 0.4166666, 0.389826], defect.basis.coordinates.values[-1]) From 97841119ddb2a4dc9c532cee65594756514eabd8 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:38:00 -0700 Subject: [PATCH 30/35] update: check for meaningfull adatom --- src/py/mat3ra/made/tools/build/defect/builders.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index b3c5b9e5..5a315e80 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -176,10 +176,9 @@ def get_equidistant_position( new_basis = material.basis adatom_position = self._calculate_position_from_2d(material, position_on_surface, distance_z) neighboring_atoms_ids = get_nearest_neighbors_atom_indices(material, adatom_position) - # check if neighboring atoms number is the same in the 3x3x1 supercell + # We need to check if neighboring atoms number is the same in pbc supercell_material = create_supercell(material, [[3, 0, 0], [0, 3, 0], [0, 0, 1]]) # Move the coordinate to the central unit cell of the supercell (crystal coordinates) - supercell_adatom_position = [1 / 3 + adatom_position[0] / 3, 1 / 3 + adatom_position[1] / 3, adatom_position[2]] supercell_neighboring_atoms_ids = get_nearest_neighbors_atom_indices( supercell_material, supercell_adatom_position From a9c837e1304e0a8c268f8c7cd8d7e626ba39f0cd Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:14:36 -0700 Subject: [PATCH 31/35] update: fix tests --- .github/workflows/cicd.yml | 4 ++-- src/py/mat3ra/made/tools/analyze.py | 8 +++----- src/py/mat3ra/made/tools/build/defect/builders.py | 2 +- tests/py/unit/test_tools_build_defect.py | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 44312987..8b74d0da 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -37,8 +37,8 @@ jobs: strategy: matrix: python-version: -# - 3.8.x -# - 3.9.x + - 3.8.x + - 3.9.x - 3.10.x - 3.11.x diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index a2f65bf1..bd2840ab 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -232,7 +232,8 @@ def get_atom_indices_with_condition_on_coordinates( def get_nearest_neighbors_atom_indices( - material: Material, position: Optional[List[float]] = None + material: Material, + position: Optional[List[float]] = None, ) -> Optional[List[int]]: """ Returns the indices of direct neighboring atoms to a specified position in the material using Voronoi tessellation. @@ -247,7 +248,6 @@ def get_nearest_neighbors_atom_indices( if position is None: position = [0, 0, 0] structure = to_pymatgen(material) - voronoi_nn = PymatgenVoronoiNN(tol=0.5) structure.append("X", position, validate_proximity=False) neighbors = voronoi_nn.get_nn_info(structure, len(structure.sites) - 1) @@ -256,9 +256,7 @@ def get_nearest_neighbors_atom_indices( all_coordinates = material.basis.coordinates all_coordinates.filter_by_indices(neighboring_atoms_pymatgen_ids) - neighboring_atoms_ids = all_coordinates.ids - - return neighboring_atoms_ids + return all_coordinates.ids def get_atomic_coordinates_extremum( diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 5a315e80..4b7e3c61 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -183,7 +183,7 @@ def get_equidistant_position( supercell_neighboring_atoms_ids = get_nearest_neighbors_atom_indices( supercell_material, supercell_adatom_position ) - if not (neighboring_atoms_ids and supercell_neighboring_atoms_ids): + if neighboring_atoms_ids is None or supercell_neighboring_atoms_ids is None: raise ValueError("No neighboring atoms found.") if len(supercell_neighboring_atoms_ids) != len(neighboring_atoms_ids): raise ValueError("The supercell is too small to add a meaningful equidistant adatom.") diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index dcde2d1f..70269ba3 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -91,4 +91,4 @@ def test_create_adatom_equidistant(): assert defect.basis.elements.values[-1] == "Si" # We expect adatom to shift from provided position - assertion_utils.assert_deep_almost_equal([0.58333333, 0.4166666, 0.389826], defect.basis.coordinates.values[-1]) + assertion_utils.assert_deep_almost_equal([0.583333333, 0.541666667, 0.389826], defect.basis.coordinates.values[-1]) From f3913dfc8d595d56b3d379eda5ff1a93ebdedf11 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:38:03 -0700 Subject: [PATCH 32/35] try: set parameters explicitly --- src/py/mat3ra/made/tools/analyze.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/py/mat3ra/made/tools/analyze.py b/src/py/mat3ra/made/tools/analyze.py index bd2840ab..74dd5c4a 100644 --- a/src/py/mat3ra/made/tools/analyze.py +++ b/src/py/mat3ra/made/tools/analyze.py @@ -248,7 +248,14 @@ def get_nearest_neighbors_atom_indices( if position is None: position = [0, 0, 0] structure = to_pymatgen(material) - voronoi_nn = PymatgenVoronoiNN(tol=0.5) + voronoi_nn = PymatgenVoronoiNN( + tol=0.5, + cutoff=13.0, + allow_pathological=False, + weight="solid_angle", + extra_nn_info=True, + compute_adj_neighbors=True, + ) structure.append("X", position, validate_proximity=False) neighbors = voronoi_nn.get_nn_info(structure, len(structure.sites) - 1) neighboring_atoms_pymatgen_ids = [n["site_index"] for n in neighbors] From c449fd9d8c479f72a95eda3c77ff6beb71e845a8 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:01:18 -0700 Subject: [PATCH 33/35] update: adjust errors messages --- src/py/mat3ra/made/tools/build/defect/builders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/mat3ra/made/tools/build/defect/builders.py b/src/py/mat3ra/made/tools/build/defect/builders.py index 4b7e3c61..3c9988c2 100644 --- a/src/py/mat3ra/made/tools/build/defect/builders.py +++ b/src/py/mat3ra/made/tools/build/defect/builders.py @@ -184,9 +184,9 @@ def get_equidistant_position( supercell_material, supercell_adatom_position ) if neighboring_atoms_ids is None or supercell_neighboring_atoms_ids is None: - raise ValueError("No neighboring atoms found.") + raise ValueError("No neighboring atoms found. Try reducing the distance_z.") if len(supercell_neighboring_atoms_ids) != len(neighboring_atoms_ids): - raise ValueError("The supercell is too small to add a meaningful equidistant adatom.") + raise ValueError("Number of neighboring atoms is not the same in PBC. Try increasing the supercell size.") neighboring_atoms_coordinates = [new_basis.coordinates.values[atom_id] for atom_id in neighboring_atoms_ids] equidistant_position = get_center_of_coordinates(neighboring_atoms_coordinates) From 83c58fc002acc3b3d72b26e14aedcd62352206ea Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:01:38 -0700 Subject: [PATCH 34/35] chore: tests pass on GHA --- tests/py/unit/test_tools_build_defect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/py/unit/test_tools_build_defect.py b/tests/py/unit/test_tools_build_defect.py index 70269ba3..050c9896 100644 --- a/tests/py/unit/test_tools_build_defect.py +++ b/tests/py/unit/test_tools_build_defect.py @@ -91,4 +91,4 @@ def test_create_adatom_equidistant(): assert defect.basis.elements.values[-1] == "Si" # We expect adatom to shift from provided position - assertion_utils.assert_deep_almost_equal([0.583333333, 0.541666667, 0.389826], defect.basis.coordinates.values[-1]) + assertion_utils.assert_deep_almost_equal([0.4583333333, 0.541666667, 0.389826], defect.basis.coordinates.values[-1]) From 27cc7c5564ce3ffe4c0ba584bff586509e28bd44 Mon Sep 17 00:00:00 2001 From: VsevolodX <79542055+VsevolodX@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:12:47 -0700 Subject: [PATCH 35/35] chore: restore file --- pyproject.toml | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f4e41947..995b93ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,10 @@ dynamic = ["version"] description = "MAterials DEfinitions and/or MAterials DEsign library." readme = "README.md" requires-python = ">=3.8" -license = { file = "LICENSE.md" } -authors = [{ name = "Exabyte Inc.", email = "info@mat3ra.com" }] +license = {file = "LICENSE.md"} +authors = [ + {name = "Exabyte Inc.", email = "info@mat3ra.com"} +] classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", @@ -23,8 +25,19 @@ dependencies = [ [project.optional-dependencies] # tracking separately the deps required to use the tools module -tools = ["pymatgen", "ase", "pymatgen-analysis-defects"] -dev = ["pre-commit", "black", "ruff", "isort", "mypy", "pip-tools"] +tools = [ + "pymatgen", + "ase", + "pymatgen-analysis-defects" +] +dev = [ + "pre-commit", + "black", + "ruff", + "isort", + "mypy", + "pip-tools", +] tests = [ "coverage[toml]>=5.3", "pytest", @@ -34,7 +47,10 @@ tests = [ "pydantic", "mat3ra-made[tools]", ] -all = ["mat3ra-made[tests]", "mat3ra-made[dev]"] +all = [ + "mat3ra-made[tests]", + "mat3ra-made[dev]", +] # Entrypoint scripts can be defined here, see examples below. [project.scripts] @@ -42,7 +58,10 @@ all = ["mat3ra-made[tests]", "mat3ra-made[dev]"] [build-system] -requires = ["setuptools>=42", "setuptools-scm[toml]>=3.4"] +requires = [ + "setuptools>=42", + "setuptools-scm[toml]>=3.4" +] build-backend = "setuptools.build_meta" [tool.setuptools_scm] @@ -65,7 +84,10 @@ extend-exclude = ''' [tool.ruff] # Exclude a variety of commonly ignored directories. -extend-exclude = ["src/js", "tests/fixtures"] +extend-exclude = [ + "src/js", + "tests/fixtures" +] line-length = 120 target-version = "py38"