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.
Files changed (40) hide show
  1. SuPyMode/__init__.py +9 -0
  2. SuPyMode/_version.py +24 -0
  3. SuPyMode/boundaries.cp313-win_amd64.pyd +0 -0
  4. SuPyMode/directories.py +51 -0
  5. SuPyMode/eigensolver.cp313-win_amd64.pyd +0 -0
  6. SuPyMode/mesh.cp313-win_amd64.pyd +0 -0
  7. SuPyMode/model_parameters.cp313-win_amd64.pyd +0 -0
  8. SuPyMode/plotter.py +325 -0
  9. SuPyMode/propagation.py +202 -0
  10. SuPyMode/representation/__init__.py +23 -0
  11. SuPyMode/representation/adiabatic.py +111 -0
  12. SuPyMode/representation/beating_length.py +95 -0
  13. SuPyMode/representation/beta.py +78 -0
  14. SuPyMode/representation/eigen_value.py +79 -0
  15. SuPyMode/representation/field.py +486 -0
  16. SuPyMode/representation/index.py +80 -0
  17. SuPyMode/representation/normalized_coupling.py +98 -0
  18. SuPyMode/solver.py +203 -0
  19. SuPyMode/supermode.cp313-win_amd64.pyd +0 -0
  20. SuPyMode/superset.py +701 -0
  21. SuPyMode/superset_plots.py +497 -0
  22. SuPyMode/taper.cp313-win_amd64.pyd +0 -0
  23. SuPyMode/utils.py +196 -0
  24. SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP02.csv +98 -0
  25. SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP21.csv +88 -0
  26. SuPyMode/validation_data/SBB_figure_4_16_a/LP01-LP41.csv +86 -0
  27. SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP12.csv +82 -0
  28. SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP31.csv +95 -0
  29. SuPyMode/validation_data/SBB_figure_4_16_a/LP11-LP51.csv +81 -0
  30. SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP02.csv +285 -0
  31. SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP21.csv +75 -0
  32. SuPyMode/validation_data/SBB_figure_4_16_b/LP01-LP41.csv +237 -0
  33. SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP12.csv +126 -0
  34. SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP31.csv +156 -0
  35. SuPyMode/validation_data/SBB_figure_4_16_b/LP11-LP51.csv +224 -0
  36. SuPyMode/workflow.py +253 -0
  37. supymode-2.4.3.dist-info/METADATA +241 -0
  38. supymode-2.4.3.dist-info/RECORD +40 -0
  39. supymode-2.4.3.dist-info/WHEEL +5 -0
  40. supymode-2.4.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,486 @@
