SuPyMode 2.4.3__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- SuPyMode/__init__.py +9 -0
- SuPyMode/_version.py +24 -0
- SuPyMode/boundaries.cp313-win_amd64.pyd +0 -0
- SuPyMode/directories.py +51 -0
- SuPyMode/eigensolver.cp313-win_amd64.pyd +0 -0
- SuPyMode/mesh.cp313-win_amd64.pyd +0 -0
- SuPyMode/model_parameters.cp313-win_amd64.pyd +0 -0
- SuPyMode/plotter.py +325 -0
- SuPyMode/propagation.py +202 -0
- SuPyMode/representation/__init__.py +23 -0
- SuPyMode/representation/adiabatic.py +111 -0
- SuPyMode/representation/beating_length.py +95 -0
- SuPyMode/representation/beta.py +78 -0
- SuPyMode/representation/eigen_value.py +79 -0
- SuPyMode/representation/field.py +486 -0
- SuPyMode/representation/index.py +80 -0
- SuPyMode/representation/normalized_coupling.py +98 -0
- SuPyMode/solver.py +203 -0
- SuPyMode/supermode.cp313-win_amd64.pyd +0 -0
- SuPyMode/superset.py +701 -0
- SuPyMode/superset_plots.py +497 -0
- SuPyMode/taper.cp313-win_amd64.pyd +0 -0
- SuPyMode/utils.py +196 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP02.csv +98 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP21.csv +88 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP41.csv +86 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP12.csv +82 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP31.csv +95 -0
- SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP51.csv +81 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP02.csv +285 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP21.csv +75 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP41.csv +237 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP12.csv +126 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP31.csv +156 -0
- SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP51.csv +224 -0
- SuPyMode/workflow.py +253 -0
- supymode-2.4.3.dist-info/METADATA +241 -0
- supymode-2.4.3.dist-info/RECORD +40 -0
- supymode-2.4.3.dist-info/WHEEL +5 -0
- supymode-2.4.3.dist-info/licenses/LICENSE +21 -0
SuPyMode/__init__.py
ADDED
SuPyMode/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = "2.4.3"
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 4, 3)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
Binary file
|
SuPyMode/directories.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import SuPyMode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"root_path",
|
|
10
|
+
"project_path",
|
|
11
|
+
"test_path",
|
|
12
|
+
"version_path",
|
|
13
|
+
"validation_data_path",
|
|
14
|
+
"doc_path",
|
|
15
|
+
"static_doc_path",
|
|
16
|
+
"logo_path",
|
|
17
|
+
"doc_css_path",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
root_path = Path(SuPyMode.__path__[0])
|
|
21
|
+
|
|
22
|
+
project_path = root_path.parents[0]
|
|
23
|
+
|
|
24
|
+
test_path = project_path.joinpath("tests")
|
|
25
|
+
|
|
26
|
+
user_data_directory = root_path.joinpath("user_data")
|
|
27
|
+
|
|
28
|
+
reports_path = root_path.joinpath("reports")
|
|
29
|
+
|
|
30
|
+
version_path = root_path.joinpath("VERSION")
|
|
31
|
+
|
|
32
|
+
validation_data_path = root_path.joinpath("validation_data")
|
|
33
|
+
|
|
34
|
+
doc_path = root_path.parents[0].joinpath("docs")
|
|
35
|
+
|
|
36
|
+
static_doc_path = doc_path.joinpath("images")
|
|
37
|
+
|
|
38
|
+
logo_path = static_doc_path.joinpath("logo.png")
|
|
39
|
+
|
|
40
|
+
doc_css_path = doc_path.joinpath("source/_static/default.css")
|
|
41
|
+
|
|
42
|
+
rtd_example = "https://supymode.readthedocs.io/en/latest/examples.html"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
for path_name in __all__:
|
|
47
|
+
path = locals()[path_name]
|
|
48
|
+
print(path)
|
|
49
|
+
assert path.exists(), f"Path {path_name} do not exists"
|
|
50
|
+
|
|
51
|
+
# -
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
SuPyMode/plotter.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional, Sequence, Tuple
|
|
5
|
+
from MPSPlots import colormaps
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
from matplotlib.animation import FuncAnimation, PillowWriter
|
|
8
|
+
|
|
9
|
+
from SuPyMode.taper import AlphaProfile
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_propagation_gif(
|
|
13
|
+
profile: AlphaProfile,
|
|
14
|
+
output_directory: str = "./new_gif.gif",
|
|
15
|
+
dpi: int = 100,
|
|
16
|
+
fps: int = 20,
|
|
17
|
+
number_of_frames: int = 200,
|
|
18
|
+
dark_background: bool = True,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""
|
|
21
|
+
Generates an animated GIF of light propagation in a taper structure.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
output_directory (str): Path where the GIF will be saved.
|
|
25
|
+
dpi (int): Dots per inch for the output GIF.
|
|
26
|
+
fps (int): Frames per second for the animation.
|
|
27
|
+
number_of_frames (int): Total number of frames in the animation.
|
|
28
|
+
dark_background (bool): If True, use a dark background for the GIF.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
None
|
|
32
|
+
"""
|
|
33
|
+
figure, ax = plt.subplots(1, 1, figsize=(12, 6))
|
|
34
|
+
ax.set_xlabel(
|
|
35
|
+
"Propagation axis [mm]", color="white" if dark_background else "black"
|
|
36
|
+
)
|
|
37
|
+
style_context = "dark_background" if dark_background else "default"
|
|
38
|
+
|
|
39
|
+
sub_sampling_factor = int(profile.distance.size / number_of_frames)
|
|
40
|
+
|
|
41
|
+
sub_distance = profile.distance[::sub_sampling_factor] * 1e3
|
|
42
|
+
sub_radius = profile.radius[::sub_sampling_factor]
|
|
43
|
+
sub_itr_list = profile.itr_list[::sub_sampling_factor]
|
|
44
|
+
with plt.style.context(style_context):
|
|
45
|
+
|
|
46
|
+
def init_func() -> tuple:
|
|
47
|
+
line_0 = ax.plot(sub_distance, sub_radius, color="black")
|
|
48
|
+
line_1 = ax.plot(sub_distance, -sub_radius, color="black")
|
|
49
|
+
|
|
50
|
+
line_2 = ax.fill_between(
|
|
51
|
+
sub_distance, +sub_radius, -sub_radius, color="lightblue", alpha=0.8
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return [*line_0, *line_1, line_2]
|
|
55
|
+
|
|
56
|
+
def animate(slice_number: int) -> tuple:
|
|
57
|
+
position = sub_distance[slice_number]
|
|
58
|
+
itr = sub_itr_list[slice_number]
|
|
59
|
+
title = f"[slice: {slice_number} - ITR: {itr:.3f}]"
|
|
60
|
+
|
|
61
|
+
if slice_number > 0:
|
|
62
|
+
ax.lines[-1].remove()
|
|
63
|
+
|
|
64
|
+
line_0 = ax.set_title(title, color="white")
|
|
65
|
+
|
|
66
|
+
line_1 = ax.axvline(position, linestyle="--", linewidth=2, color="red")
|
|
67
|
+
|
|
68
|
+
return line_0, line_1
|
|
69
|
+
|
|
70
|
+
animation = FuncAnimation(
|
|
71
|
+
fig=figure,
|
|
72
|
+
func=animate,
|
|
73
|
+
init_func=init_func,
|
|
74
|
+
blit=True,
|
|
75
|
+
repeat=True,
|
|
76
|
+
frames=sub_itr_list.size,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
animation.save(output_directory, dpi=dpi, writer=PillowWriter(fps=fps))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_3_figures(
|
|
83
|
+
*,
|
|
84
|
+
figure_size: Tuple[float, float] = (8, 6),
|
|
85
|
+
left: float = 0.1,
|
|
86
|
+
right: float = 0.9,
|
|
87
|
+
bottom: float = 0.1,
|
|
88
|
+
top: float = 0.9,
|
|
89
|
+
wspace: float = 1.0,
|
|
90
|
+
hspace: float = 0.15,
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
Create a 3-axes figure layout:
|
|
94
|
+
- top row: one axis spanning both columns
|
|
95
|
+
- bottom row: two square axes (images)
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
fig : matplotlib.figure.Figure
|
|
100
|
+
ax0 : matplotlib.axes.Axes
|
|
101
|
+
Top axis spanning full width.
|
|
102
|
+
ax1 : matplotlib.axes.Axes
|
|
103
|
+
Bottom-left image axis.
|
|
104
|
+
ax2 : matplotlib.axes.Axes
|
|
105
|
+
Bottom-right image axis.
|
|
106
|
+
"""
|
|
107
|
+
fig = plt.figure(figsize=figure_size)
|
|
108
|
+
|
|
109
|
+
gs = fig.add_gridspec(
|
|
110
|
+
2,
|
|
111
|
+
2,
|
|
112
|
+
width_ratios=(1, 1),
|
|
113
|
+
height_ratios=(1, 1),
|
|
114
|
+
left=left,
|
|
115
|
+
right=right,
|
|
116
|
+
bottom=bottom,
|
|
117
|
+
top=top,
|
|
118
|
+
wspace=wspace,
|
|
119
|
+
hspace=hspace,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
ax0 = fig.add_subplot(gs[0, :])
|
|
123
|
+
ax1 = fig.add_subplot(gs[1, 0])
|
|
124
|
+
ax2 = fig.add_subplot(gs[1, 1])
|
|
125
|
+
|
|
126
|
+
for ax in (ax1, ax2):
|
|
127
|
+
ax.set_aspect("equal")
|
|
128
|
+
ax.set_xticks([])
|
|
129
|
+
ax.set_yticks([])
|
|
130
|
+
ax.tick_params(
|
|
131
|
+
axis="both",
|
|
132
|
+
which="both",
|
|
133
|
+
top=False,
|
|
134
|
+
bottom=False,
|
|
135
|
+
right=False,
|
|
136
|
+
left=False,
|
|
137
|
+
labelleft=False,
|
|
138
|
+
labelbottom=False,
|
|
139
|
+
grid_alpha=0,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return fig, ax0, ax1, ax2
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass(frozen=True)
|
|
146
|
+
class _AnimationSettings:
|
|
147
|
+
n_step: int = 20
|
|
148
|
+
dpi: float = 100.0
|
|
149
|
+
fps: int = 50
|
|
150
|
+
interval_ms: int = 40
|
|
151
|
+
outfile: str = "propagation.gif"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ModePropagationGifCreator:
|
|
155
|
+
"""
|
|
156
|
+
Create an animated GIF of mode fields along a taper profile.
|
|
157
|
+
|
|
158
|
+
The API is intentionally close to your original implementation:
|
|
159
|
+
- constructor takes (superset, profile, max_number_of_mode, dark_background)
|
|
160
|
+
- make_animation(n_step, dpi, fps) writes "propagation.gif" by default
|
|
161
|
+
|
|
162
|
+
Improvements
|
|
163
|
+
------------
|
|
164
|
+
- Stable artists: uses persistent QuadMesh objects and updates their data,
|
|
165
|
+
avoiding ax.clear() which is slow and breaks blitting.
|
|
166
|
+
- Robust figure layout: fields are created from a single method and stored.
|
|
167
|
+
- Profile marker: uses one Line2D updated each frame.
|
|
168
|
+
- Optional progress printing without spamming by default.
|
|
169
|
+
|
|
170
|
+
Notes
|
|
171
|
+
-----
|
|
172
|
+
This expects:
|
|
173
|
+
- profile.distance, profile.radius, profile.length
|
|
174
|
+
- profile.master_interpolation_z_to_itr(z) -> float
|
|
175
|
+
- superset.get_slice_structure(itr=..., add_symmetries=True) -> object with .fields
|
|
176
|
+
- superset[mode].stylized_name
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
def __init__(
|
|
180
|
+
self,
|
|
181
|
+
superset,
|
|
182
|
+
profile,
|
|
183
|
+
max_number_of_mode: Optional[int] = None,
|
|
184
|
+
dark_background: bool = True,
|
|
185
|
+
):
|
|
186
|
+
self.superset = superset
|
|
187
|
+
self.profile = profile
|
|
188
|
+
self.dark_background = dark_background
|
|
189
|
+
|
|
190
|
+
self.number_of_mode = (
|
|
191
|
+
len(superset.supermodes)
|
|
192
|
+
if max_number_of_mode is None
|
|
193
|
+
else int(max_number_of_mode)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
self.figure = None
|
|
197
|
+
self.grid_spec = None
|
|
198
|
+
self.ax_profile = None
|
|
199
|
+
self.field_axes: list = []
|
|
200
|
+
self._field_meshes: list = []
|
|
201
|
+
self._profile_line = None
|
|
202
|
+
|
|
203
|
+
self.generate_figure()
|
|
204
|
+
|
|
205
|
+
# -------------------------
|
|
206
|
+
# Figure construction
|
|
207
|
+
# -------------------------
|
|
208
|
+
def generate_figure(self, unit_size: Tuple[float, float] = (3, 6)) -> None:
|
|
209
|
+
figure_size = (unit_size[0] * self.number_of_mode, unit_size[1])
|
|
210
|
+
self.figure = plt.figure(figsize=figure_size)
|
|
211
|
+
|
|
212
|
+
self.grid_spec = self.figure.add_gridspec(
|
|
213
|
+
2,
|
|
214
|
+
self.number_of_mode,
|
|
215
|
+
left=0.1,
|
|
216
|
+
right=0.95,
|
|
217
|
+
bottom=0.1,
|
|
218
|
+
top=0.9,
|
|
219
|
+
wspace=0.5,
|
|
220
|
+
hspace=0.35,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
self._generate_profile_ax()
|
|
224
|
+
self._generate_field_axes()
|
|
225
|
+
|
|
226
|
+
def _generate_profile_ax(self) -> None:
|
|
227
|
+
self.ax_profile = self.figure.add_subplot(self.grid_spec[0, :])
|
|
228
|
+
self.ax_profile.set_xlabel("Propagation distance z")
|
|
229
|
+
self.ax_profile.set_ylabel("Coupler profile")
|
|
230
|
+
|
|
231
|
+
top = self.profile.radius
|
|
232
|
+
bottom = -self.profile.radius
|
|
233
|
+
|
|
234
|
+
self.ax_profile.plot(self.profile.distance, top, color="black")
|
|
235
|
+
self.ax_profile.plot(self.profile.distance, bottom, color="black")
|
|
236
|
+
self.ax_profile.fill_between(
|
|
237
|
+
self.profile.distance, top, bottom, color="lightblue", alpha=0.8
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
self._profile_line = self.ax_profile.axvline(0.0, linestyle="--", color="red")
|
|
241
|
+
|
|
242
|
+
def _generate_field_axes(self) -> None:
|
|
243
|
+
self.field_axes = []
|
|
244
|
+
self._field_meshes = []
|
|
245
|
+
|
|
246
|
+
for mode_index in range(self.number_of_mode):
|
|
247
|
+
ax = self.figure.add_subplot(self.grid_spec[1, mode_index])
|
|
248
|
+
ax.set_aspect("equal")
|
|
249
|
+
ax.set_xticks([])
|
|
250
|
+
ax.set_yticks([])
|
|
251
|
+
ax.set_title(self.superset[mode_index].stylized_name)
|
|
252
|
+
|
|
253
|
+
# Placeholder mesh. We will replace data on first populate.
|
|
254
|
+
mesh = ax.pcolormesh(
|
|
255
|
+
[[0.0, 0.0], [0.0, 0.0]], cmap=colormaps.blue_black_red, shading="auto"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
self.field_axes.append(ax)
|
|
259
|
+
self._field_meshes.append(mesh)
|
|
260
|
+
|
|
261
|
+
# -------------------------
|
|
262
|
+
# Frame evaluation
|
|
263
|
+
# -------------------------
|
|
264
|
+
def _get_fields_at_z(self, z: float):
|
|
265
|
+
itr = self.profile.master_interpolation_z_to_itr(z)
|
|
266
|
+
slice_structure = self.superset.get_slice_structure(
|
|
267
|
+
itr=itr, add_symmetries=True
|
|
268
|
+
)
|
|
269
|
+
return itr, slice_structure.fields
|
|
270
|
+
|
|
271
|
+
def populate_axes(self, z: float) -> None:
|
|
272
|
+
itr, fields = self._get_fields_at_z(z)
|
|
273
|
+
|
|
274
|
+
self._profile_line.set_xdata([z, z])
|
|
275
|
+
self.ax_profile.set_title(f"Z-distance: {z:>5.3e} ITR: {itr:>5.3f}")
|
|
276
|
+
|
|
277
|
+
for mesh, field in zip(self._field_meshes, fields):
|
|
278
|
+
# QuadMesh stores a flattened array; set_array expects 1D.
|
|
279
|
+
mesh.set_array(field.ravel())
|
|
280
|
+
|
|
281
|
+
self.figure.canvas.draw_idle()
|
|
282
|
+
|
|
283
|
+
def update_axes(self, z: float) -> Sequence:
|
|
284
|
+
itr, fields = self._get_fields_at_z(z)
|
|
285
|
+
|
|
286
|
+
self._profile_line.set_xdata([z, z])
|
|
287
|
+
self.ax_profile.set_title(f"Z-distance: {z:>5.3e} ITR: {itr:>5.3f}")
|
|
288
|
+
|
|
289
|
+
updated_artists = [self._profile_line]
|
|
290
|
+
|
|
291
|
+
for mesh, field in zip(self._field_meshes, fields):
|
|
292
|
+
mesh.set_array(field.ravel())
|
|
293
|
+
updated_artists.append(mesh)
|
|
294
|
+
|
|
295
|
+
return updated_artists
|
|
296
|
+
|
|
297
|
+
# -------------------------
|
|
298
|
+
# Animation
|
|
299
|
+
# -------------------------
|
|
300
|
+
def make_animation(self, n_step: int = 20, dpi: float = 100, fps: int = 50):
|
|
301
|
+
settings = _AnimationSettings(n_step=n_step, dpi=float(dpi), fps=int(fps))
|
|
302
|
+
|
|
303
|
+
self.populate_axes(z=0.0)
|
|
304
|
+
|
|
305
|
+
z_max = float(self.profile.length)
|
|
306
|
+
step = z_max / float(settings.n_step)
|
|
307
|
+
|
|
308
|
+
def animate(frame_index: int):
|
|
309
|
+
z = frame_index * step
|
|
310
|
+
return self.update_axes(z)
|
|
311
|
+
|
|
312
|
+
animation = FuncAnimation(
|
|
313
|
+
self.figure,
|
|
314
|
+
animate,
|
|
315
|
+
interval=settings.interval_ms,
|
|
316
|
+
blit=True,
|
|
317
|
+
repeat=True,
|
|
318
|
+
frames=settings.n_step,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
animation.save(
|
|
322
|
+
settings.outfile,
|
|
323
|
+
dpi=settings.dpi,
|
|
324
|
+
writer=PillowWriter(fps=settings.fps),
|
|
325
|
+
)
|
SuPyMode/propagation.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
import pyvista
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
|
|
8
|
+
from SuPyMode.taper import AlphaProfile
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Propagation:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
superset: object,
|
|
15
|
+
distance: numpy.ndarray,
|
|
16
|
+
profile: AlphaProfile,
|
|
17
|
+
amplitudes: numpy.ndarray,
|
|
18
|
+
z_to_itr: object,
|
|
19
|
+
):
|
|
20
|
+
"""
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
profile : AlphaProfile
|
|
24
|
+
The profile to propagate.
|
|
25
|
+
amplitude : numpy.ndarray
|
|
26
|
+
The modes amplitudes.
|
|
27
|
+
"""
|
|
28
|
+
self.superset = superset
|
|
29
|
+
self.supermodes = superset.supermodes
|
|
30
|
+
self.z_to_itr = z_to_itr
|
|
31
|
+
self.distance = distance
|
|
32
|
+
self.profile = profile
|
|
33
|
+
self.amplitudes = amplitudes
|
|
34
|
+
self.z_to_itr = self.profile.get_itr_vs_distance_interpolation()
|
|
35
|
+
|
|
36
|
+
def plot(
|
|
37
|
+
self,
|
|
38
|
+
sub_sampling: int = 5,
|
|
39
|
+
show_energy: bool = True,
|
|
40
|
+
show_amplitudes: bool = True,
|
|
41
|
+
**kwargs: dict,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Plots the propagation of amplitudes over a given profile, optionally showing energy and amplitude plots.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
sub_sampling : int
|
|
49
|
+
The factor for sub-sampling data for plotting. Defaults to 5.
|
|
50
|
+
show_energy : bool
|
|
51
|
+
Whether to plot the energy of the modes. Defaults to True.
|
|
52
|
+
show_amplitudes : bool
|
|
53
|
+
Whether to plot the real part of the amplitudes. Defaults to True.
|
|
54
|
+
**kwargs : dict
|
|
55
|
+
Additional keyword arguments for solver.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
fig, ax = plt.subplots()
|
|
59
|
+
ax.set(xlabel="Propagation distance (z)", ylabel="Inverse taper ratio (ITR)")
|
|
60
|
+
|
|
61
|
+
x_values = self.distance[::sub_sampling]
|
|
62
|
+
|
|
63
|
+
for idx, mode in enumerate(self.supermodes):
|
|
64
|
+
color = f"C{idx}"
|
|
65
|
+
y_energy = numpy.abs(self.amplitudes[idx, ::sub_sampling]) ** 2
|
|
66
|
+
y_amplitude = self.amplitudes[idx, ::sub_sampling].real
|
|
67
|
+
|
|
68
|
+
if show_energy:
|
|
69
|
+
ax.plot(
|
|
70
|
+
x_values,
|
|
71
|
+
y_energy,
|
|
72
|
+
label=f"{mode.stylized_label} Energy",
|
|
73
|
+
linewidth=2.0,
|
|
74
|
+
linestyle="-",
|
|
75
|
+
color=color,
|
|
76
|
+
)
|
|
77
|
+
if show_amplitudes:
|
|
78
|
+
ax.plot(
|
|
79
|
+
x_values,
|
|
80
|
+
y_amplitude,
|
|
81
|
+
label=f"{mode.stylized_label} Amplitude",
|
|
82
|
+
linewidth=2.0,
|
|
83
|
+
linestyle="--",
|
|
84
|
+
color=color,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if show_energy:
|
|
88
|
+
total_energy = numpy.sqrt(
|
|
89
|
+
numpy.sum(numpy.abs(self.amplitudes) ** 2, axis=0)
|
|
90
|
+
)[::sub_sampling]
|
|
91
|
+
ax.plot(
|
|
92
|
+
x_values,
|
|
93
|
+
total_energy,
|
|
94
|
+
label="Total Energy",
|
|
95
|
+
linewidth=3.0,
|
|
96
|
+
linestyle="--",
|
|
97
|
+
color="black",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
ax.legend()
|
|
101
|
+
plt.show()
|
|
102
|
+
|
|
103
|
+
def get_field_combination(
|
|
104
|
+
self, amplitudes: numpy.ndarray, itr: int
|
|
105
|
+
) -> numpy.ndarray:
|
|
106
|
+
|
|
107
|
+
shape = self.superset.supermodes[0].field.data[0].shape
|
|
108
|
+
total_field = numpy.zeros(shape).astype(complex)
|
|
109
|
+
slice_number = 0
|
|
110
|
+
|
|
111
|
+
for mode, amplitude in zip(self.superset.supermodes, amplitudes):
|
|
112
|
+
total_field += amplitude * mode.field.data[slice_number]
|
|
113
|
+
|
|
114
|
+
return total_field
|
|
115
|
+
|
|
116
|
+
def generate_gif(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
sub_sampling: int = 5,
|
|
120
|
+
mutliplicative_factor: float = -100,
|
|
121
|
+
delta_azimuth: float = 0,
|
|
122
|
+
save_directory: str = "new_figure.gif",
|
|
123
|
+
colormap: str = "bwr",
|
|
124
|
+
**kwargs,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""
|
|
127
|
+
Generates a gif video of the mode propagation.
|
|
128
|
+
|
|
129
|
+
Parameters
|
|
130
|
+
----------
|
|
131
|
+
sub_sampling : int
|
|
132
|
+
Propagation undersampling factor for the video production.
|
|
133
|
+
mutliplicative_factor : float
|
|
134
|
+
Multiplicative factor for scaling.
|
|
135
|
+
save_directory : str
|
|
136
|
+
Directory to save the generated GIF.
|
|
137
|
+
delta_azimuth : float
|
|
138
|
+
Azimuthal change per frame.
|
|
139
|
+
**kwargs : dict
|
|
140
|
+
Additional keyword arguments for solver.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
Tuple: Propagation distances, amplitudes, and ITR list.
|
|
145
|
+
"""
|
|
146
|
+
sub_amplitudes = self.amplitudes[:, ::sub_sampling]
|
|
147
|
+
initial_amplitudes = self.amplitudes[:, 0]
|
|
148
|
+
|
|
149
|
+
sub_distance = self.distance[::sub_sampling]
|
|
150
|
+
sub_itr = self.z_to_itr(sub_distance)
|
|
151
|
+
|
|
152
|
+
total_field = self.get_field_combination(amplitudes=initial_amplitudes, itr=1.0)
|
|
153
|
+
|
|
154
|
+
x, y = numpy.mgrid[0 : total_field.shape[0], 0 : total_field.shape[1]]
|
|
155
|
+
grid = pyvista.StructuredGrid(x, y, total_field)
|
|
156
|
+
|
|
157
|
+
plotter = pyvista.Plotter(notebook=False, off_screen=True)
|
|
158
|
+
plotter.open_gif(save_directory, fps=20)
|
|
159
|
+
plotter.view_isometric()
|
|
160
|
+
# plotter.set_background('black', top='white')
|
|
161
|
+
|
|
162
|
+
plotter.add_mesh(
|
|
163
|
+
grid,
|
|
164
|
+
scalars=total_field,
|
|
165
|
+
style="surface",
|
|
166
|
+
show_edges=True,
|
|
167
|
+
edge_color="k",
|
|
168
|
+
colormap=colormap,
|
|
169
|
+
show_scalar_bar=False,
|
|
170
|
+
clim=[-100, 100],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
pts = grid.points.copy()
|
|
174
|
+
azimuth = 0
|
|
175
|
+
for z, amplitudes, itr in zip(sub_distance, sub_amplitudes.T, sub_itr):
|
|
176
|
+
print(f"itr: {itr}")
|
|
177
|
+
plotter.camera.elevation = -20
|
|
178
|
+
plotter.camera.azimuth = azimuth
|
|
179
|
+
azimuth += delta_azimuth
|
|
180
|
+
|
|
181
|
+
structure = self.superset.get_slice_structure(itr=itr, add_symmetries=True)
|
|
182
|
+
total_field = (
|
|
183
|
+
structure.get_field_combination(amplitudes, Linf_normalization=True)
|
|
184
|
+
* mutliplicative_factor
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
pts[:, -1] = total_field.T.ravel()
|
|
188
|
+
plotter.update_coordinates(pts, render=True)
|
|
189
|
+
plotter.update_scalars(total_field.T.ravel(), render=False)
|
|
190
|
+
plotter.add_title(
|
|
191
|
+
f"ITR: {itr: .3f}\t z: {z: .3e}",
|
|
192
|
+
font="courier",
|
|
193
|
+
color="w",
|
|
194
|
+
font_size=20,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
plotter.write_frame()
|
|
198
|
+
|
|
199
|
+
plotter.close()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# -
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .adiabatic import Adiabatic
|
|
2
|
+
|
|
3
|
+
from .index import Index
|
|
4
|
+
|
|
5
|
+
from .beating_length import BeatingLength
|
|
6
|
+
|
|
7
|
+
from .eigen_value import EigenValue
|
|
8
|
+
|
|
9
|
+
from .field import Field
|
|
10
|
+
|
|
11
|
+
from .beta import Beta
|
|
12
|
+
|
|
13
|
+
from .normalized_coupling import NormalizedCoupling
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Adiabatic",
|
|
17
|
+
"Beta",
|
|
18
|
+
"BeatingLength",
|
|
19
|
+
"EigenValue",
|
|
20
|
+
"Field",
|
|
21
|
+
"Index",
|
|
22
|
+
"NormalizedCoupling",
|
|
23
|
+
]
|