From 76aadfd79dc5997d6bb827ea3aec8ffd9b166077 Mon Sep 17 00:00:00 2001 From: MartinPdeS Date: Tue, 28 Nov 2023 20:52:57 -0500 Subject: [PATCH] api changes, version 1.0.0 rebased [publish] --- SuPyMode/cpp/model_parameters_interface.cpp | 42 +++ SuPyMode/representation/__init__.py | 15 ++ SuPyMode/representation/adiabatic.py | 27 ++ SuPyMode/representation/base.py | 142 +++++++++++ SuPyMode/representation/beating_length.py | 22 ++ SuPyMode/representation/beta.py | 19 ++ SuPyMode/representation/eigen_value.py | 18 ++ SuPyMode/representation/field.py | 239 ++++++++++++++++++ SuPyMode/representation/index.py | 20 ++ .../representation/normalized_coupling.py | 27 ++ SuPyMode/representation/overlap.py | 21 ++ 11 files changed, 592 insertions(+) create mode 100644 SuPyMode/cpp/model_parameters_interface.cpp create mode 100644 SuPyMode/representation/__init__.py create mode 100644 SuPyMode/representation/adiabatic.py create mode 100644 SuPyMode/representation/base.py create mode 100644 SuPyMode/representation/beating_length.py create mode 100644 SuPyMode/representation/beta.py create mode 100644 SuPyMode/representation/eigen_value.py create mode 100644 SuPyMode/representation/field.py create mode 100644 SuPyMode/representation/index.py create mode 100644 SuPyMode/representation/normalized_coupling.py create mode 100644 SuPyMode/representation/overlap.py diff --git a/SuPyMode/cpp/model_parameters_interface.cpp b/SuPyMode/cpp/model_parameters_interface.cpp new file mode 100644 index 000000000..7ec486ad1 --- /dev/null +++ b/SuPyMode/cpp/model_parameters_interface.cpp @@ -0,0 +1,42 @@ +#include +#include +#include "definitions.cpp" + + +PYBIND11_MODULE(ModelParameters, module) +{ + module.doc() = "A c++ wrapper class for ModelParameters"; + + pybind11::class_(module, "ModelParameters") + + .def_readwrite("nx", &ModelParameters::nx) + .def_readwrite("ny", &ModelParameters::ny) + .def_readwrite("dx", &ModelParameters::dx) + .def_readwrite("dy", &ModelParameters::dy) + .def_readwrite("wavelength", &ModelParameters::wavelength) + .def_readwrite("wavenumber", &ModelParameters::wavenumber) + .def_readwrite("itr_list", &ModelParameters::itr_list_py) + + .def( + pybind11::pickle( + [](ModelParameters& model_parameter) + { + return model_parameter.get_state(); // dump + }, + [](pybind11::tuple t) + { + return ModelParameters{ + t[0].cast(), // wavelength + t[1].cast>(), // mesh_gradient_py, + t[2].cast>(), // itr_list_py, + t[3].cast(), // dx + t[4].cast() // dy + }; // load + } + ) + + + ); + +} + diff --git a/SuPyMode/representation/__init__.py b/SuPyMode/representation/__init__.py new file mode 100644 index 000000000..3f8ef68bb --- /dev/null +++ b/SuPyMode/representation/__init__.py @@ -0,0 +1,15 @@ +from .adiabatic import Adiabatic + +from .index import Index + +from .beating_length import BeatingLength + +from .eigen_value import EigenValue + +from .overlap import Overlap + +from .field import Field + +from .beta import Beta + +from .normalized_coupling import NormalizedCoupling \ No newline at end of file diff --git a/SuPyMode/representation/adiabatic.py b/SuPyMode/representation/adiabatic.py new file mode 100644 index 000000000..308da55a7 --- /dev/null +++ b/SuPyMode/representation/adiabatic.py @@ -0,0 +1,27 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseMultiModePlot + + +class Adiabatic(InheritFromSuperMode, BaseMultiModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self.plot_style = plot_style.adiabatic + + def get_values(self, other_supermode) -> numpy.ndarray: + """ + Return the array of the modal coupling for the mode + """ + output = self.parent_supermode.binded_supermode.get_adiabatic_with_mode(other_supermode.binded_supermode) + + if not self.parent_supermode.is_computation_compatible(other_supermode): + output *= numpy.inf + + return output + + +# - diff --git a/SuPyMode/representation/base.py b/SuPyMode/representation/base.py new file mode 100644 index 000000000..8dd4f56ac --- /dev/null +++ b/SuPyMode/representation/base.py @@ -0,0 +1,142 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy +from MPSPlots.render2D import SceneList, Axis + + +class BaseMultiModePlot(): + def _render_on_ax_(self, ax: Axis, other_supermode: 'SuperMode' = None): + if other_supermode is None: + other_supermode = self.parent_supermode.parent_set.supermodes + else: + other_supermode = numpy.atleast_1d(other_supermode) + + for mode in other_supermode: + if mode.ID == self.ID or mode.solver_number != self.solver_number: + continue + + ax.add_line( + x=self.itr_list, + y=self.get_values(mode), + label=f'{self.stylized_label} - {mode.stylized_label}' + ) + + ax.set_style(**self.plot_style) + + def plot(self, other_supermode=None, row: int = 0, col: int = 0) -> None: + """ + Plotting method for the index. + + :param slice_list: Value reprenting the slice where the mode field is evaluated. + :type slice_list: list + :param itr_list: Value of itr value to evaluate the mode field. + :type itr_list: list + + :returns: the figure containing all the plots. + :rtype: SceneList + """ + figure = SceneList(unit_size=(10, 4), tight_layout=True) + + ax = figure.append_ax() + + self._render_on_ax_(ax=ax, other_supermode=other_supermode) + + return figure + + +class BaseSingleModePlot(): + def __getitem__(self, idx): + return self._data[idx] + + def _render_on_ax_(self, ax: Axis) -> None: + self._set_axis_(ax) + + ax.set_style(self.plot_style) + + ax.add_line(x=self.itr_list, y=self._data, label=self.stylized_label) + + def plot(self, row: int = 0, col: int = 0) -> None: + """ + Plotting method for the index. + + :param slice_list: Value reprenting the slice where the mode field is evaluated. + :type slice_list: list + :param itr_list: Value of itr value to evaluate the mode field. + :type itr_list: list + + :returns: the figure containing all the plots. + :rtype: SceneList + """ + figure = SceneList(unit_size=(10, 4), tight_layout=True) + + ax = figure.append_ax() + + self._render_on_ax_(ax) + + return figure + + +class InheritFromSuperMode(): + def _set_axis_(self, ax: Axis): + for element, value in self.plot_style.items(): + setattr(ax, element, value) + + def __getitem__(self, idx): + return self._data[idx] + + @property + def mode_number(self) -> int: + return self.parent_supermode.mode_number + + @property + def solver_number(self) -> int: + return self.parent_supermode.solver_number + + @property + def axes(self): + return self.parent_supermode.axes + + @property + def boundaries(self): + return self.parent_supermode.boundaries + + @property + def itr_list(self): + return self.parent_supermode.itr_list + + @property + def ID(self): + return self.parent_supermode.ID + + @property + def label(self): + return self.parent_supermode.label + + @property + def stylized_label(self): + return self.parent_supermode.stylized_label + + def slice_to_itr(self, slice: list = []): + return self.parent_supermode.parent_set.slice_to_itr(slice) + + def itr_to_slice(self, itr: list = []): + return self.parent_supermode.parent_set.itr_to_slice(itr) + + def _interpret_itr_slice_list_(self, *args, **kwargs): + return self.parent_supermode.parent_set._interpret_itr_slice_list_(*args, **kwargs) + + def _get_symmetrize_vector(self, *args, **kwargs): + return self.parent_supermode._get_symmetrize_vector(*args, **kwargs) + + def _get_axis_vector(self, *args, **kwargs): + return self.parent_supermode._get_axis_vector(*args, **kwargs) + + def get_axis(self, *args, **kwargs): + return self.parent_supermode.get_axis(*args, **kwargs) + + +class NameSpace(): + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) \ No newline at end of file diff --git a/SuPyMode/representation/beating_length.py b/SuPyMode/representation/beating_length.py new file mode 100644 index 000000000..b731a7f53 --- /dev/null +++ b/SuPyMode/representation/beating_length.py @@ -0,0 +1,22 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseMultiModePlot + + +class BeatingLength(InheritFromSuperMode, BaseMultiModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self.plot_style = plot_style.beating_length + + def get_values(self, other_supermode) -> numpy.ndarray: + """ + Return the array of the modal coupling for the mode + """ + return self.parent_supermode.binded_supermode.get_beating_length_with_mode(other_supermode.binded_supermode) + + +# - diff --git a/SuPyMode/representation/beta.py b/SuPyMode/representation/beta.py new file mode 100644 index 000000000..1ec9ab90b --- /dev/null +++ b/SuPyMode/representation/beta.py @@ -0,0 +1,19 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseSingleModePlot + + +class Beta(InheritFromSuperMode, BaseSingleModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self._data = self.parent_supermode.binded_supermode.get_betas() + self.plot_style = plot_style.beta + + def get_values(self) -> numpy.ndarray: + return self._data + +# - diff --git a/SuPyMode/representation/eigen_value.py b/SuPyMode/representation/eigen_value.py new file mode 100644 index 000000000..537b003bc --- /dev/null +++ b/SuPyMode/representation/eigen_value.py @@ -0,0 +1,18 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseSingleModePlot + + +class EigenValue(InheritFromSuperMode, BaseSingleModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self._data = self.parent_supermode.binded_supermode.get_eigen_value() + self.plot_style = plot_style.eigen_value + + def get_values(self) -> numpy.ndarray: + return self._data +# - diff --git a/SuPyMode/representation/field.py b/SuPyMode/representation/field.py new file mode 100644 index 000000000..a08d42cd8 --- /dev/null +++ b/SuPyMode/representation/field.py @@ -0,0 +1,239 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy +from MPSPlots.render2D import Axis +from MPSPlots import colormaps +from MPSPlots.render2D import SceneMatrix + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode + + +class Field(InheritFromSuperMode): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self._data = self.parent_supermode.binded_supermode.get_fields() + + def get_values(self): + return self._data + + def get_norm(self, slice_number: int) -> float: + return self.parent_supermode.binded_supermode.get_norm(slice_number) + + def get_field(self, slice_number: int = None, itr: float = None, add_symmetries: bool = True, normalization: str = 'L2') -> numpy.ndarray: + """ + Returns the field with the predefined boundary conditions for a certain slice number. + The normalization type must either be square integration (L2), max value set to one (max), center value set to one (center) + or the normalization provided with coupled-mode theory (cmt). + + :param slice_number: The slice number + :type slice_number: int + :param add_symmetries: Add or not the boundary symmetries + :type add_symmetries: bool + :param normalization: The normalization type ['L2', 'max', 'center', 'cmt'] + :type normalization: str + + :returns: The field mesh. + :rtype: numpy.ndarray + """ + assert (slice_number is None) ^ (itr is None), 'Exactly one of the two values [slice_number, itr] has to be defined' + assert normalization.lower() in ['l2', 'max', 'center', 'cmt'], "Normalization type must be in ['l2', 'max', 'center', 'cmt']" + + slice_number, itr = self.parent_supermode.parent_set._interpret_itr_slice_list_( + slice_list=[] if slice_number is None else slice_number, + itr_list=[] if itr is None else itr + ) + + field = self._data[slice_number[0]] + + if add_symmetries: + field = self._get_symmetrized_field(field=field) + + return self.normalize_field(field=field, norm_type=normalization, itr=itr) + + def normalize_field(self, field: numpy.ndarray, itr: float, norm_type: str = 'L2') -> numpy.ndarray: + match norm_type.lower(): + case 'max': + norm = abs(field).max() + case 'center': + idx_x_center = numpy.argmin(abs(self.parent_supermode.parent_set.coordinate_system.x_vector)) + idx_y_center = numpy.argmin(abs(self.parent_supermode.parent_set.coordinate_system.y_vector)) + center_value = field[idx_x_center, idx_y_center] + norm = center_value + case 'l2': + dx_scaled = self.parent_supermode.parent_set.coordinate_system.dx * itr + dy_scaled = self.parent_supermode.parent_set.coordinate_system.dy * itr + norm = numpy.sqrt(numpy.trapz(numpy.trapz(numpy.square(field), dx=dy_scaled, axis=0), dx=dx_scaled, axis=0)) + case 'cmt': + dx_scaled = self.parent_supermode.parent_set.coordinate_system.dx * itr + dy_scaled = self.parent_supermode.parent_set.coordinate_system.dy * itr + norm = numpy.sqrt(numpy.trapz(numpy.trapz(numpy.square(field), dx=dx_scaled, axis=0), dx=dy_scaled, axis=0)) + + return field / norm + + def _get_symmetrized_field_and_axis(self, field: numpy.ndarray) -> tuple: + x_axis, y_axis = self._get_axis_vector(add_symmetries=True) + + field = self._get_symmetrized_field(field=field) + + return x_axis, y_axis, field + + def _get_symmetrized_field(self, field: numpy.ndarray) -> numpy.ndarray: + """ + Take as input a mesh and return a symmetric version of that mesh. + The symmetry can be setted mirror the top or bottom, left or right. + + :param vector: The 2-d mesh + :type vector: numpy.ndarray + + :returns: The symmetrized mesh + :rtype: numpy.ndarray + + :raises AssertionError: Verify that input vector is 2-dimensionnal. + """ + assert field.ndim == 2, f'Mesh should be 2d, instead {field.ndim} dimensional is provided.' + + symmetric_field = field[:, -2::-1] + match self.boundaries.left.lower(): + case 'symmetric': + field = numpy.c_[symmetric_field, field] + + case 'anti-symmetric': + field = numpy.c_[-symmetric_field, field] + + match self.boundaries.right.lower(): + case 'symmetric': + field = numpy.c_[field, symmetric_field] + + case 'anti-symmetric': + field = numpy.c_[field, -symmetric_field] + + symmetric_field = field[-2::-1, :] + match self.boundaries.top.lower(): + case 'symmetric': + field = numpy.r_[field, symmetric_field] + + case 'anti-symmetric': + field = numpy.r_[field, -symmetric_field] + + match self.boundaries.bottom.lower(): + case 'symmetric': + field = numpy.r_[symmetric_field, field] + + case 'anti-symmetric': + field = numpy.r_[-symmetric_field, field] + + return field + + def _render_on_ax_(self, ax: Axis, slice: int) -> None: + x, y, field = self._get_symmetrized_field_and_axis(field=self._data[slice]) + + artist = ax.add_mesh( + x=x, + y=y, + scalar=field, + ) + + ax.add_colorbar( + artist=artist, + colormap=colormaps.blue_black_red, + symmetric=True, + position='right' + ) + + ax.set_style(**plot_style.field) + + def plot_field(self, + mode_of_interest: list = 'all', + itr_list: list[float] = [], + slice_list: list[int] = [], + show_mode_label: bool = True, + show_itr: bool = True, + show_slice: bool = True) -> SceneMatrix: + """ + Plot each of the mode field for different itr value or slice number. + + :param itr_list: List of itr value to evaluate the mode field + :type itr_list: list + :param slice_list: List of integer reprenting the slice where the mode field is evaluated + :type slice_list: list + + :returns: The figure + :rtype: SceneMatrix + """ + figure = SceneMatrix(unit_size=(3, 3)) + + slice_list, itr_list = self._interpret_itr_slice_list_(slice_list=slice_list, itr_list=itr_list) + + mode_of_interest = self.interpret_mode_of_interest(mode_of_interest=mode_of_interest) + + for m, mode in enumerate(mode_of_interest): + for n, (itr, slice_number) in enumerate(zip(itr_list, slice_list)): + title = self.get_plot_mode_field_title( + supermode=mode, + itr=itr, + slice_number=slice_number, + show_mode_label=show_mode_label, + show_itr=show_itr, + show_slice=show_slice + ) + + ax = figure.append_ax( + row=n, + column=m, + title=title + ) + + field = mode.field.get_field(slice_number=slice_number, add_symmetries=True) + + x, y = mode.field.get_axis(slice_number=slice_number) + + artist = ax.add_mesh( + x=x, + y=y, + scalar=field, + ) + + ax.add_colorbar( + artist=artist, + colormap=colormaps.blue_black_red, + symmetric=True + ) + + ax.set_style(**plot_style.field) + + return figure + + # def plot(self, slice_list: list = [], itr_list: list = []) -> SceneList: + # """ + # Plotting method for the fields. + + # :param slice_list: Value reprenting the slice where the mode field is evaluated. + # :type slice_list: list + # :param itr_list: Value of itr value to evaluate the mode field. + # :type itr_list: list + + # :returns: the figure containing all the plots. + # :rtype: SceneList + # """ + # figure = SceneList(unit_size=(3, 3), tight_layout=True, ax_orientation='horizontal') + + # slice_list, itr_list = self._interpret_itr_slice_list_( + # slice_list=slice_list, + # itr_list=itr_list + # ) + + # slice_list = numpy.atleast_1d(slice_list) + # itr_list = numpy.atleast_1d(itr_list) + + # for n, (slice, itr) in enumerate(zip(slice_list, itr_list)): + # ax = figure.append_ax( + # title=f'{self.parent_supermode.stylized_label}\n[slice: {slice} ITR: {itr:.4f}]' + # ) + + # self._render_on_ax_(ax=ax, slice=slice) + + # return figure + +# - diff --git a/SuPyMode/representation/index.py b/SuPyMode/representation/index.py new file mode 100644 index 000000000..0bc59be3b --- /dev/null +++ b/SuPyMode/representation/index.py @@ -0,0 +1,20 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseSingleModePlot + + +class Index(InheritFromSuperMode, BaseSingleModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self._data = self.parent_supermode.binded_supermode.get_index() + self.plot_style = plot_style.index + + def get_values(self) -> numpy.ndarray: + return self._data + + +# - diff --git a/SuPyMode/representation/normalized_coupling.py b/SuPyMode/representation/normalized_coupling.py new file mode 100644 index 000000000..6cf847679 --- /dev/null +++ b/SuPyMode/representation/normalized_coupling.py @@ -0,0 +1,27 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseMultiModePlot + + +class NormalizedCoupling(InheritFromSuperMode, BaseMultiModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self.plot_style = plot_style.normalized_coupling + + def get_values(self, other_supermode) -> numpy.ndarray: + """ + Return the array of the modal coupling for the mode + """ + + output = self.parent_supermode.binded_supermode.get_normalized_coupling_with_mode(other_supermode.binded_supermode) + + if not self.parent_supermode.is_computation_compatible(other_supermode): + output *= 0 + + return output + +# - diff --git a/SuPyMode/representation/overlap.py b/SuPyMode/representation/overlap.py new file mode 100644 index 000000000..ea51da7b0 --- /dev/null +++ b/SuPyMode/representation/overlap.py @@ -0,0 +1,21 @@ +# #!/usr/bin/env python +# # -*- coding: utf-8 -*- + +import numpy + +from SuPyMode.tools import plot_style +from SuPyMode.representation.base import InheritFromSuperMode, BaseMultiModePlot + + +class Overlap(InheritFromSuperMode, BaseMultiModePlot): + def __init__(self, parent_supermode): + self.parent_supermode = parent_supermode + self.plot_style = plot_style.overlap + + def get_values(self, other_supermode) -> numpy.ndarray: + """ + Return the array of the modal coupling for the mode + """ + return self.parent_supermode.binded_supermode.get_overlap_integrals_with_mode(other_supermode.binded_supermode) + +# -