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 7397 #151

Merged
merged 59 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4f049c6
feat: add first implementations of perturbation
VsevolodX Jul 25, 2024
af0f656
update: rename to deformation
VsevolodX Jul 26, 2024
15a99d0
update: rename to deformation
VsevolodX Jul 26, 2024
20b79c3
feat: add deformation function builder
VsevolodX Jul 26, 2024
7279daa
update: prepare for continuos deformation
VsevolodX Jul 26, 2024
6939b39
feat: working implementation
VsevolodX Jul 26, 2024
95337d6
update: add sine and radial sine
VsevolodX Jul 27, 2024
b53ce87
update: add sine helpers
VsevolodX Jul 27, 2024
50658ad
update: use sine helpers
VsevolodX Jul 27, 2024
b90b7ab
update: helps with second time interface creation
VsevolodX Jul 27, 2024
71987fb
Merge branch 'feature/SOF-7395' into feature/SOF-7397
VsevolodX Jul 30, 2024
11db833
update: simply use continuous deformations
VsevolodX Jul 31, 2024
b49ca69
update: optimize
VsevolodX Jul 31, 2024
f304dcb
update: types, cleanup
VsevolodX Jul 31, 2024
a97b7c3
Merge branch 'main' into feature/SOF-7397
VsevolodX Jul 31, 2024
9e98b26
update: fix and wrap
VsevolodX Aug 1, 2024
425892d
update: move functions to holder
VsevolodX Aug 1, 2024
c7f8d53
update: OOP inheritance
VsevolodX Aug 1, 2024
3861d28
update: rename slab -> material
VsevolodX Aug 1, 2024
83325e3
update: rename deformation -> perturbation
VsevolodX Aug 1, 2024
e512d29
update: make work with distinct builders
VsevolodX Aug 1, 2024
678eda1
update: use OOP and cleanup
VsevolodX Aug 1, 2024
a879f52
update: small adjustments
VsevolodX Aug 1, 2024
7cb6053
chore: add docstring
VsevolodX Aug 1, 2024
07cf54c
update: even more optimization
VsevolodX Aug 1, 2024
2b0805a
update: fix a mistake
VsevolodX Aug 2, 2024
7d410a7
feat: add cell matching dpspb
VsevolodX Aug 2, 2024
2ecd3cf
feat: add method to create lattice
VsevolodX Aug 2, 2024
34eec9e
update: adjustments:
VsevolodX Aug 2, 2024
e8f403a
update: add a test for perturbations
VsevolodX Aug 2, 2024
4d44046
update: small but nessessary change
VsevolodX Aug 2, 2024
830140a
update: noticed and issue with name and metadata
VsevolodX Aug 2, 2024
9e3642c
chore: fix lint
VsevolodX Aug 2, 2024
49e148b
chore: types fixes
VsevolodX Aug 2, 2024
aa757fc
update: add flag for ease of use
VsevolodX Aug 3, 2024
460a321
update: add a constructor from arrays
VsevolodX Aug 5, 2024
1eea222
update: move coordinate related utils to a folder
VsevolodX Aug 5, 2024
ee92a58
update: rename nested_array -> vectors_array
VsevolodX Aug 5, 2024
9d4c882
update: move defs from defs
VsevolodX Aug 6, 2024
427994e
update: minor fix
VsevolodX Aug 6, 2024
bf561bb
chore: run lint fix
VsevolodX Aug 6, 2024
bccdeb1
update: move sine realated and add factory
VsevolodX Aug 6, 2024
88d95cd
update: cleanup
VsevolodX Aug 6, 2024
b34ae4c
update: use factory for pertrubation
VsevolodX Aug 6, 2024
37db051
chore: run lint fix
VsevolodX Aug 6, 2024
3806063
update: add test for distance preserving perturbation
VsevolodX Aug 6, 2024
ccf9702
update: add json as method:
VsevolodX Aug 7, 2024
ae3cdcb
chore: add types
VsevolodX Aug 7, 2024
5fca5dc
chore: remove duplicated method
VsevolodX Aug 7, 2024
e8a22a1
update: change folders and import
VsevolodX Aug 7, 2024
98a4358
update: renames
VsevolodX Aug 7, 2024
3697be4
chore: run lint fix
VsevolodX Aug 7, 2024
a42ebbd
update: optimize ann OOP
VsevolodX Aug 7, 2024
e8f4947
chore: add a todo memo
VsevolodX Aug 7, 2024
f84e873
update: optimize with OOP
VsevolodX Aug 7, 2024
963822a
update: add set coordintes as a method
VsevolodX Aug 7, 2024
5f2dcf4
update: rearrange
VsevolodX Aug 11, 2024
8ce0856
update: remove tuples
VsevolodX Aug 12, 2024
4c6a391
update: rename
VsevolodX Aug 12, 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
Empty file.
63 changes: 63 additions & 0 deletions src/py/mat3ra/made/tools/build/deformation/builders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import List
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use perturbation


from mat3ra.made.material import Material
from mat3ra.made.tools.build import BaseBuilder

from .configuration import DeformationConfiguration


