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,497 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from typing import TYPE_CHECKING
4
+
5
+ import matplotlib.pyplot as plt
6
+ import numpy
7
+ from matplotlib.backends.backend_pdf import PdfPages
8
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
9
+ from MPSPlots import helper
10
+
11
+ from SuPyMode.taper import AlphaProfile
12
+ from SuPyMode.supermode import SUPERMODE
13
+ from SuPyMode.utils import (
14
+ parse_filename,
15
+ interpret_slice_number_and_itr,
16
+ )
17
+
18
+ if TYPE_CHECKING:
19
+ from SuPyMode.superset import SuperSet
20
+
21
+
22
+ class SuperSetPlots(object):
23
+
24
+ @staticmethod
25
+ def test_valid_input(
26
+ user_input, valid_inputs: list, variable_name: str = ""
27
+ ) -> None:
28
+ if user_input not in valid_inputs:
29
+ raise ValueError(
30
+ f"[{variable_name}] user_input: {user_input} argument not valid. Valid choices are: {valid_inputs}"
31
+ )
32
+
33
+ @staticmethod
34
+ def interpret_mode_of_interest(
35
+ superset: "SuperSet", mode_of_interest: str | SUPERMODE | list[SUPERMODE]
36
+ ) -> list[SUPERMODE]:
37
+ """
38
+ Resolves the mode of interest from user input to the appropriate list of SUPERMODE instances
39
+ based on the specified criteria or direct references.
40
+
41
+ Parameters
42
+ ----------
43
+ superset : SuperSet
44
+ Superset containing the available modes.
45
+ mode_of_interest : str or SUPERMODE or list of SUPERMODE
46
+ A category ("fundamental", "non-fundamental", or "all"), one mode,
47
+ or an explicit list of modes.
48
+
49
+ Returns
50
+ -------
51
+ list of SUPERMODE
52
+ Modes selected by the request.
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ If the category or input type is unsupported.
58
+ """
59
+ from SuPyMode.supermode import SUPERMODE
60
+
61
+ if isinstance(mode_of_interest, str):
62
+ match mode_of_interest:
63
+ case "fundamental":
64
+ return superset.fundamental_supermodes
65
+ case "non-fundamental":
66
+ return superset.non_fundamental_supermodes
67
+ case "all":
68
+ return superset.supermodes
69
+ case _:
70
+ raise ValueError(
71
+ f"Unrecognized mode category '{mode_of_interest}'. Expected 'fundamental', 'non-fundamental', or 'all'."
72
+ )
73
+
74
+ if isinstance(mode_of_interest, SUPERMODE):
75
+ return [mode_of_interest]
76
+
77
+ if isinstance(mode_of_interest, list) and all(
78
+ isinstance(item, SUPERMODE) for item in mode_of_interest
79
+ ):
80
+ return mode_of_interest
81
+
82
+ raise ValueError(
83
+ "mode_of_interest must be either 'fundamental', 'non-fundamental', 'all', a SUPERMODE instance, or a list of SUPERMODE instances."
84
+ )
85
+
86
+ @helper.pre_plot(nrows=1, ncols=1)
87
+ def plot_index(
88
+ self,
89
+ axes: plt.Axes,
90
+ mode_of_interest: list[SUPERMODE] = "all",
91
+ ) -> plt.Figure:
92
+ """
93
+ Plot the effective index for each mode as a function of inverse taper ratio (ITR).
94
+
95
+ Parameters
96
+ ----------
97
+ axes : matplotlib.axes.Axes
98
+ The axes object on which to plot.
99
+ mode_of_interest : list of SUPERMODE
100
+ List of modes to be plotted.
101
+
102
+ Returns
103
+ -------
104
+ plt.Figure
105
+ The figure object containing the generated plots. This can be customized further or saved to a file.
106
+ """
107
+ mode_of_interest = self.interpret_mode_of_interest(
108
+ superset=self, mode_of_interest=mode_of_interest
109
+ )
110
+
111
+ for mode in mode_of_interest:
112
+ mode.index.plot(ax=axes, show=False)
113
+
114
+ axes.legend()
115
+
116
+ @helper.pre_plot(nrows=1, ncols=1)
117
+ def plot_beta(
118
+ self, axes: plt.Axes, mode_of_interest: list[SUPERMODE] | str = "all"
119
+ ) -> plt.Figure:
120
+ """
121
+ Plot the effective propagation constant for each mode as a function of inverse taper ratio (ITR).
122
+
123
+ Parameters
124
+ ----------
125
+ axes : matplotlib.axes.Axes
126
+ The axes object on which to plot.
127
+ mode_of_interest : list of SUPERMODE
128
+ List of modes to be plotted.
129
+
130
+ Examples
131
+ --------
132
+ >>> fig, ax = plt.subplots()
133
+ >>> superset_plots.plot_index(ax=ax, mode_of_interest=[mode1, mode2])
134
+ >>> plt.show()
135
+
136
+ Returns
137
+ -------
138
+ plt.Figure
139
+ The figure object containing the generated plots. This can be customized further or saved to a file.
140
+ """
141
+ mode_of_interest = self.interpret_mode_of_interest(
142
+ superset=self, mode_of_interest=mode_of_interest
143
+ )
144
+
145
+ for mode in mode_of_interest:
146
+ mode.beta.plot(ax=axes, show=False)
147
+
148
+ axes.legend()
149
+
150
+ @helper.pre_plot(nrows=1, ncols=1)
151
+ def plot_eigen_value(
152
+ self, axes: plt.Axes, mode_of_interest: list[SUPERMODE] | str = "all"
153
+ ) -> plt.Figure:
154
+ """
155
+ Plot the computed eigen-values for each mode as a function of inverse taper ratio (ITR).
156
+
157
+ Parameters
158
+ ----------
159
+ axes : matplotlib.axes.Axes
160
+ The axes object on which to plot.
161
+ mode_of_interest : list of SUPERMODE
162
+ List of modes to be plotted.
163
+
164
+ Examples
165
+ --------
166
+ >>> fig, ax = plt.subplots()
167
+ >>> superset_plots.plot_index(ax=ax, mode_of_interest=[mode1, mode2])
168
+ >>> plt.show()
169
+
170
+ Returns
171
+ -------
172
+ plt.Figure
173
+ The figure object containing the generated plots. This can be customized further or saved to a file.
174
+ """
175
+ mode_of_interest = self.interpret_mode_of_interest(
176
+ superset=self, mode_of_interest=mode_of_interest
177
+ )
178
+
179
+ for mode in mode_of_interest:
180
+ mode.eigenvalue.plot(ax=axes, show=False)
181
+
182
+ axes.legend()
183
+
184
+ @helper.pre_plot(nrows=1, ncols=1)
185
+ def plot_beating_length(
186
+ self,
187
+ axes: plt.Axes,
188
+ mode_of_interest: list[SUPERMODE] = "all",
189
+ combination: list = "pairs",
190
+ ) -> plt.Figure:
191
+ """
192
+ Render a figure representing the beating length for each mode combination as a function of inverse taper ratio (ITR).
193
+
194
+ Parameters
195
+ ----------
196
+ axes : matplotlib.axes.Axes
197
+ The axes object on which to plot.
198
+ mode_of_interest : list of SUPERMODE
199
+ List of modes to be plotted.
200
+ combination : list
201
+ List of mode combinations.
202
+
203
+ Returns
204
+ -------
205
+ plt.Figure
206
+ The figure object containing the generated plots. This can be customized further or saved to a file.
207
+ """
208
+ mode_of_interest = self.interpret_mode_of_interest(
209
+ superset=self, mode_of_interest=mode_of_interest
210
+ )
211
+
212
+ combination = self.interpret_combination(
213
+ mode_of_interest=mode_of_interest, combination=combination
214
+ )
215
+
216
+ for mode_0, mode_1 in combination:
217
+ mode_0.beating_length.plot(ax=axes, other_supermode=mode_1)
218
+
219
+ axes.legend()
220
+
221
+ @helper.pre_plot(nrows=1, ncols=1)
222
+ def plot_normalized_coupling(
223
+ self,
224
+ axes: plt.Axes,
225
+ mode_of_interest: list[SUPERMODE] | str = "all",
226
+ combination: list | str = "pairs",
227
+ ) -> plt.Figure:
228
+ """
229
+ Render a figure representing the normalized coupling for each mode combination as a function of inverse taper ratio (ITR).
230
+
231
+ Parameters
232
+ ----------
233
+ ax : matplotlib.axes.Axes
234
+ The axes object on which to plot.
235
+ mode_of_interest : list of SUPERMODE
236
+ List of modes to be plotted.
237
+ combination : list
238
+ List of mode combinations.
239
+
240
+ Returns
241
+ -------
242
+ plt.Figure
243
+ The figure object containing the generated plots. This can be customized further or saved to a file.
244
+ """
245
+ mode_of_interest = self.interpret_mode_of_interest(
246
+ superset=self, mode_of_interest=mode_of_interest
247
+ )
248
+
249
+ combination = self.interpret_combination(
250
+ mode_of_interest=mode_of_interest, combination=combination
251
+ )
252
+
253
+ for mode_0, mode_1 in combination:
254
+ mode_0.normalized_coupling.plot(ax=axes, other_supermode=mode_1, show=False)
255
+
256
+ @helper.pre_plot(nrows=1, ncols=1)
257
+ def plot_adiabatic(
258
+ self,
259
+ axes: plt.Axes,
260
+ mode_of_interest: list[SUPERMODE] | str = "all",
261
+ combination: list | str = "pairs",
262
+ add_profile: list[AlphaProfile] = None,
263
+ ) -> plt.Figure:
264
+ """
265
+ Render a figure representing the adiabatic criterion for each mode combination as a function of inverse taper ratio (ITR).
266
+
267
+ Parameters
268
+ ----------
269
+ ax : matplotlib.axes.Axes
270
+ The axes object on which to plot.
271
+ mode_of_interest : list of SUPERMODE
272
+ List of modes to be plotted.
273
+ combination : list
274
+ List of mode combinations.
275
+ add_profile : list of AlphaProfile, optional
276
+ List of profiles to add to the plot (default is an empty list).
277
+
278
+ Returns
279
+ -------
280
+ plt.Figure
281
+ The figure object containing the generated plots. This can be customized further or saved to a file.
282
+ """
283
+ mode_of_interest = self.interpret_mode_of_interest(
284
+ superset=self, mode_of_interest=mode_of_interest
285
+ )
286
+
287
+ combination = self.interpret_combination(
288
+ mode_of_interest=mode_of_interest, combination=combination
289
+ )
290
+
291
+ for mode_0, mode_1 in combination:
292
+ mode_0.adiabatic.plot(ax=axes, other_supermode=mode_1, show=False)
293
+
294
+ if add_profile is not None:
295
+ for profile in numpy.atleast_1d(add_profile):
296
+ profile.render_adiabatic_factor_vs_itr_on_ax(ax=axes, line_style="--")
297
+
298
+ @helper.post_mpl_plot
299
+ def plot_field(
300
+ self,
301
+ mode_of_interest: list = "all",
302
+ itr_list: list[float] = None,
303
+ slice_list: list[int] = None,
304
+ show_mode_label: bool = True,
305
+ show_itr: bool = True,
306
+ show_slice: bool = True,
307
+ ) -> plt.Figure:
308
+ """
309
+ Render the mode field for different ITR values or slice numbers.
310
+
311
+ Parameters
312
+ ----------
313
+ mode_of_interest : list, optional
314
+ List of modes to be plotted (default is 'all').
315
+ itr_list : list of float, optional
316
+ List of ITR values for plotting (default is None).
317
+ slice_list : list of int, optional
318
+ List of slice numbers for plotting (default is None).
319
+ show_mode_label : bool, optional
320
+ Flag to display mode labels (default is True).
321
+ show_itr : bool, optional
322
+ Flag to display ITR values (default is True).
323
+ show_slice : bool, optional
324
+ Flag to display slice numbers (default is True).
325
+
326
+ Returns
327
+ -------
328
+ plt.Figure
329
+ The figure object containing the generated plots. This can be customized further or saved to a file.
330
+ """
331
+ mode_of_interest = self.interpret_mode_of_interest(
332
+ superset=self, mode_of_interest=mode_of_interest
333
+ )
334
+
335
+ # Interpret input lists
336
+ slice_list, itr_list = interpret_slice_number_and_itr(
337
+ itr_baseline=self.model_parameters.itr_list,
338
+ itr_list=itr_list,
339
+ slice_list=slice_list,
340
+ )
341
+
342
+ mode_of_interest = self.interpret_mode_of_interest(
343
+ superset=self, mode_of_interest=mode_of_interest
344
+ )
345
+
346
+ n_mode = len(mode_of_interest)
347
+ n_slice = len(slice_list)
348
+ grid_size = numpy.array([n_slice, n_mode])
349
+
350
+ figure, axes = plt.subplots(
351
+ *grid_size,
352
+ figsize=3 * numpy.flip(grid_size),
353
+ squeeze=False,
354
+ sharex=False,
355
+ sharey=False,
356
+ )
357
+
358
+ # Plot each mode field on the grid
359
+ for m, mode in enumerate(mode_of_interest):
360
+ for n, slice_number in enumerate(slice_list):
361
+ mode.field.render_on_ax(
362
+ ax=axes[n, m],
363
+ slice_number=slice_number,
364
+ show_mode_label=show_mode_label,
365
+ show_itr=show_itr,
366
+ show_slice=show_slice,
367
+ )
368
+
369
+ return figure
370
+
371
+ @helper.post_mpl_plot
372
+ def plot_geometry(self) -> plt.Figure:
373
+ """
374
+ Render the geometry of the taper on the provided axes.
375
+
376
+ Parameters
377
+ ----------
378
+ ax : matplotlib.axes.Axes
379
+ The axes object on which to plot the geometry.
380
+
381
+ Returns
382
+ -------
383
+ plt.Figure
384
+ The figure object containing the generated plot. This can be customized further or saved to a file.
385
+ """
386
+ figure, ax = plt.subplots(figsize=(5, 5))
387
+
388
+ pcm = ax.pcolormesh(
389
+ self.model_parameters.x_vector,
390
+ self.model_parameters.y_vector,
391
+ self.model_parameters.mesh,
392
+ shading="auto",
393
+ cmap="Blues",
394
+ )
395
+
396
+ ax.set_aspect("equal")
397
+
398
+ divider = make_axes_locatable(ax)
399
+ cax = divider.append_axes("right", size="5%", pad=0.05)
400
+
401
+ figure.colorbar(pcm, cax=cax)
402
+
403
+ return figure
404
+
405
+ def plot(self, plot_type: str, **kwargs) -> None:
406
+ """
407
+ General plotting function to handle different types of supermode plots.
408
+
409
+ Parameters
410
+ ----------
411
+ plot_type : str
412
+ The type of plot to generate. Options include 'index', 'beta', 'eigen-value', etc.
413
+ **kwargs : dict
414
+ Additional keyword arguments for specific plot configurations.
415
+
416
+ Raises
417
+ ------
418
+ ValueError
419
+ If an unrecognized plot type is specified.
420
+ """
421
+ match plot_type.lower().replace("_", "-"):
422
+ case "geometry":
423
+ return self.plot_geometry(**kwargs)
424
+ case "index":
425
+ return self.plot_index(**kwargs)
426
+ case "beta":
427
+ return self.plot_beta(**kwargs)
428
+ case "eigen-value":
429
+ return self.plot_eigen_value(**kwargs)
430
+ case "normalized-coupling":
431
+ return self.plot_normalized_coupling(**kwargs)
432
+ case "overlap":
433
+ return self.plot_overlap(**kwargs)
434
+ case "adiabatic":
435
+ return self.plot_adiabatic(**kwargs)
436
+ case "field":
437
+ return self.plot_field(**kwargs)
438
+ case "beating-length":
439
+ return self.plot_beating_length(**kwargs)
440
+ case "normalized-adiabatic":
441
+ return self.plot_normalized_adiabatic(**kwargs)
442
+ case _:
443
+ raise ValueError(
444
+ f"Invalid plot type: {plot_type}. Options are: geometry, index, beta, eigen-value, adiabatic, normalized-adiabatic, normalized-coupling, field, beating-length"
445
+ )
446
+
447
+ @parse_filename
448
+ def generate_pdf_report(
449
+ self,
450
+ filename: str = "auto",
451
+ itr_list: list[float] | None = None,
452
+ slice_list: list[int] | None = None,
453
+ mode_of_interest: list[SUPERMODE] | str = "all",
454
+ combination: str = "pairs",
455
+ ) -> None:
456
+ """
457
+ Generate a full report of the coupler properties as a PDF file.
458
+
459
+ Parameters
460
+ ----------
461
+ filename : str, optional, default="auto"
462
+ Name of the report file to be output. If "auto", a default name will be generated based on the current timestamp.
463
+ directory : str, optional, default='.'
464
+ Directory to save the report.
465
+ itr_list : list of float, optional, default=None
466
+ List of ITR values to evaluate the mode field. If None, all available ITR values will be used.
467
+ slice_list : list of int, optional, default=None
468
+ List of slice values to evaluate the mode field. If None, all available slices will be used.
469
+ mode_of_interest : list, optional, default='all'
470
+ List of modes to consider in the report. If 'all', all available modes will be included.
471
+ combination : str, optional, default='specific'
472
+ Method for selecting mode combinations ('specific' or 'pairs').
473
+
474
+ Examples
475
+ --------
476
+ >>> superset_plots.generate_pdf_report(filename="coupler_report", itr_list=[0.1, 0.2, 0.5], mode_of_interest=['LP01', 'LP11'])
477
+ This will generate a PDF report named 'coupler_report.pdf' containing plots for the specified modes at given ITR values.
478
+ """
479
+ kwargs = dict(show=False, mode_of_interest=mode_of_interest)
480
+
481
+ figure_list = [
482
+ self.plot_geometry(show=False),
483
+ self.plot_field(itr_list=itr_list, slice_list=slice_list, **kwargs),
484
+ self.plot_index(**kwargs),
485
+ self.plot_beta(**kwargs),
486
+ self.plot_normalized_coupling(**kwargs, combination=combination),
487
+ self.plot_adiabatic(show=False, combination=combination),
488
+ ]
489
+
490
+ pp = PdfPages(filename.with_suffix(".pdf"))
491
+
492
+ for fig in figure_list:
493
+ fig.savefig(pp, format="pdf")
494
+
495
+ pp.close()
496
+
497
+ plt.close()
Binary file
SuPyMode/utils.py ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ from typing import Iterable, Callable, List, Union
4
+ import numpy
5
+ import pickle
6
+ from pathlib import Path
7
+ from functools import wraps
8
+ from pathvalidate import sanitize_filepath
9
+ import logging
10
+
11
+ from SuPyMode.directories import user_data_directory
12
+
13
+
14
+ def interpret_slice_number_and_itr(
15
+ itr_baseline: numpy.ndarray,
16
+ slice_list: Union[int, List[int], None] = None,
17
+ itr_list: Union[float, List[float], None] = None,
18
+ sort_slice_number: bool = True,
19
+ ) -> tuple:
20
+ """
21
+ Interprets slice numbers and corresponding inverse taper ratios (ITRs), returning arrays of slice numbers
22
+ and their respective ITRs based on the provided lists of slices and ITRs.
23
+
24
+ Parameters:
25
+ - itr_baseline (numpy.ndarray): Array of baseline ITR values, typically equidistant, representing the full range.
26
+ - slice_list (Union[int, List[int]], optional): A single slice index or a list of slice indices. If None and itr_list is also None, defaults to [0, -1].
27
+ - itr_list (Union[float, List[float]], optional): A single ITR value or a list of ITR values to be converted to slice indices. If provided, slice_list defaults to an empty list unless specified.
28
+ - sort_slice_number (bool, optional): Whether to sort the resulting slice numbers and corresponding ITRs in descending order. Default is False.
29
+
30
+ Returns:
31
+ - Tuple[numpy.ndarray, numpy.ndarray]: Two numpy arrays, the first containing slice indices and the second containing the corresponding ITR values.
32
+
33
+ Raises:
34
+ - ValueError: If the provided ITRs or slice indices are outside the bounds of the baseline ITR array.
35
+ """
36
+
37
+ if slice_list is None and itr_list is None:
38
+ slice_list = [0, -1]
39
+ elif itr_list is not None and slice_list is None:
40
+ slice_list = []
41
+
42
+ slice_list = numpy.atleast_1d(slice_list)
43
+ itr_list = numpy.atleast_1d(itr_list) if itr_list is not None else numpy.array([])
44
+
45
+ # Logic to convert ITRs to slice indices, and combine lists
46
+ slice_from_itr = (
47
+ itr_to_slice(itr_baseline, itr=itr_list)
48
+ if itr_list.size > 0
49
+ else numpy.array([])
50
+ )
51
+ total_slice_list = numpy.concatenate([slice_list, slice_from_itr]).astype(int)
52
+
53
+ total_itr_list = slice_to_itr(itr_baseline, slice_number=total_slice_list)
54
+
55
+ # Sort if required
56
+ if sort_slice_number:
57
+ indices = numpy.argsort(total_itr_list)
58
+ total_slice_list = total_slice_list[indices][::-1] # Descending order
59
+ total_itr_list = total_itr_list[indices][::-1]
60
+
61
+ return total_slice_list, total_itr_list
62
+
63
+
64
+ def parse_filename(save_function: Callable) -> Callable:
65
+ @wraps(save_function)
66
+ def wrapper(superset, filename: str, directory: str = "auto", **kwargs):
67
+ directory = user_data_directory if directory == "auto" else directory
68
+
69
+ filename = Path(filename)
70
+
71
+ filename = sanitize_filepath(filename)
72
+
73
+ filename = Path(directory).joinpath(filename)
74
+
75
+ save_function(superset, filename=filename, **kwargs)
76
+
77
+ logging.info(f"Saving data into: {filename}")
78
+
79
+ return filename
80
+
81
+ return wrapper
82
+
83
+
84
+ def load_superset(filename: str, directory: str = "default"):
85
+ """
86
+ Saves the superset instance as a serialized pickle file.
87
+
88
+ :param filename: The filename
89
+ :type filename: str
90
+ """
91
+ if directory == "default":
92
+ directory = user_data_directory
93
+
94
+ filename = Path(directory).joinpath(filename).with_suffix(".pickle")
95
+
96
+ with open(filename, "rb") as input_file:
97
+ superset = pickle.load(input_file)
98
+
99
+ return superset
100
+
101
+
102
+ def itr_to_slice(itr_list: numpy.ndarray, itr: float | list) -> int | list:
103
+ """
104
+ Converts an inverse taper ratio (itr) or a list of itrs to their closest corresponding slice numbers
105
+ in the provided itr_list. If a single itr is provided, returns a single slice number. If a list of
106
+ itrs is provided, returns a list of slice numbers.
107
+
108
+ Parameters:
109
+ itr_list (np.ndarray): Array of itrs, typically representing a continuous range of values.
110
+ itr (Union[float, List[float]]): A single itr value or a list of itr values.
111
+
112
+ Returns:
113
+ Union[int, List[int]]: The closest slice number or list of slice numbers corresponding to the provided itr or itrs.
114
+
115
+ """
116
+ if numpy.isscalar(itr):
117
+ return numpy.argmin(abs(itr_list - itr))
118
+
119
+ if isinstance(itr_list, Iterable):
120
+ return [numpy.argmin(abs(itr_list - value)) for value in itr]
121
+
122
+ raise TypeError("itr must be a float or a list of floats.")
123
+
124
+
125
+ def slice_to_itr(itr_list: numpy.ndarray, slice_number: int | list) -> float | list:
126
+ """
127
+ Converts a slice number or a list of slice numbers to their corresponding inverse taper ratios (itrs)
128
+ from the provided itr_list. If a single slice number is provided, returns a single itr value.
129
+ If a list of slice numbers is provided, returns a list of itr values.
130
+
131
+ Parameters:
132
+ - itr_list (np.ndarray): Array of itrs, typically representing a continuous range of values.
133
+ - slice_number (Union[int, List[int]]): A single slice index or a list of slice indices.
134
+
135
+ Returns:
136
+ - Union[float, List[float]]: The itr or list of itrs corresponding to the given slice numbers.
137
+
138
+ """
139
+ if numpy.isscalar(slice_number):
140
+ return itr_list[slice_number]
141
+
142
+ if isinstance(slice_number, Iterable):
143
+ return numpy.take(itr_list, slice_number)
144
+
145
+ raise TypeError("slice_number must be a scalar or a list of scalars.")
146
+
147
+
148
+ def get_symmetrized_vector(
149
+ vector: numpy.ndarray, symmetry_type: str = "last"
150
+ ) -> numpy.ndarray:
151
+ """
152
+ Generate a symmetric version of the input vector based on the specified symmetry type.
153
+
154
+ Parameters:
155
+ -----------
156
+ vector : numpy.ndarray
157
+ A one-dimensional array for which the symmetric version is to be calculated.
158
+ symmetry_type : str, optional
159
+ Type of symmetry to apply. Supported types:
160
+ - 'last': Symmetrize using the last element as reference.
161
+ - 'first': Symmetrize using the first element as reference.
162
+ Default is 'last'.
163
+
164
+ Returns
165
+ -------
166
+ numpy.ndarray
167
+ A new vector that is the symmetrized version of the input vector.
168
+
169
+ Raises:
170
+ -------
171
+ ValueError
172
+ If the input vector is not one-dimensional or the symmetry type is unsupported.
173
+ """
174
+
175
+ if vector.ndim != 1:
176
+ raise ValueError(
177
+ f"Expected a 1-dimensional vector, but got a {vector.ndim}-dimensional vector instead."
178
+ )
179
+
180
+ if symmetry_type.lower() not in ["last", "first"]:
181
+ raise ValueError("Symmetry type must be 'last' or 'first'.")
182
+
183
+ size = len(vector)
184
+ dx = numpy.diff(vector)[0] # More robust than assuming vector[1] - vector[0]
185
+
186
+ if symmetry_type.lower() == "last":
187
+ start_value = vector[-1]
188
+ expanded = numpy.arange(0, 2 * size - 1) * dx
189
+ return start_value - expanded[::-1] if dx > 0 else start_value + expanded[::-1]
190
+ else: # 'first'
191
+ start_value = vector[0]
192
+ expanded = numpy.arange(0, 2 * size - 1) * dx
193
+ return start_value + expanded
194
+
195
+
196
+ # -