-
Notifications
You must be signed in to change notification settings - Fork 3
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
feature/SOF 7397 #151
Changes from 14 commits
4f049c6
af0f656
15a99d0
20b79c3
7279daa
6939b39
95337d6
b53ce87
50658ad
b90b7ab
71987fb
11db833
b49ca69
f304dcb
a97b7c3
9e98b26
425892d
c7f8d53
3861d28
83325e3
e512d29
678eda1
a879f52
7cb6053
07cf54c
2b0805a
7d410a7
2ecd3cf
34eec9e
e8f403a
4d44046
830140a
9e3642c
49e148b
aa757fc
460a321
1eea222
ee92a58
9d4c882
427994e
bf561bb
bccdeb1
88d95cd
b34ae4c
37db051
3806063
ccf9702
ae3cdcb
5fca5dc
e8a22a1
98a4358
3697be4
a42ebbd
e8f4947
f84e873
963822a
5f2dcf4
8ce0856
4c6a391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
from typing import List | ||
|
||
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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic is ambiguous - why is there slab in configuration for deformation? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] |
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DeformationConfiguration with deformation_function SlabDeformationConfiguration inheriting from it and adding 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, | ||
} |
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 ( | ||
|
@@ -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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. substrate_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, | ||
|
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 | ||
|
@@ -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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use perturbation