class DeformationBuilder(BaseBuilder):
_ConfigurationType: type(DeformationConfiguration) = DeformationConfiguration # type: ignore
_GeneratedItemType: Material = Material

@staticmethod
def deform_slab(configuration):
new_material = configuration.slab.clone()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic is ambiguous - why is there slab in configuration for deformation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to SlabDeformationBuilder

new_material.to_cartesian()
new_coordinates = []
for coord in new_material.basis.coordinates.values:
perturbed_coord = configuration.deformation_function[0](coord)
new_coordinates.append(perturbed_coord)
new_basis = new_material.basis.copy()
new_basis.coordinates.values = new_coordinates
new_basis.to_crystal()
new_material.basis = new_basis
return new_material

def _generate(self, configuration: _ConfigurationType) -> List[_GeneratedItemType]:
"""Generate materials with applied deformation based on the given configuration."""
new_material = self.deform_slab(configuration)
return [new_material]

def _update_material_name(self, material: Material, configuration: _ConfigurationType) -> Material:
deformation_details = f"Deformation: {configuration.deformation_function[0].__name__}"
material.name = f"{material.name} ({deformation_details})"
return material


class ContinuousDeformationBuilder(DeformationBuilder):
def deform_slab_isometrically(self, configuration):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

new_material = configuration.slab.clone()
new_material.to_cartesian()
new_coordinates = []

deformation_function, deformation_json = configuration.deformation_function
for coord in new_material.basis.coordinates.values:
perturbed_coord = deformation_function(coord)
new_coordinates.append(perturbed_coord)

new_basis = new_material.basis.copy()
new_basis.coordinates.values = new_coordinates
new_basis.to_crystal()
new_material.basis = new_basis

# TODO: adjust the lattice parameters with the same coordinates transformation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove or implement


return new_material

def _generate(
self, configuration: DeformationBuilder._ConfigurationType
) -> List[DeformationBuilder._GeneratedItemType]:
"""Generate materials with applied continuous deformation based on the given configuration."""
new_material = self.deform_slab_isometrically(configuration)
return [new_material]
24 changes: 24 additions & 0 deletions src/py/mat3ra/made/tools/build/deformation/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import Callable, List, Dict, Tuple

from mat3ra.code.entity import InMemoryEntity
from mat3ra.made.material import Material
from pydantic import BaseModel

from ...utils import DeformationFunctionBuilder


class DeformationConfiguration(BaseModel, InMemoryEntity):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeformationConfiguration with deformation_function

SlabDeformationConfiguration inheriting from it and adding slab

Same for builders

slab: Material
deformation_function: Tuple[Callable[[List[float]], float], Dict] = DeformationFunctionBuilder().sine_wave()

class Config:
arbitrary_types_allowed = True

@property
def _json(self):
deformation_function_json = self.deformation_function[1]
return {
"type": self.get_cls_name(),
"slab": self.slab.to_json(),
"deformation_function": deformation_function_json,
}
7 changes: 5 additions & 2 deletions src/py/mat3ra/made/tools/build/interface/builders.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Any, List, Optional