1
+ # #!/usr/bin/env python
2
+ # # -*- coding: utf-8 -*-
3
+
4
+ import numpy
5
+ from MPSPlots import colormaps
6
+ import matplotlib.pyplot as plt
7
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
8
+ from matplotlib import colors
9
+
10
+ from SuPyMode.utils import (
11
+ interpret_slice_number_and_itr,
12
+ slice_to_itr,
13
+ get_symmetrized_vector,
14
+ )
15
+ from SuPyMode.boundaries import BoundaryValue
16
+ from SuPyMode.supermode import SUPERMODE
17
+
18
+
19
+ class Field:
20
+ """
21
+ Represents a field derived from a supermode in a modal analysis framework.
22
+
23
+ This class extends functionality from a parent supermode class to manage field data operations,
24
+ including retrieving and processing field data for visualization and analysis.
25
+
26
+ Attributes
27
+ ----------
28
+ supermode : SUPERMODE
29
+ Reference to the parent supermode object that provides source data.
30
+ data : numpy.ndarray
31
+ The field data retrieved from the parent supermode binding.
32
+ """
33
+
34
+ def __init__(self, supermode: SUPERMODE):
35
+ """
36
+ Initialize the Field object with a reference to a parent supermode.
37
+
38
+ Parameters
39
+ ----------
40
+ supermode : SUPERMODE
41
+ The parent supermode from which this field is derived.
42
+ """
43
+ self.supermode = supermode
44
+ self.data = self.supermode.get_fields()
45
+
46
+ def _get_symmetrize_vector(self, *args, **kwargs):
47
+ """
48
+ Get the symmetrization vector from the parent supermode.
49
+
50
+ Returns
51
+ -------
52
+ Any
53
+ The symmetrization vector computed by the parent supermode.
54
+ """
55
+ return self.supermode._get_symmetrize_vector(*args, **kwargs)
56
+
57
+ def _get_axis_vector(self, supermode: object, add_symmetries: bool = True) -> tuple:
58
+ """
59
+ Computes the full axis vectors, optionally including symmetries.
60
+
61
+ Parameters
62
+ ----------
63
+ add_symmetries : bool, optional, default=True
64
+ Whether to include symmetries when computing the axis vectors.
65
+
66
+ Returns
67
+ -------
68
+ tuple
69
+ A tuple containing the full x-axis and y-axis vectors.
70
+ """
71
+ full_x_axis = self.supermode.model_parameters.x_vector
72
+ full_y_axis = self.supermode.model_parameters.y_vector
73
+
74
+ if not add_symmetries:
75
+ return full_x_axis, full_y_axis
76
+
77
+ if supermode.boundaries.right in [
78
+ BoundaryValue.Symmetric,
79
+ BoundaryValue.AntiSymmetric,
80
+ ]:
81
+ full_x_axis = get_symmetrized_vector(full_x_axis, symmetry_type="last")
82
+ full_x_axis.sort()
83
+
84
+ if supermode.boundaries.left in [
85
+ BoundaryValue.Symmetric,
86
+ BoundaryValue.AntiSymmetric,
87
+ ]:
88
+ full_x_axis = get_symmetrized_vector(full_x_axis, symmetry_type="first")
89
+ full_x_axis.sort()
90
+
91
+ if supermode.boundaries.top in [
92
+ BoundaryValue.Symmetric,
93
+ BoundaryValue.AntiSymmetric,
94
+ ]:
95
+ full_y_axis = get_symmetrized_vector(full_y_axis, symmetry_type="last")
96
+ full_y_axis.sort()
97
+
98
+ if supermode.boundaries.bottom in [
99
+ BoundaryValue.Symmetric,
100
+ BoundaryValue.AntiSymmetric,
101
+ ]:
102
+ full_y_axis = get_symmetrized_vector(full_y_axis, symmetry_type="first")
103
+ full_y_axis.sort()
104
+
105
+ return full_x_axis, full_y_axis
106
+
107
+ def get_axis(
108
+ self, supermode: object, slice_number: int, add_symmetries: bool = True
109
+ ) -> tuple:
110
+ """
111
+ Computes the scaled axis vectors for a specific slice, optionally including symmetries.
112
+
113
+ Parameters
114
+ ----------
115
+ slice_number : int
116
+ The slice index for which to compute the axis vectors.
117
+ add_symmetries : bool, optional, default=True
118
+ Whether to include symmetries in the computed axis vectors.
119
+
120
+ Returns
121
+ -------
122
+ tuple
123
+ A tuple containing the scaled x-axis and y-axis vectors for the given slice.
124
+ """
125
+ itr = supermode.model_parameters.itr_list[slice_number]
126
+ x_axis, y_axis = self._get_axis_vector(
127
+ supermode=supermode, add_symmetries=add_symmetries
128
+ )
129
+ return (x_axis * itr, y_axis * itr)
130
+
131
+ def get_norm(self, slice_number: int) -> float:
132
+ """
133
+ Calculate the norm of the field for a specific slice.
134
+
135
+ Parameters
136
+ ----------
137
+ slice_number : int
138
+ The slice number for which to calculate the norm.
139
+
140
+ Returns
141
+ -------
142
+ float
143
+ The norm of the field for the specified slice.
144
+ """
145
+ return self.supermode.get_norm(slice_number)
146
+
147
+ @property
148
+ def itr_list(self) -> numpy.ndarray:
149
+ """
150
+ Provides a list of iteration indices available for the fields.
151
+
152
+ Returns
153
+ -------
154
+ numpy.ndarray
155
+ An array of iteration indices.
156
+ """
157
+ return self.supermode.model_parameters.itr_list
158
+
159
+ @property
160
+ def parent_superset(self) -> object:
161
+ """
162
+ Access the parent set of the supermode.
163
+
164
+ Returns
165
+ -------
166
+ object
167
+ The parent set object.
168
+ """
169
+ return self.supermode.parent_set
170
+
171
+ def get_field(
172
+ self, slice_number: int = None, itr: float = None, add_symmetries: bool = True
173
+ ) -> numpy.ndarray:
174
+ """
175
+ Retrieve a specific field adjusted for boundary conditions and optionally add symmetries.
176
+
177
+ Parameters
178
+ ----------
179
+ slice_number : int, optional
180
+ The slice number to retrieve.
181
+ itr : float, optional
182
+ The iteration to use for retrieving the field.
183
+ add_symmetries : bool, optional, default=True
184
+ Whether to add boundary symmetries to the field.
185
+
186
+ Returns
187
+ -------
188
+ numpy.ndarray
189
+ The requested field as a numpy array.
190
+
191
+ Raises
192
+ ------
193
+ AssertionError
194
+ If neither or both of `slice_number` and `itr` are defined.
195
+ """
196
+ slice_list, itr_list = interpret_slice_number_and_itr(
197
+ itr_baseline=self.itr_list, itr_list=itr, slice_list=slice_number
198
+ )
199
+
200
+ fields = self.supermode.get_fields()
201
+
202
+ fields = numpy.take(fields, slice_list, axis=0)
203
+ if add_symmetries:
204
+ fields = self._get_symmetrized_field(fields)
205
+
206
+ return fields
207
+
208
+ def normalize_field(
209
+ self, field: numpy.ndarray, itr: float, norm_type: str = "L2"
210
+ ) -> numpy.ndarray:
211
+ """
212
+ Normalize a field array based on a specified normalization method.
213
+
214
+ Parameters
215
+ ----------
216
+ field : numpy.ndarray
217
+ The field to normalize.
218
+ itr : float
219
+ The iteration value for normalization scaling.
220
+ norm_type : str, optional, default='L2'
221
+ The type of normalization ('max', 'center', 'L2', 'cmt').
222
+
223
+ Returns
224
+ -------
225
+ numpy.ndarray
226
+ The normalized field.
227
+ """
228
+ match norm_type.lower():
229
+ case "max":
230
+ norm = abs(field).max()
231
+ case "center":
232
+ idx_x_center = numpy.argmin(
233
+ abs(self.supermode.parent_set.coordinate_system.x_vector)
234
+ )
235
+ idx_y_center = numpy.argmin(
236
+ abs(self.supermode.parent_set.coordinate_system.y_vector)
237
+ )
238
+ center_value = field[idx_x_center, idx_y_center]
239
+ norm = center_value
240
+ case "l2":
241
+ dx_scaled = self.supermode.parent_set.coordinate_system.dx * itr
242
+ dy_scaled = self.supermode.parent_set.coordinate_system.dy * itr
243
+ norm = numpy.sqrt(
244
+ numpy.trapz(
245
+ numpy.trapz(numpy.square(field), dx=dy_scaled, axis=0),
246
+ dx=dx_scaled,
247
+ axis=0,
248
+ )
249
+ )
250
+ case "cmt":
251
+ dx_scaled = self.supermode.parent_set.coordinate_system.dx * itr
252
+ dy_scaled = self.supermode.parent_set.coordinate_system.dy * itr
253
+ norm = numpy.sqrt(
254
+ numpy.trapz(
255
+ numpy.trapz(numpy.square(field), dx=dx_scaled, axis=0),
256
+ dx=dy_scaled,
257
+ axis=0,
258
+ )
259
+ )
260
+
261
+ return field / norm
262
+
263
+ def _get_symmetrized_field_and_axis(self, field: numpy.ndarray) -> tuple:
264
+ """
265
+ Generate a symmetrical version of the input field mesh according to defined boundary conditions.
266
+
267
+ Parameters
268
+ ----------
269
+ field : numpy.ndarray
270
+ The 2D field mesh to be symmetrized.
271
+
272
+ Returns
273
+ -------
274
+ tuple
275
+ A tuple containing the x-axis, y-axis, and the symmetrized field.
276
+
277
+ Raises
278
+ ------
279
+ AssertionError
280
+ If the input is not a 2D array.
281
+ """
282
+ x_axis, y_axis = self._get_axis_vector(add_symmetries=True)
283
+
284
+ field = self._get_symmetrized_field(field=field)
285
+
286
+ return x_axis, y_axis, field
287
+
288
+ def _get_symmetrized_field(self, field: numpy.ndarray) -> numpy.ndarray:
289
+ """
290
+ Retrieve the field and axis data adjusted for symmetry.
291
+
292
+ This method generates symmetrized versions of the field and its corresponding axis vectors.
293
+
294
+ Parameters
295
+ ----------
296
+ field : numpy.ndarray
297
+ The field data array to be symmetrized.
298
+
299
+ Returns
300
+ -------
301
+ numpy.ndarray
302
+ The symmetrized field data.
303
+
304
+ Raises
305
+ ------
306
+ AssertionError
307
+ If the input field is not a 2D array.
308
+ """
309
+ field = field.squeeze()
310
+ assert (
311
+ field.ndim == 2
312
+ ), f"Expected a 2-dimensional array, but got {field.ndim}-dimensional."
313
+
314
+ symmetric_field = field[:, -2::-1]
315
+ match self.supermode.boundaries.left:
316
+ case BoundaryValue.Symmetric:
317
+ field = numpy.c_[symmetric_field, field]
318
+
319
+ case BoundaryValue.AntiSymmetric:
320
+ field = numpy.c_[-symmetric_field, field]
321
+
322
+ match self.supermode.boundaries.right:
323
+ case BoundaryValue.Symmetric:
324
+ field = numpy.c_[field, symmetric_field]
325
+
326
+ case BoundaryValue.AntiSymmetric:
327
+ field = numpy.c_[field, -symmetric_field]
328
+
329
+ symmetric_field = field[-2::-1, :]
330
+ match self.supermode.boundaries.top:
331
+ case BoundaryValue.Symmetric:
332
+ field = numpy.r_[field, symmetric_field]
333
+
334
+ case BoundaryValue.AntiSymmetric:
335
+ field = numpy.r_[field, -symmetric_field]
336
+
337
+ match self.supermode.boundaries.bottom:
338
+ case BoundaryValue.Symmetric:
339
+ field = numpy.r_[symmetric_field, field]
340
+
341
+ case BoundaryValue.AntiSymmetric:
342
+ field = numpy.r_[-symmetric_field, field]
343
+
344
+ return field
345
+
346
+ def plot(
347
+ self,
348
+ itr_list: list[float] = [],
349
+ slice_list: list[int] = [0, -1],
350
+ add_symmetries: bool = True,
351
+ show_colorbar: bool = False,
352
+ show: bool = True,
353
+ ) -> None:
354
+ """
355
+ Plot the field for specified iterations or slice numbers.
356
+
357
+ Parameters
358
+ ----------
359
+ itr_list : list of float, optional
360
+ List of iterations to evaluate the field.
361
+ slice_list : list of int, optional, default=[0, -1]
362
+ List of slices to evaluate the field.
363
+ add_symmetries : bool, optional, default=True
364
+ Whether to include boundary symmetries in the plot.
365
+ show_mode_label : bool, optional, default=True
366
+ Whether to show the mode label.
367
+ show_itr : bool, optional, default=True
368
+ Whether to show the iteration value.
369
+ show_colorbar : bool, optional, default=False
370
+ Whether to show the colorbar.
371
+ show_slice : bool, optional, default=True
372
+ Whether to show the slice number.
373
+ """
374
+ slice_list, itr_list = interpret_slice_number_and_itr(
375
+ itr_baseline=self.itr_list, itr_list=itr_list, slice_list=slice_list
376
+ )
377
+
378
+ unit_size = numpy.array([len(slice_list), 1])
379
+ figure, axes = plt.subplots(*unit_size, figsize=3 * numpy.flip(unit_size))
380
+
381
+ for n, (itr, slice_number) in enumerate(zip(itr_list, slice_list)):
382
+ self.render_on_ax(
383
+ ax=axes[n],
384
+ slice_number=slice_number,
385
+ add_symmetries=add_symmetries,
386
+ show_colorbar=show_colorbar,
387
+ )
388
+
389
+ plt.tight_layout()
390
+
391
+ if show:
392
+ plt.show()
393
+
394
+ def render_on_ax(
395
+ self,
396
+ ax: plt.Axes,
397
+ slice_number: int,
398
+ show_mode_label: bool = True,
399
+ show_itr: bool = True,
400
+ show_slice: bool = True,
401
+ show_colorbar: bool = False,
402
+ add_symmetries: bool = True,
403
+ ) -> None:
404
+ """
405
+ Render the mode field at the given slice number into the input axis.
406
+
407
+ Args:
408
+ ax (plt.Axes): The matplotlib axis to render the field on.
409
+ slice_number (int): The slice number to render.
410
+ show_mode_label (bool): Whether to show the mode label.
411
+ show_itr (bool): Whether to show the iteration value.
412
+ show_slice (bool): Whether to show the slice number.
413
+ show_colorbar (bool): Whether to show the colorbar.
414
+ add_symmetries (bool): Whether to include boundary symmetries.
415
+ """
416
+ title = self.get_plot_mode_field_title(
417
+ slice_number=slice_number,
418
+ show_mode_label=show_mode_label,
419
+ show_itr=show_itr,
420
+ show_slice=show_slice,
421
+ )
422
+
423
+ ax.set_title(title)
424
+ ax.set_aspect("equal")
425
+
426
+ field = self.get_field(slice_number=slice_number, add_symmetries=add_symmetries)
427
+
428
+ x, y = self.get_axis(
429
+ supermode=self.supermode,
430
+ slice_number=slice_number,
431
+ add_symmetries=add_symmetries,
432
+ )
433
+
434
+ mappable = ax.pcolormesh(
435
+ x * 1e6,
436
+ y * 1e6,
437
+ field,
438
+ cmap=colormaps.blue_black_red,
439
+ norm=colors.CenteredNorm(),
440
+ )
441
+ plt.setp(ax.get_xticklabels(), visible=False)
442
+
443
+ if show_colorbar:
444
+ divider = make_axes_locatable(ax)
445
+ cax = divider.append_axes("right", size="5%", pad=0.05)
446
+
447
+ figure = ax.get_figure()
448
+ figure.colorbar(mappable, cax=cax, orientation="vertical")
449
+
450
+ def get_plot_mode_field_title(
451
+ self, slice_number: int, show_mode_label: bool, show_itr: bool, show_slice: bool
452
+ ) -> str:
453
+ """
454
+ Constructs a title for the field plot based on the mode, iteration, and slice number.
455
+
456
+ Args:
457
+ slice_number (int): The slice number for which the field is plotted.
458
+ show_mode_label (bool): Flag to include the mode label in the title.
459
+ show_itr (bool): Flag to include the iteration number in the title.
460
+ show_slice (bool): Flag to include the slice number in the title.
461
+
462
+ Returns:
463
+ str: The constructed title for the plot.
464
+ """
465
+ title = ""
466
+
467
+ if show_mode_label:
468
+ title += f"{self.supermode.stylized_label}"
469
+
470
+ if show_itr or show_slice:
471
+ itr = slice_to_itr(
472
+ itr_list=self.supermode.model_parameters.itr_list,
473
+ slice_number=slice_number,
474
+ )
475
+ title += "\n"
476
+
477
+ if show_slice:
478
+ title += f"slice: {slice_number}"
479
+
480
+ if show_itr:
481
+ title += f" itr: {itr:.3f}"
482
+
483
+ return title
484
+
485
+
486
+ # -
@@ -0,0 +1,80 @@
1
+ # #!/usr/bin/env python
2
+ # # -*- coding: utf-8 -*-
3
+ import matplotlib.pyplot as plt
4
+ from MPSPlots.helper import post_mpl_plot
5
+
6
+ from SuPyMode.supermode import SUPERMODE
7
+
8
+
9
+ class Index:
10
+ """
11
+ Represents the effective refractive index of a mode derived from a supermode in optical fiber simulations.
12
+
13
+ This class extends from `InheritFromSuperMode` for accessing supermode-related data and `BaseSingleModePlot`
14
+ for plotting functionalities tailored to visualize the effective refractive index.
15
+
16
+ Attributes
17
+ ----------
18
+ supermode : SUPERMODE
19
+ The supermode instance to which this Index object is linked.
20
+ data : numpy.ndarray
21
+ The effective refractive index data for the mode derived from the parent supermode.
22
+ """
23
+
24
+ def __init__(self, supermode: SUPERMODE):
25
+ """
26
+ Initialize an Index object with a reference to a parent supermode.
27
+
28
+ Parameters
29
+ ----------
30
+ supermode : SUPERMODE
31
+ The parent supermode object that provides the base mode data.
32
+ """
33
+ self.supermode = supermode
34
+ self.data = self.supermode.get_index()
35
+
36
+ @post_mpl_plot
37
+ def plot(self, ax: plt.Axes = None, show: bool = True) -> plt.Figure:
38
+ """
39
+ Plot the propagation constant for a single mode.
40
+
41
+ This method generates a plot of the propagation constants as a function of the inverse taper ratio (ITR),
42
+ formatted according to the predefined plot style.
43
+
44
+ Parameters
45
+ ----------
46
+ ax : matplotlib.axes.Axes, optional
47
+ The axis on which to plot. If `None`, a new axis is created (default is `None`).
48
+ show : bool, optional
49
+ Whether to display the plot immediately (default is `True`).
50
+
51
+ Returns
52
+ -------
53
+ matplotlib.figure.Figure
54
+ The figure object containing the generated plot.
55
+
56
+ """
57
+ if ax is None:
58
+ figure, ax = plt.subplots(1, 1)
59
+ else:
60
+ figure = ax.figure
61
+
62
+ ax.plot(
63
+ self.supermode.model_parameters.itr_list,
64
+ self.data,
65
+ label=f"{self.supermode.stylized_label}",
66
+ linewidth=2,
67
+ )
68
+
69
+ ax.legend()
70
+
71
+ ax.set(
72
+ xlabel="Inverse taper ratio",
73
+ ylabel="Effective refractive index",
74
+ ylim=[1.44, 1.455],
75
+ )
76
+
77
+ return figure
78
+
79
+
80
+ # -
@@ -0,0 +1,98 @@
1
+ # #!/usr/bin/env python
2
+ # # -*- coding: utf-8 -*-
3
+ import numpy
4
+ import matplotlib.pyplot as plt
5
+ from MPSPlots.helper import post_mpl_plot
6
+
7
+ from SuPyMode.supermode import SUPERMODE
8
+
9
+
10
+ class NormalizedCoupling:
11
+ """
12
+ Represents the normalized mode coupling between modes of different supermodes in optical fiber simulations.
13
+
14
+ This class extends from `InheritFromSuperMode` for accessing supermode-related data and provides plotting functionalities
15
+ tailored to visualize mode coupling comparisons.
16
+
17
+ Attributes
18
+ ----------
19
+ supermode : SUPERMODE
20
+ The supermode instance to which this NormalizedCoupling object is linked.
21
+
22
+ """
23
+
24
+ def __init__(self, supermode: SUPERMODE):
25
+ """
26
+ Initialize a NormalizedCoupling object with a reference to a parent supermode.
27
+
28
+ Parameters
29
+ ----------
30
+ supermode : SUPERMODE
31
+ The parent supermode object that provides the base mode data.
32
+ """
33
+ self.supermode = supermode
34
+
35
+ def get_values(self, other_supermode: SUPERMODE) -> numpy.ndarray:
36
+ """
37
+ Calculate the normalized mode coupling between the parent supermode and another specified supermode.
38
+
39
+ Parameters
40
+ ----------
41
+ other_supermode : SUPERMODE
42
+ The supermode with which to compare the parent supermode.
43
+
44
+ Returns
45
+ -------
46
+ numpy.ndarray
47
+ An array of normalized mode coupling values, adjusted for computational compatibility.
48
+ """
49
+ output = self.supermode.get_normalized_coupling_with_mode(other_supermode)
50
+
51
+ if not self.supermode.is_computation_compatible(other_supermode):
52
+ output *= 0
53
+
54
+ return output
55
+
56
+ @post_mpl_plot
57
+ def plot(self, other_supermode: SUPERMODE, ax: plt.Axes = None) -> plt.Figure:
58
+ """
59
+ Plot the normalized mode coupling between the parent supermode and another specified supermode.
60
+
61
+ This method generates a plot of the normalized coupling as a function of the inverse taper ratio (ITR),
62
+ formatted according to the predefined plot style.
63
+
64
+ Parameters
65
+ ----------
66
+ other_supermode : SUPERMODE
67
+ The supermode to compare against.
68
+ ax : matplotlib.axes.Axes
69
+ The axis on which to plot.
70
+
71
+ Returns
72
+ -------
73
+ matplotlib.figure.Figure
74
+ The figure object containing the generated plot.
75
+ """
76
+ if ax is None:
77
+ figure, ax = plt.subplots(nrows=1, ncols=1)
78
+ else:
79
+ figure = ax.figure
80
+
81
+ ax.set_xlabel("Inverse taper ratio")
82
+ ax.set_ylabel("Mode coupling")
83
+
84
+ if not self.supermode.is_computation_compatible(other_supermode):
85
+ return
86
+
87
+ y = self.get_values(other_supermode=other_supermode)
88
+
89
+ label = f"{self.supermode.stylized_label} - {other_supermode.stylized_label}"
90
+
91
+ ax.plot(self.supermode.model_parameters.itr_list, abs(y), label=label)
92
+
93
+ ax.legend()
94
+
95
+ return figure
96
+
97
+
98
+ # -