Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/SOF 7413 #154

Merged
merged 92 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
27410b4
feat: add undercoordiantein analysis
VsevolodX Aug 15, 2024
365bfd0
feat: add passivation modfy:
VsevolodX Aug 15, 2024
312786a
update: add atom to material
VsevolodX Aug 15, 2024
b6fda33
Merge branch 'main' into feature/SOF-7413
VsevolodX Aug 17, 2024
16d83d6
update: add undercoordination function
VsevolodX Aug 21, 2024
35d6f86
update: fix hashable
VsevolodX Aug 21, 2024
9a5e7c8
feat: add passivation calculation
VsevolodX Aug 21, 2024
475c1cc
chore: run lint fix
VsevolodX Aug 21, 2024
9757591
update: temporarily two different nn calculations
VsevolodX Aug 21, 2024
7895123
update: passivate surface cirrectly
VsevolodX Aug 21, 2024
e646efb
chore: run lint fix
VsevolodX Aug 21, 2024
48734a9
Merge branch 'main' into feature/SOF-7413
VsevolodX Aug 21, 2024
bd34d44
chore: cleanup
VsevolodX Aug 22, 2024
ac7a67b
update: generalize for supercells
VsevolodX Aug 22, 2024
9dc1880
update: make work for 3d materials
VsevolodX Aug 22, 2024
6854d54
update: cleanup
VsevolodX Aug 22, 2024
9fa3833
update: correctly transform between central cell
VsevolodX Aug 22, 2024
1912a0a
feat: add simple passivation
VsevolodX Aug 22, 2024
05bbe35
update: optimize and add threshold
VsevolodX Aug 22, 2024
66c1b7c
update: give cotnrol for both surfaces
VsevolodX Aug 22, 2024
acf43de
update: add tests for passivation
VsevolodX Aug 23, 2024
b67bc99
update: combine coordinate and atom nn functions
VsevolodX Aug 23, 2024
93b670a
chore: run lint fix
VsevolodX Aug 23, 2024
ceb325f
update: add passivation for edges
VsevolodX Aug 23, 2024
65a7b76
chore: run lint fix
VsevolodX Aug 23, 2024
66d96ea
feat: add kd tree surface atoms calculation
VsevolodX Aug 23, 2024
64460e9
update: add z factor to look deeper
VsevolodX Aug 23, 2024
6169470
update: small adjustment
VsevolodX Aug 24, 2024
a63e32f
update :add missing handy function
VsevolodX Aug 24, 2024
1db1ccd
update: speedup using np
VsevolodX Aug 24, 2024
3968a14
fix: get more of the island by selecting deeper
VsevolodX Aug 24, 2024
cdd7f86
fix: adjust test since the island creation fixed
VsevolodX Aug 24, 2024
356bad8
feat: add passivate top function
VsevolodX Aug 24, 2024
88e6ace
update: add shadowing parameter
VsevolodX Aug 24, 2024
3719024
update: optimize
VsevolodX Aug 25, 2024
8db314a
feat: add get id by value
VsevolodX Aug 25, 2024
e7ae0c4
update: optimize search
VsevolodX Aug 25, 2024
97563e0
update: add structure for passivation builders
VsevolodX Aug 25, 2024
af7b1b1
update: move passivation functions
VsevolodX Aug 25, 2024
b05bf5b
update: move passivation functions
VsevolodX Aug 26, 2024
015f712
chore: add types
VsevolodX Aug 26, 2024
611ecfc
update: move passivation tests
VsevolodX Aug 26, 2024
69cdc0b
update: add enums
VsevolodX Aug 26, 2024
ab9f2dc
feat: add surface and edge configs
VsevolodX Aug 26, 2024
589ba02
updaet: wip on passivation builders
VsevolodX Aug 26, 2024
2793a49
Merge branch 'feature/SOF-7442' into feature/SOF-7413
VsevolodX Aug 26, 2024
acc6da3
update: adjust analyze
VsevolodX Aug 26, 2024
5a90895
chore: small rename
VsevolodX Aug 26, 2024
e931db5
update: create surface passivation
VsevolodX Aug 26, 2024
eccb861
update: correctly detect surface
VsevolodX Aug 26, 2024
0235224
chore: renames
VsevolodX Aug 26, 2024
203c0c9
update: remove from modify
VsevolodX Aug 26, 2024
77d64bc
update: adjust test to use new builders
VsevolodX Aug 26, 2024
23f172d
update: add metadata to fixture
VsevolodX Aug 26, 2024
e298829
chore: run lint fix
VsevolodX Aug 26, 2024
3ddb0e7
update: apply OOP
VsevolodX Aug 26, 2024
3af5701
feat: add undercocodintion detection
VsevolodX Aug 27, 2024
7b5323e
update: use undercoordination
VsevolodX Aug 27, 2024
ff5f611
update: remove redundant
VsevolodX Aug 27, 2024
0634b9d
update: add vector away from local neighbors
VsevolodX Aug 27, 2024
de30779
update: simplify
VsevolodX Aug 27, 2024
1623de1
chore: renames and add types
VsevolodX Aug 27, 2024
6199ed4
update: simplify and OOP
VsevolodX Aug 27, 2024
c4125b1
feat: add PBC handler decorator
VsevolodX Aug 29, 2024
8e1e692
udpate: optimize performance and deps
VsevolodX Aug 29, 2024
e908fe6
update: some fixes for tests to pass
VsevolodX Aug 29, 2024
e7df9f4
update: correctly handle diagonal cells
VsevolodX Aug 29, 2024
cbc4131
update: fix exessive after decorator
VsevolodX Aug 29, 2024
499d8ae
update: add try for faulty nn
VsevolodX Aug 30, 2024
bd996d5
update: don't add colliding atom
VsevolodX Aug 30, 2024
008264d
chore: run lint fix
VsevolodX Aug 30, 2024
f841cb6
chore: add docstring
VsevolodX Sep 8, 2024
1cc1971
update: simplify and add docstring
VsevolodX Sep 9, 2024
32461ef
update: add coreect cartesian detection of collision
VsevolodX Sep 9, 2024
4b04911
update: fix test
VsevolodX Sep 9, 2024
13d8538
update: add top level function to create passivation
VsevolodX Sep 9, 2024
60e8b69
update: move surface enums to top level to avoid circular deps
VsevolodX Sep 9, 2024
35f40f4
chore: run lint fix and types
VsevolodX Sep 9, 2024
ddfc661
update: specify cart or cryst coords for add atom
VsevolodX Sep 10, 2024
a20bb2d
chore: make simpler, i think
VsevolodX Sep 10, 2024
e281136
update: add docstring
VsevolodX Sep 10, 2024
51d878f
update: add link to voronoi
VsevolodX Sep 10, 2024
a42aea8
chore: try to explain shadowing
VsevolodX Sep 10, 2024
fa2c269
chore: rename
VsevolodX Sep 10, 2024
c7bac59
update: use cartesians to make easier to understand
VsevolodX Sep 10, 2024
add238c
chore: remove unused
VsevolodX Sep 10, 2024
70e5716
chore: add description of depth for passivation
VsevolodX Sep 10, 2024
943d315
chore: run lint fix
VsevolodX Sep 10, 2024
99aca0a
test: print island
VsevolodX Sep 10, 2024
6bd32c7
test: print island
VsevolodX Sep 10, 2024
8da0d5e
update: fix test by using 001 si slab since this doesnt have any disc…
VsevolodX Sep 10, 2024
3e89a8f
update: rename function
VsevolodX Sep 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 38 additions & 3 deletions src/py/mat3ra/made/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
from pydantic import BaseModel