import numpy as np
from mat3ra.made.tools.modify import translate_to_z_level
from pydantic import BaseModel
from ase.build.tools import niggli_reduce
from pymatgen.analysis.interfaces.coherent_interfaces import (
Expand Down Expand Up @@ -144,9 +145,11 @@ class ZSLStrainMatchingInterfaceBuilder(ConvertGeneratedItemsPymatgenStructureMi

def _generate(self, configuration: InterfaceConfiguration) -> List[PymatgenInterface]:
generator = ZSLGenerator(**self.build_parameters.strain_matching_parameters.dict())
substrate_down = translate_to_z_level(configuration.substrate_configuration.bulk, "bottom")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

substrate_with_atoms_translated_to_bottom
film_with_atoms_translated_to_bottom

film_down = translate_to_z_level(configuration.film_configuration.bulk, "bottom")
builder = CoherentInterfaceBuilder(
substrate_structure=to_pymatgen(configuration.substrate_configuration.bulk),
film_structure=to_pymatgen(configuration.film_configuration.bulk),
substrate_structure=to_pymatgen(substrate_down),
film_structure=to_pymatgen(film_down),
substrate_miller=configuration.substrate_configuration.miller_indices,
film_miller=configuration.film_configuration.miller_indices,
zslgen=generator,
Expand Down
140 changes: 139 additions & 1 deletion src/py/mat3ra/made/tools/utils.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
from functools import wraps
from typing import Callable, Dict, List, Optional, Tuple
from typing import Callable, Dict, List, Literal, Optional, Tuple

import numpy as np
from mat3ra.utils.matrix import convert_2x2_to_3x3
from scipy.integrate import quad
from scipy.optimize import root_scalar

from .third_party import PymatgenStructure

DEFAULT_SCALING_FACTOR = np.array([3, 3, 3])
DEFAULT_TRANSLATION_VECTOR = 1 / DEFAULT_SCALING_FACTOR
AXIS_TO_INDEX_MAP = {"x": 0, "y": 1, "z": 2}
EQUATION_RANGE_COEFFICIENT = 10


# TODO: convert to accept ASE Atoms object
Expand Down Expand Up @@ -322,3 +326,137 @@ def plane(plane_normal: List[float], plane_point_coordinate: List[float]):
plane_normal=plane_normal,
plane_point_coordinate=plane_point_coordinate,
)


def sine_wave(
coordinate: List[float],
amplitude: float = 0.1,
wavelength: float = 1,
phase: float = 0,
axis: Literal["x", "y"] = "x",
) -> List[float]:
"""
Deform a coordinate using a sine wave.
Args:
coordinate (List[float]): The coordinate to deform.
amplitude (float): The amplitude of the sine wave in cartesian coordinates.
wavelength (float): The wavelength of the sine wave in cartesian coordinates.
phase (float): The phase of the sine wave in cartesian coordinates.
axis (str): The axis of the direction of the sine wave.

Returns:
List[float]: The deformed coordinate.
"""
if axis in AXIS_TO_INDEX_MAP:
index = AXIS_TO_INDEX_MAP[axis]
return [
coordinate[0],
coordinate[1],
coordinate[2] + amplitude * np.sin(2 * np.pi * coordinate[index] / wavelength + phase),
]
else:
return coordinate


def solve_sine_wave_coordinate_prime(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docstring

coordinate: List[float], amplitude: float, wavelength: float, phase: float, axis="x"
) -> List[float]:
def sine_wave_diff(w: float, amplitude: float, wavelength: float, phase: float) -> float:
return amplitude * 2 * np.pi / wavelength * np.cos(2 * np.pi * w / wavelength + phase)

def sine_wave_length_integral_equation(w_prime: float, w: float, amplitude: float, wavelength: float, phase: float):
arc_length = quad(
lambda t: np.sqrt(1 + (sine_wave_diff(t, amplitude, wavelength, phase)) ** 2), a=0, b=w_prime
)[0]
return arc_length - w

index = AXIS_TO_INDEX_MAP.get(axis, 0)
w = coordinate[index]
# Find x' such that the integral from 0 to x' equals x
result = root_scalar(
sine_wave_length_integral_equation,
args=(w, amplitude, wavelength, phase),
bracket=[0, EQUATION_RANGE_COEFFICIENT * w],
method="brentq",
)
new_coordinate = coordinate[:]
new_coordinate[index] = result.root
return new_coordinate


def sine_wave_isometric(
coordinate: List[float],
amplitude: float = 0.1,
wavelength: float = 1,
phase: float = 0,
axis: Literal["x", "y"] = "x",
) -> List[float]:
coordinate_prime = solve_sine_wave_coordinate_prime(coordinate, amplitude, wavelength, phase, axis)
return sine_wave(coordinate_prime, amplitude, wavelength, phase, axis="x")


def sine_wave_radial(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coordinate functions need to be better organized

coordinate: List[float], amplitude: float = 0.1, wavelength: float = 1, phase: float = 0, center_position=None
) -> List[float]:
"""
Deform a coordinate using a radial sine wave.
Args:
coordinate (List[float]): The coordinate to deform.
amplitude (float): The amplitude of the sine wave in cartesian coordinates.
wavelength (float): The wavelength of the sine wave in cartesian coordinates.
phase (float): The phase of the sine wave in cartesian coordinates.
center_position (List[float]): The center position of the sine wave on the plane.

Returns:
List[float]: The deformed coordinate.
"""
if center_position is None:
center_position = [0.5, 0.5]
np_position = np.array(coordinate[:2])
np_center_position = np.array(center_position)
distance = np.linalg.norm(np_position - np_center_position)
return [
coordinate[0],
coordinate[1],
coordinate[2] + amplitude * np.sin(2 * np.pi * distance / wavelength + phase),
]


class DeformationFunctionBuilder:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder -> Holder for this and above class

def create_deformation_function(
self, deformation_type: str, evaluation_func: Callable, **kwargs
) -> Tuple[Callable, Dict]:
deformation_json = {"type": deformation_type, **kwargs}
return lambda coordinate: evaluation_func(coordinate, **kwargs), deformation_json

def sine_wave(self, amplitude: float = 0.1, wavelength: float = 1, phase: float = 0, axis: Literal["x", "y"] = "x"):
return self.create_deformation_function(
deformation_type=sine_wave.__name__,
evaluation_func=sine_wave,
amplitude=amplitude,
wavelength=wavelength,
phase=phase,
axis=axis,
)

def sine_wave_isometric(
self, amplitude: float = 0.1, wavelength: float = 1, phase: float = 0, axis: Literal["x", "y"] = "x"
):
return self.create_deformation_function(
deformation_type=sine_wave_isometric.__name__,
evaluation_func=sine_wave_isometric,
amplitude=amplitude,
wavelength=wavelength,
phase=phase,
axis=axis,
)

def sine_wave_radial(self, amplitude: float = 0.1, wavelength: float = 1, phase: float = 0, center_position=None):
return self.create_deformation_function(
deformation_type=sine_wave_radial.__name__,
evaluation_func=sine_wave_radial,
amplitude=amplitude,
wavelength=wavelength,
phase=phase,
center_position=center_position,
)
Loading