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 7412 #153

Merged
merged 31 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
86f57ee
feat: first implementation of a nanrobibon
VsevolodX Aug 10, 2024
edc6823
update: corrections
VsevolodX Aug 10, 2024
b94016b
chore: run lint fix
VsevolodX Aug 10, 2024
7ef3c76
Merge branch 'main' into feature/SOF-7412
VsevolodX Aug 14, 2024
ac25818
update: wrap in a builder
VsevolodX Aug 14, 2024
dffb6ba
update: make work + docstring
VsevolodX Aug 14, 2024
60996f6
update: make work correctly
VsevolodX Aug 14, 2024
6f20e05
update: fix for even number fo vacuum{
VsevolodX Aug 14, 2024
8a454d7
update: armchair case
VsevolodX Aug 14, 2024
cbc1d67
update: create nanoribbons in rectangular cell
VsevolodX Aug 16, 2024
5fd3752
update: small fix
VsevolodX Aug 16, 2024
0d13547
update: rotate lattice for armchair
VsevolodX Aug 19, 2024
09b7c25
update: cleanup
VsevolodX Aug 19, 2024
b507dff
update: split into functions
VsevolodX Aug 19, 2024
7500318
update: small adjustments
VsevolodX Aug 19, 2024
a35ce89
update: separate into files
VsevolodX Aug 19, 2024
5180699
update: add centering of nanoribbon
VsevolodX Aug 19, 2024
a218c21
update: add centering to modify
VsevolodX Aug 19, 2024
a6f3768
update: add a test for zigzag
VsevolodX Aug 20, 2024
cf2f84e
chore: fix a mistake
VsevolodX Aug 20, 2024
1c44ed6
chore: fix a mistake
VsevolodX Aug 20, 2024
7786f68
update: add a test for armchair
VsevolodX Aug 20, 2024
a51645d
chore: run lint fix
VsevolodX Aug 20, 2024
cb570f8
chore: fix a typo
VsevolodX Aug 20, 2024
b09a869
update: use enums
VsevolodX Aug 20, 2024
31791a4
chore: run lint fix
VsevolodX Aug 20, 2024
26ecf59
update: create base config
VsevolodX Aug 20, 2024
f0ba249
update: add set new lattice vectors method
VsevolodX Aug 20, 2024
1eb7c6a
update: explain with code
VsevolodX Aug 20, 2024
d2db8f6
update: add types and docstrings
VsevolodX Aug 20, 2024
3173ecd
fix: safe termination selection
VsevolodX Aug 20, 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
13 changes: 13 additions & 0 deletions src/py/mat3ra/made/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,16 @@ def set_coordinates(self, coordinates: List[List[float]]) -> None:
new_basis = self.basis.copy()
new_basis.coordinates.values = coordinates
self.basis = new_basis

def set_new_lattice_vectors(
self, lattice_vector1: List[float], lattice_vector2: List[float], lattice_vector3: List[float]
) -> None:
new_basis = self.basis.copy()
new_basis.to_cartesian()
new_basis.cell.vector1 = lattice_vector1
new_basis.cell.vector2 = lattice_vector2
new_basis.cell.vector3 = lattice_vector3
new_basis.to_crystal()
self.basis = new_basis
lattice = Lattice.from_vectors_array([lattice_vector1, lattice_vector2, lattice_vector3])
self.lattice = lattice
18 changes: 18 additions & 0 deletions src/py/mat3ra/made/tools/build/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
from typing import List, Optional, Any

from mat3ra.code.entity import InMemoryEntity
from pydantic import BaseModel

from ...material import Material


class BaseConfiguration(BaseModel, InMemoryEntity):
"""
Base class for material build configurations.
This class provides an interface for defining the configuration parameters.

The class is designed to be subclassed and the subclass should define the following attributes:
- `_json`: The JSON representation of the configuration.
"""

class Config:
arbitrary_types_allowed = True

@property
def _json(self):
raise NotImplementedError


class BaseBuilder(BaseModel):
"""
Base class for material builders.
Expand Down
8 changes: 6 additions & 2 deletions src/py/mat3ra/made/tools/build/interface/termination_pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def safely_select_termination_pair(
) -> TerminationPair:
"""
Attempt finding provided in generated terminations to find a complete match,
if match isn't found, get terminations with equivalent chemical elements.
if match isn't found, get terminations with equivalent chemical elements,
if that fails, return the first generated termination pair.
"""
provided_film_termination = provided_termination_pair.film_termination
provided_substrate_termination = provided_termination_pair.substrate_termination
Expand All @@ -55,5 +56,8 @@ def safely_select_termination_pair(
== provided_substrate_termination.chemical_elements
):
hotfix_termination_pair = termination_pair
print("Interface will be built with terminations: ", hotfix_termination_pair)
else:
hotfix_termination_pair = generated_termination_pairs[0]
print("Interface will be built with terminations: ", hotfix_termination_pair)

return hotfix_termination_pair
9 changes: 9 additions & 0 deletions src/py/mat3ra/made/tools/build/nanoribbon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from mat3ra.made.material import Material

from .builders import NanoribbonBuilder
from .configuration import NanoribbonConfiguration


def create_nanoribbon(configuration: NanoribbonConfiguration) -> Material:
builder = NanoribbonBuilder()
return builder.get_material(configuration)
149 changes: 149 additions & 0 deletions src/py/mat3ra/made/tools/build/nanoribbon/builders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from typing import List, Optional, Any, Tuple

import numpy as np

from mat3ra.made.material import Material
from mat3ra.made.tools.build import BaseBuilder
from mat3ra.made.tools.build.supercell import create_supercell
from mat3ra.made.tools.modify import filter_by_rectangle_projection, wrap_to_unit_cell

from ...modify import translate_to_center
from .configuration import NanoribbonConfiguration
from .enums import EdgeTypes


class NanoribbonBuilder(BaseBuilder):
"""
Builder class for creating a nanoribbon from a material.

The process creates a supercell with large enough dimensions to contain the nanoribbon and then
filters the supercell to only include the nanoribbon. The supercell is then centered and returned as the nanoribbon.
The nanoribbon can have either Armchair or Zigzag edge. The edge is defined along the vector 1 of the material cell,
which corresponds to [1,0,0] direction.
"""

_ConfigurationType: type(NanoribbonConfiguration) = NanoribbonConfiguration # type: ignore
_GeneratedItemType: Material = Material
_PostProcessParametersType: Any = None

def create_nanoribbon(self, config: NanoribbonConfiguration) -> Material:
material = config.material
(
length_cartesian,
width_cartesian,
height_cartesian,
vacuum_length_cartesian,
vacuum_width_cartesian,
) = self._calculate_cartesian_dimensions(config, material)
length_lattice_vector, width_lattice_vector, height_lattice_vector = self._get_new_lattice_vectors(
length_cartesian,
width_cartesian,
height_cartesian,
vacuum_length_cartesian,
vacuum_width_cartesian,
config.edge_type,
)
n = max(config.length, config.width)
large_supercell_to_cut = create_supercell(material, np.diag([2 * n, 2 * n, 1]))

min_coordinate, max_coordinate = self._calculate_coordinates_of_cut(
length_cartesian, width_cartesian, height_cartesian, config.edge_type
)
nanoribbon = filter_by_rectangle_projection(
large_supercell_to_cut,
min_coordinate=min_coordinate,
max_coordinate=max_coordinate,
use_cartesian_coordinates=True,
)
nanoribbon.set_new_lattice_vectors(length_lattice_vector, width_lattice_vector, height_lattice_vector)
return translate_to_center(nanoribbon)

@staticmethod
def _calculate_cartesian_dimensions(config: NanoribbonConfiguration, material: Material):
"""
Calculate the dimensions of the nanoribbon in the cartesian coordinate system.
"""
nanoribbon_width = config.width
nanoribbon_length = config.length
vacuum_width = config.vacuum_width
vacuum_length = config.vacuum_length
edge_type = config.edge_type

if edge_type == EdgeTypes.armchair:
nanoribbon_length, nanoribbon_width = nanoribbon_width, nanoribbon_length
vacuum_width, vacuum_length = vacuum_length, vacuum_width

length_cartesian = nanoribbon_length * np.dot(np.array(material.basis.cell.vector1), np.array([1, 0, 0]))
width_cartesian = nanoribbon_width * np.dot(np.array(material.basis.cell.vector2), np.array([0, 1, 0]))
height_cartesian = np.dot(np.array(material.basis.cell.vector3), np.array([0, 0, 1]))
vacuum_length_cartesian = vacuum_length * np.dot(np.array(material.basis.cell.vector1), np.array([1, 0, 0]))
vacuum_width_cartesian = vacuum_width * np.dot(np.array(material.basis.cell.vector2), np.array([0, 1, 0]))

return length_cartesian, width_cartesian, height_cartesian, vacuum_length_cartesian, vacuum_width_cartesian

@staticmethod
def _get_new_lattice_vectors(
length: float, width: float, height: float, vacuum_length: float, vacuum_width: float, edge_type: EdgeTypes
) -> Tuple[List[float], List[float], List[float]]:
"""
Calculate the new lattice vectors for the nanoribbon.

Args:
length: Length of the nanoribbon.
width: Width of the nanoribbon.
height: Height of the nanoribbon.
vacuum_length: Length of the vacuum region.
vacuum_width: Width of the vacuum region.
edge_type: Type of the edge of the nanoribbon.

Returns:
Tuple of the new lattice vectors.
"""
length_lattice_vector = [length + vacuum_length, 0, 0]
width_lattice_vector = [0, width + vacuum_width, 0]
height_lattice_vector = [0, 0, height]

if edge_type == EdgeTypes.armchair:
length_lattice_vector, width_lattice_vector = width_lattice_vector, length_lattice_vector

return length_lattice_vector, width_lattice_vector, height_lattice_vector

@staticmethod
def _calculate_coordinates_of_cut(
length: float, width: float, height: float, edge_type: EdgeTypes
) -> Tuple[List[float], List[float]]:
"""
Calculate the coordinates of the rectangular nanoribbon cut from the supercell.

Args:
length: Length of the nanoribbon.
width: Width of the nanoribbon.
height: Height of the nanoribbon.
edge_type: Type of the edge of the nanoribbon.

Returns:
Tuple of the minimum and maximum coordinates of the cut.
"""
edge_nudge_value = 0.01
conditional_nudge_value = edge_nudge_value * (
-1 * (edge_type == EdgeTypes.armchair) + 1 * (edge_type == EdgeTypes.zigzag)
)
min_coordinate = [-edge_nudge_value, conditional_nudge_value, 0]
max_coordinate = [length - edge_nudge_value, width + conditional_nudge_value, height]
return min_coordinate, max_coordinate

def _generate(self, configuration: NanoribbonConfiguration) -> List[_GeneratedItemType]:
nanoribbon = self.create_nanoribbon(configuration)
return [nanoribbon]

def _post_process(
self,
items: List[_GeneratedItemType],
post_process_parameters: Optional[_PostProcessParametersType],
) -> List[Material]:
return [wrap_to_unit_cell(item) for item in items]

def _update_material_name(self, material: Material, configuration: NanoribbonConfiguration) -> Material:
edge_type = configuration.edge_type.capitalize()
material.name = f"{material.name} ({edge_type} nanoribbon)"
return material
37 changes: 37 additions & 0 deletions src/py/mat3ra/made/tools/build/nanoribbon/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from mat3ra.made.material import Material
from mat3ra.made.tools.build.nanoribbon.enums import EdgeTypes

from ...build import BaseConfiguration


class NanoribbonConfiguration(BaseConfiguration):
"""
Configuration for building a nanoribbon from a material.


Attributes:
material (Material): The material to build the nanoribbon from.
width (int): The width of the nanoribbon in number of unit cells.
length (int): The length of the nanoribbon in number of unit cells.
vacuum_width (int): The width of the vacuum region in number of unit cells.
vacuum_length (int): The length of the vacuum region in number of unit cells.
edge_type (EdgeTypes): The type of edge to use for the nanoribbon, either zigzag or armchair.
"""

material: Material
width: int # in number of unit cells
length: int # in number of unit cells
vacuum_width: int = 3 # in number of unit cells
vacuum_length: int = 0 # in number of unit cells
edge_type: EdgeTypes = EdgeTypes.zigzag

@property
def _json(self):
return {
"material": self.material.to_json(),
"width": self.width,
"length": self.length,
"vacuum_width": self.vacuum_width,
"vacuum_length": self.vacuum_length,
"edge_type": self.edge_type,
}
10 changes: 10 additions & 0 deletions src/py/mat3ra/made/tools/build/nanoribbon/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from enum import Enum


class EdgeTypes(str, Enum):
"""
Enum for nanoribbon edge types.
"""

zigzag = "zigzag"
armchair = "armchair"
25 changes: 25 additions & 0 deletions src/py/mat3ra/made/tools/modify.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,31 @@ def translate_by_vector(
return Material(from_ase(atoms))


def translate_to_center(material: Material, axes: Optional[List[str]] = None) -> Material:
"""
Center the material in the unit cell.

Args:
material (Material): The material object to center.
axes (List[str]): The axes to center the material along.
Returns:
Material: The centered material object.
"""
new_material = material.clone()
new_material.to_crystal()
if axes is None:
axes = ["x", "y", "z"]
min_x = get_atomic_coordinates_extremum(material, axis="x", extremum="min") if "x" in axes else 0
max_x = get_atomic_coordinates_extremum(material, axis="x", extremum="max") if "x" in axes else 1
min_y = get_atomic_coordinates_extremum(material, axis="y", extremum="min") if "y" in axes else 0
max_y = get_atomic_coordinates_extremum(material, axis="y", extremum="max") if "y" in axes else 1
if "z" in axes:
material = translate_to_z_level(material, z_level="center")

material = translate_by_vector(material, vector=[(1 - min_x - max_x) / 2, (1 - min_y - max_y) / 2, 0])
return material


def wrap_to_unit_cell(material: Material) -> Material:
"""
Wrap the material to the unit cell.
Expand Down
Loading
Loading