from .cell import Cell
from .utils import ArrayWithIds
from .utils import ArrayWithIds, get_overlapping_coordinates


class Basis(RoundNumericValuesMixin, BaseModel):
elements: ArrayWithIds = ArrayWithIds(values=["Si"])
coordinates: ArrayWithIds = ArrayWithIds(values=[0, 0, 0])
units: str = AtomicCoordinateUnits.crystal
cell: Optional[Cell] = None
cell: Cell = Cell()
labels: Optional[ArrayWithIds] = ArrayWithIds(values=[])
constraints: Optional[ArrayWithIds] = ArrayWithIds(values=[])

Expand Down Expand Up @@ -76,7 +76,42 @@ def to_crystal(self):
self.coordinates.map_array_in_place(self.cell.convert_point_to_crystal)
self.units = AtomicCoordinateUnits.crystal

def add_atom(self, element="Si", coordinate=[0.5, 0.5, 0.5]):
def add_atom(
self,
element="Si",
coordinate: Optional[List[float]] = None,
use_cartesian_coordinates: bool = False,
force: bool = False,
):
"""
Add an atom to the basis.

Before adding the atom at the specified coordinate, checks that no other atom is overlapping within a threshold.

Args:
element (str): Element symbol of the atom to be added.
coordinate (List[float]): Coordinate of the atom to be added.
use_cartesian_coordinates (bool): Whether the coordinate is in Cartesian units (or crystal by default).
force (bool): Whether to force adding the atom even if it overlaps with another atom.
"""
if coordinate is None:
coordinate = [0, 0, 0]
if use_cartesian_coordinates and self.is_in_crystal_units:
coordinate = self.cell.convert_point_to_crystal(coordinate)
if not use_cartesian_coordinates and self.is_in_cartesian_units:
coordinate = self.cell.convert_point_to_cartesian(coordinate)
cartesian_coordinates_for_overlap_check = [
self.cell.convert_point_to_cartesian(coord) for coord in self.coordinates.values
]
cartesian_coordinate_for_overlap_check = self.cell.convert_point_to_cartesian(coordinate)
if get_overlapping_coordinates(
cartesian_coordinate_for_overlap_check, cartesian_coordinates_for_overlap_check, threshold=0.01
):
if force:
print(f"Warning: Overlapping coordinates found for {coordinate}. Adding atom anyway.")
else:
print(f"Warning: Overlapping coordinates found for {coordinate}. Not adding atom.")
return
self.elements.add_item(element)
self.coordinates.add_item(coordinate)

Expand Down
5 changes: 5 additions & 0 deletions src/py/mat3ra/made/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ def set_new_lattice_vectors(
self.basis = new_basis
lattice = Lattice.from_vectors_array([lattice_vector1, lattice_vector2, lattice_vector3])
self.lattice = lattice

def add_atom(self, element: str, coordinate: List[float]) -> None:
timurbazhirov marked this conversation as resolved.
Show resolved Hide resolved
new_basis = self.basis.copy()
new_basis.add_atom(element, coordinate)
self.basis = new_basis
159 changes: 152 additions & 7 deletions src/py/mat3ra/made/tools/analyze.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from typing import Callable, List, Literal, Optional

import numpy as np
from scipy.spatial import cKDTree

from ..material import Material
from .convert import decorator_convert_material_args_kwargs_to_atoms, to_pymatgen
from .enums import SurfaceTypes
from .third_party import ASEAtoms, PymatgenIStructure, PymatgenVoronoiNN
from .utils import decorator_handle_periodic_boundary_conditions


@decorator_convert_material_args_kwargs_to_atoms
Expand Down Expand Up @@ -265,13 +268,19 @@ def get_atom_indices_with_condition_on_coordinates(
def get_nearest_neighbors_atom_indices(
material: Material,
coordinate: Optional[List[float]] = None,
tolerance: float = 0.1,
timurbazhirov marked this conversation as resolved.
Show resolved Hide resolved
cutoff: float = 13.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.
coordinate (List[float]): The position to find neighbors for.
tolerance (float): tolerance parameter for near-neighbor finding. Faces that are smaller than tol fraction
of the largest face are not included in the tessellation. (default: 0.1).
as per: https://pymatgen.org/pymatgen.analysis.html#pymatgen.analysis.local_env.VoronoiNN
cutoff (float): The cutoff radius for identifying neighbors, in angstroms.
Returns:
List[int]: A list of indices of neighboring atoms, or an empty list if no neighbors are found.
Expand All @@ -280,17 +289,26 @@ def get_nearest_neighbors_atom_indices(
coordinate = [0, 0, 0]
structure = to_pymatgen(material)
voronoi_nn = PymatgenVoronoiNN(
tol=0.5,
cutoff=15.0,
allow_pathological=False,
tol=tolerance,
cutoff=cutoff,
weight="solid_angle",
extra_nn_info=True,
extra_nn_info=False,
compute_adj_neighbors=True,
)
structure.append("X", coordinate, validate_proximity=False)
neighbors = voronoi_nn.get_nn_info(structure, len(structure.sites) - 1)
coordinates = material.basis.coordinates
site_index = coordinates.get_element_id_by_value(coordinate)
remove_dummy_atom = False
if site_index is None:
structure.append("X", coordinate, validate_proximity=False)
site_index = len(structure.sites) - 1
remove_dummy_atom = True
try:
neighbors = voronoi_nn.get_nn_info(structure, site_index)
except ValueError:
return None
neighboring_atoms_pymatgen_ids = [n["site_index"] for n in neighbors]
structure.remove_sites([-1])
if remove_dummy_atom:
structure.remove_sites([-1])

all_coordinates = material.basis.coordinates
all_coordinates.filter_by_indices(neighboring_atoms_pymatgen_ids)
Expand Down Expand Up @@ -324,3 +342,130 @@ def get_atomic_coordinates_extremum(
coordinates = new_material.basis.coordinates.to_array_of_values_with_ids()
values = [coord.value[{"x": 0, "y": 1, "z": 2}[axis]] for coord in coordinates]
return getattr(np, extremum)(values)


def is_height_within_limits(z: float, z_extremum: float, depth: float, surface: SurfaceTypes) -> bool:
"""
Check if the height of an atom is within the specified limits.
Args:
z (float): The z-coordinate of the atom.
z_extremum (float): The extremum z-coordinate of the surface.
depth (float): The depth from the surface to look for exposed atoms.
surface (SurfaceTypes): The surface type (top or bottom).
Returns:
bool: True if the height is within the limits, False otherwise.
"""
return (z >= z_extremum - depth) if surface == SurfaceTypes.TOP else (z <= z_extremum + depth)


def is_shadowed_by_neighbors_from_surface(
z: float, neighbors_indices: List[int], surface: SurfaceTypes, coordinates: np.ndarray
) -> bool:
"""
Check if any one of the neighboring atoms shadow the atom from the surface by being closer to the specified surface.
Args:
z (float): The z-coordinate of the atom.
neighbors_indices (List[int]): List of indices of neighboring atoms.
surface (SurfaceTypes): The surface type (top or bottom).
coordinates (np.ndarray): The coordinates of the atoms.
Returns:
bool: True if the atom is not shadowed, False otherwise.
"""
return not any(
(coordinates[n][2] > z if surface == SurfaceTypes.TOP else coordinates[n][2] < z) for n in neighbors_indices
)


@decorator_handle_periodic_boundary_conditions(cutoff=0.1)
def get_surface_atom_indices(
material: Material, surface: SurfaceTypes = SurfaceTypes.TOP, shadowing_radius: float = 2.5, depth: float = 5
) -> List[int]:
"""
Identify exposed atoms on the top or bottom surface of the material.
Args:
material (Material): Material object to get surface atoms from.
surface (SurfaceTypes): Specify "top" or "bottom" to detect the respective surface atoms.
shadowing_radius (float): Radius for atoms shadowing underlying from detecting as exposed.
depth (float): Depth from the surface to look for exposed atoms.
Returns:
List[int]: List of indices of exposed surface atoms.
"""
new_material = material.clone()
new_material.to_cartesian()
coordinates = np.array(new_material.basis.coordinates.values)
ids = new_material.basis.coordinates.ids
kd_tree = cKDTree(coordinates)

z_extremum = np.max(coordinates[:, 2]) if surface == SurfaceTypes.TOP else np.min(coordinates[:, 2])

exposed_atoms_indices = []
for idx, (x, y, z) in enumerate(coordinates):
if is_height_within_limits(z, z_extremum, depth, surface):
neighbors_indices = kd_tree.query_ball_point([x, y, z], r=shadowing_radius)
if is_shadowed_by_neighbors_from_surface(z, neighbors_indices, surface, coordinates):
exposed_atoms_indices.append(ids[idx])

return exposed_atoms_indices


def get_coordination_numbers(
material: Material,
indices: Optional[List[int]] = None,
cutoff: float = 3.0,
) -> List[int]:
"""
Calculate the coordination numbers of atoms in the material.
Args:
material (Material): Material object to calculate coordination numbers for.
indices (List[int]): List of atom indices to calculate coordination numbers for.
cutoff (float): The cutoff radius for identifying neighbors.
Returns:
List[int]: List of coordination numbers for each atom in the material.
"""
new_material = material.clone()
new_material.to_cartesian()
if indices is not None:
new_material.basis.coordinates.filter_by_indices(indices)
coordinates = np.array(new_material.basis.coordinates.values)
kd_tree = cKDTree(coordinates)

coordination_numbers = []
for idx, (x, y, z) in enumerate(coordinates):
neighbors = kd_tree.query_ball_point([x, y, z], r=cutoff)
# Explicitly remove the atom itself from the list of neighbors
neighbors = [n for n in neighbors if n != idx]
coordination_numbers.append(len(neighbors))

return coordination_numbers


@decorator_handle_periodic_boundary_conditions(cutoff=0.1)
def get_undercoordinated_atom_indices(
material: Material,
indices: List[int],
cutoff: float = 3.0,
coordination_threshold: int = 3,
) -> List[int]:
"""
Identify undercoordinated atoms among the specified indices in the material.
Args:
material (Material): Material object to identify undercoordinated atoms in.
indices (List[int]): List of atom indices to check for undercoordination.
cutoff (float): The cutoff radius for identifying neighbors.
coordination_threshold (int): The coordination number threshold for undercoordination.
Returns:
List[int]: List of indices of undercoordinated atoms.
"""
coordination_numbers = get_coordination_numbers(material, indices, cutoff)
undercoordinated_atoms_indices = [i for i, cn in enumerate(coordination_numbers) if cn <= coordination_threshold]
return undercoordinated_atoms_indices
4 changes: 2 additions & 2 deletions src/py/mat3ra/made/tools/build/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ def get_material(
) -> Material:
return self.get_materials(configuration, selector_parameters, post_process_parameters)[0]

def _update_material_name(self, material, configuration):
def _update_material_name(self, material, configuration) -> Material:
# Do nothing by default
return material

def _update_material_metadata(self, material, configuration):
def _update_material_metadata(self, material, configuration) -> Material:
if "build" not in material.metadata:
material.metadata["build"] = {}
material.metadata["build"]["configuration"] = configuration.to_json()
Expand Down
27 changes: 14 additions & 13 deletions src/py/mat3ra/made/tools/build/defect/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
get_closest_site_id_from_coordinate_and_element,
)
from ....utils import get_center_of_coordinates
from ...utils import transform_coordinate_to_supercell
from ...utils import coordinate as CoordinateCondition
from ...utils import transform_coordinate_to_supercell, coordinate as CoordinateCondition
from ..utils import merge_materials
from ..slab import SlabConfiguration, create_slab, Termination
from ..supercell import create_supercell
Expand Down Expand Up @@ -246,7 +245,7 @@ def get_equidistant_position(
)

neighboring_atoms_ids_in_supercell = get_nearest_neighbors_atom_indices(
supercell_material, adatom_coordinate_in_supercell
material=supercell_material, coordinate=adatom_coordinate_in_supercell
)
if neighboring_atoms_ids_in_supercell is None:
raise ValueError("No neighboring atoms found. Try reducing the distance_z.")
Expand Down Expand Up @@ -392,6 +391,10 @@ class IslandSlabDefectBuilder(SlabDefectBuilder):
_ConfigurationType: type(IslandSlabDefectConfiguration) = IslandSlabDefectConfiguration # type: ignore
_GeneratedItemType: Material = Material

@staticmethod
def _default_condition(coordinate: List[float]):
return True

def create_island(
self,
material: Material,
Expand All @@ -410,28 +413,26 @@ def create_island(
Returns:
The material with the island added.
"""

new_material = material.clone()
original_max_z = get_atomic_coordinates_extremum(new_material, use_cartesian_coordinates=False)
original_max_z = get_atomic_coordinates_extremum(new_material, use_cartesian_coordinates=True)
material_with_additional_layers = self.create_material_with_additional_layers(new_material, thickness)
added_layers_max_z = get_atomic_coordinates_extremum(material_with_additional_layers)

added_layers_max_z = get_atomic_coordinates_extremum(
material_with_additional_layers, use_cartesian_coordinates=True
)
if condition is None:

def condition(coordinate: List[float]):
return True
condition = self._default_condition

atoms_within_island = filter_by_condition_on_coordinates(
material=material_with_additional_layers,
condition=condition,
use_cartesian_coordinates=use_cartesian_coordinates,
)

# Filter atoms in the added layers
# Filter atoms in the added layers between the original and added layers
island_material = filter_by_box(
material=atoms_within_island,
min_coordinate=[0, 0, original_max_z],
max_coordinate=[1, 1, added_layers_max_z],
max_coordinate=[material.lattice.a, material.lattice.b, added_layers_max_z],
use_cartesian_coordinates=True,
)

return self.merge_slab_and_defect(island_material, new_material)
Expand Down
18 changes: 18 additions & 0 deletions src/py/mat3ra/made/tools/build/passivation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Union

from mat3ra.made.material import Material
from .configuration import PassivationConfiguration
from .builders import (
SurfacePassivationBuilder,
CoordinationBasedPassivationBuilder,
SurfacePassivationBuilderParameters,
)


def create_passivation(
configuration: PassivationConfiguration,
builder: Union[SurfacePassivationBuilder, CoordinationBasedPassivationBuilder, None] = None,
) -> Material:
if builder is None:
builder = SurfacePassivationBuilder(build_parameters=SurfacePassivationBuilderParameters())
return builder.get_material(configuration)
Loading
Loading