SpectrumAnalyzer
- class ipkiss3.all.SpectrumAnalyzer
Tool to analyze the transmission spectra of an S-matrix.
Finds and stores passband (or stopband) information and provides measurement methods.
- Parameters:
- smatrix: SMatrix1DSweep
The smatrix sweep object
- input_port_mode: str
The input port or port-mode. e.g. “in1”, “out1:0”
- output_port_modes: List[str]
The output ports or port-modes e.g. [“out0”, “out1”, “out2”]
- dB: bool
Whether to analyze the smatrix power in decibel (True) or linear scale (False)
- peak_method: ‘spline’ or ‘cwt’
Peak detection method to be used. Please check
i3.find_peaks
for a detailed description of the available methods.- peak_threshold: float or None
Threshold for peak detection
- bandpass: bool or None
Treat spectra as bandpass (True), bandstop (False) or automatic (None)
Examples
Taking the wavelength division multiplexer (MUX2) as an example, this code demonstrates how to use SpectrumAnalyzer to plot transmission and extract performance indices such as insertion loss, crosstalk, and bandwidth.
import ipkiss3.all as i3 import pylab as plt import numpy as np import pteam_library_si_fab.all as pt_lib import json # Design a wavelength - division multiplexer (MUX2) cell = pt_lib.Mux2( name="MUX2", fsr=0.02, center_wavelength=1.55, ) cell_lv = cell.Layout() cell_lv.visualize(annotate=True) cell_lv.write_gdsii("mux2.gds") # Simulating the design wavelengths = np.linspace(1.5, 1.6, 1001) cell_cm = cell.CircuitModel() S = cell_cm.get_smatrix(wavelengths=wavelengths) # use SpectrumAnalyzer to analyze the data obtained from the simulation. output_ports = ["out1", "out2"] sa = i3.SpectrumAnalyzer( S, input_port_mode="in1", output_port_modes=["out1", "out2"], dB=True, peak_method="spline", peak_threshold=-5, bandpass=True, ) channel_width = 0.0001 # Get the wavelengths and powers of the peaks of all output ports. peaks = sa.peaks() # Get the bandwidth of all output ports bands = sa.width_passbands(channel_width) # Get the min insertion loss of all output ports(the minimum power value within the channel bandwidth range) min_insertion_loss = sa.min_insertion_losses(bands=bands) # Get the max insertion loss of all output ports(the maximum power value within the channel bandwidth range) max_insertion_loss = sa.max_insertion_losses(bands=bands) # Get the adjacent channel crosstalk of all channels crosstalk_near = sa.near_crosstalk(bands=bands) # Get the non-adjacent channel crosstalk of all channels crosstalk_far = sa.far_crosstalk(bands=bands) # Get the 3dB bandwidth of the channel(If you want to get 1dB bandwidth, # just modify the "cutoff" value to -1.0.) passbands_3dB = sa.cutoff_passbands(cutoff=-3.0) # Get free spectrum range of all output channels fsr = sa.fsr() # Plot the spectrum of the simulation result sa.visualize(show=False, title="Spectrum", figure=None) for cnt, port in enumerate(output_ports): peak_number = len(peaks[port]) for j in range(peak_number): plt.plot() plt.axvline(x=peaks[port][j][0], color="r", label=f"{port}_{j}") plt.axvline(x=passbands_3dB[port][j][0], color="k", linestyle=":") plt.axvline(x=passbands_3dB[port][j][1], color="k", linestyle=":") plt.ylim(-50, 5) plt.show()
import pprint # Write all the performance indicators into a report. report = dict() for cnt, port in enumerate(output_ports): peak_number = len(peaks[port]) for j in range(peak_number): peak_name = f"peak_{j+1}_of_{port}" peaks_report = { "peak": peaks[port][j], "passbands_3dB": passbands_3dB[port][j][1] - passbands_3dB[port][j][0], "min_insertion_loss": min_insertion_loss[port][j], "max_insertion_loss": max_insertion_loss[port][j], "crosstalk_near": crosstalk_near[port], "crosstalk_far": crosstalk_far[port], } if j < peak_number - 1: peaks_report.update({"FSR": fsr[port][j]}) else: pass report[peak_name] = peaks_report pprint.pprint(report)
>>> {'peak_1_of_out1': {'FSR': 0.019299999999999873, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.5158837837218805, >>> 'min_insertion_loss': -0.5098368580368513, >>> 'passbands_3dB': 0.010000000000000009, >>> 'peak': (1.5111, -0.50983686)}, >>> 'peak_2_of_out1': {'FSR': 0.019600000000000062, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.4708532726562996, >>> 'min_insertion_loss': -0.46557237262919277, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.5304, -0.46557237)}, >>> 'peak_3_of_out1': {'FSR': 0.020000000000000018, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.47205772830361836, >>> 'min_insertion_loss': -0.4704133168455788, >>> 'passbands_3dB': 0.009799999999999809, >>> 'peak': (1.55, -0.47205773)}, >>> 'peak_4_of_out1': {'FSR': 0.020499999999999963, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.5076730733348127, >>> 'min_insertion_loss': -0.4997901712647961, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.57, -0.50767307)}, >>> 'peak_5_of_out1': {'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.572587344302674, >>> 'min_insertion_loss': -0.5602843027097913, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.5905, -0.57258734)}} >>> {'peak_1_of_out1': {'FSR': 0.019299999999999873, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.5158837837218805, >>> 'min_insertion_loss': -0.5098368580368513, >>> 'passbands_3dB': 0.010000000000000009, >>> 'peak': (1.5111, -0.50983686)}, >>> 'peak_1_of_out2': {'FSR': 0.019400000000000084, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -8.358909227452227, >>> 'max_insertion_loss': -0.9434032436381177, >>> 'min_insertion_loss': -0.9373168113401275, >>> 'passbands_3dB': 0.009400000000000075, >>> 'peak': (1.5208, -0.93731681)}, >>> 'peak_2_of_out1': {'FSR': 0.019600000000000062, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.4708532726562996, >>> 'min_insertion_loss': -0.46557237262919277, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.5304, -0.46557237)}, >>> 'peak_2_of_out2': {'FSR': 0.01980000000000004, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -8.358909227452227, >>> 'max_insertion_loss': -0.5958969351862193, >>> 'min_insertion_loss': -0.5952674678192059, >>> 'passbands_3dB': 0.009700000000000042, >>> 'peak': (1.5402, -0.59589694)}, >>> 'peak_3_of_out1': {'FSR': 0.020000000000000018, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.47205772830361836, >>> 'min_insertion_loss': -0.4704133168455788, >>> 'passbands_3dB': 0.009799999999999809, >>> 'peak': (1.55, -0.47205773)}, >>> 'peak_3_of_out2': {'FSR': 0.020199999999999996, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -8.358909227452227, >>> 'max_insertion_loss': -0.37917860822815724, >>> 'min_insertion_loss': -0.37386875944663156, >>> 'passbands_3dB': 0.009999999999999787, >>> 'peak': (1.56, -0.37917861)}, >>> 'peak_4_of_out1': {'FSR': 0.020499999999999963, >>> 'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.5076730733348127, >>> 'min_insertion_loss': -0.4997901712647961, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.57, -0.50767307)}, >>> 'peak_4_of_out2': {'crosstalk_far': None, >>> 'crosstalk_near': -8.358909227452227, >>> 'max_insertion_loss': -0.31463172002639866, >>> 'min_insertion_loss': -0.314602513963726, >>> 'passbands_3dB': 0.010499999999999954, >>> 'peak': (1.5802, -0.31460251)}, >>> 'peak_5_of_out1': {'crosstalk_far': None, >>> 'crosstalk_near': -12.466452971867321, >>> 'max_insertion_loss': -0.572587344302674, >>> 'min_insertion_loss': -0.5602843027097913, >>> 'passbands_3dB': 0.00990000000000002, >>> 'peak': (1.5905, -0.57258734)}}
def convert_numpy_void(data): if isinstance(data, dict): return {k: convert_numpy_void(v) for k, v in data.items()} elif isinstance(data, (list | tuple)): return [convert_numpy_void(i) for i in data] elif isinstance(data, np.void): return data.item() else: return data report_data = convert_numpy_void(report) # Save the report data as a JSON file with open("report_mux2.json", "w") as json_file: json.dump(report_data, json_file, indent=4)
- visualize(show=True, title=None, figure=None)
Visualize the transmission spectra.
- peaks()
Return the peaks per channel.
the peak data is stored as numpy record arrays. This allows you to use strings to access the values:
analyzer.peaks()['out1']['power']
Will give you only the power values of the peaks. Using ‘wavelength’ gives you the frequencies where the peaks are located.
analyzer.peaks()[‘out1’][‘wavelength’]
- Returns:
- dict
A mapping from the output ports to their corresponding spectral peaks:
{'out1': [(peak1_x, peak1_power), (peak2_x, peak2_power)]}
Notes
The first index of the record array takes its value from the sweep_parameter_name of the smatrix-sweep. In most cases this will be ‘wavelength’ as used in the example above.
- find_peaks(method='spline', threshold=None, bandpass=None, store=True, smoothing=0)
Uses
i3.find_peaks
to calculate a new list of peaks for every channel.- Parameters:
- method: ‘spline’ or ‘cwt’
Peak finding method
- threshold: float or None
Threshold power for peak finding. (autodetect if None)
- bandpass: True or None
Interprete spectra as bandpass (True), bandstop (False) or autodetect (None)
- store: bool
True: store the found peaks on the spectra. False: don’t store
- Returns:
- dict
A mapping from the output ports to their corresponding spectral peaks:
{'out1': [(peak1_x, peak1_power), (peak2_x, peak2_power)]}
- trim(band=None)
Trim the channels over a wavelength range of interest.
- Parameters:
- band
Lower and upper bound of x for the channels, i.e. (x_min, x_max). If no band is given, the whole spectrum is taken into account.
- Returns:
- SpectrumAnalyzer
New instance of SpectrumAnalyzer with trimmed channels
- min_insertion_losses(bands=None)
Calculate the minimum insertion losses for every channel.
- Parameters:
- bands
Lower and upper bounds of x for the channels. If not specified, the whole spectrum will be used. Can be specified manually or calculated by means of
cutoff_passbands
orwidth_passbands
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A mapping from the output ports to their corresponding insertion losses (one per band):
{'out1': [-3.134, -3.132], 'out2': [-3.024]}
- max_insertion_losses(bands=None)
Calculate the maximum insertion losses for every channel.
- Parameters:
- bands
Lower and upper bounds of x for the channels. If not specified, the whole spectrum will be used. Can be specified manually or calculated by means of
cutoff_passbands
orwidth_passbands
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A mapping from the output ports to their corresponding insertion losses (one per band):
{'out1': [-3.134, -3.132], 'out2': [-3.024]}
- fsr()
Calculate the free spectral range for every channel.
- Returns:
- dict
A mapping from the output ports to their corresponding FSR:
{'out1': 0.020, 'out2': 0.021}
- width_passbands(width)
Calculate x-limited passbands centered around the peaks for every channel.
- Parameters:
- width: float
Width of one passband, expressed in the same units and scale as x.
- Returns:
- dict
A mapping from the output ports to their corresponding passbands:
{'out1': [(x1, x2), (x3, x4)], 'out2': [(x5, x6)]}
- cutoff_passbands(cutoff)
Calculate power-limited passbands centered around the peaks for every channel.
- Parameters:
- cutoff: float
Maximum acceptable power loss with respect to the peak power.
- Returns:
- dict
A mapping from the output ports to their corresponding passbands:
{'out1': [(x1, x2), (x3, x4)], 'out2': [(x5, x6)]}
- far_crosstalk(bands)
Calculate the maximum crosstalk for every channel, ignoring nearest neighbours.
- Parameters:
- bands
Lower and upper bounds of x for the channels. Can be specified manually or calculated by means of
cutoff_passbands
orwidth_passbands
. Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A mapping from the output ports to their corresponding maximum crosstalks, ignoring nearest neighbours:
{'out1': -80.3, 'out2': -79.1}
- near_crosstalk(bands)
Calculate the maximum crosstalk for every channel, taking only nearest neighbours into account.
- Parameters:
- bands
Lower and upper bounds of x for the channels. Can be specified manually or calculated by means of
cutoff_passbands
orwidth_passbands
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A mapping from the output ports to their corresponding maximum crosstalks, taking only nearest neighbours into account:
{'out1': -80.3, 'out2': -79.1}
- far_crosstalk_matrix(bands)
Calculate the crosstalk for every passband in every channel, ignoring nearest neighbours.
- Parameters:
- bands
Lower and upper bounds of x for the channels. Can be specified manually or calculated by means of
cutoff_passbands
orwidth_passbands
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A matrix in the form of a dictionary that maps two output ports to their corresponding crosstalk. crosstalk_matrix[‘out1’][‘out3’] is the crosstalk in ‘out1’ caused by ‘out3’. Because ‘out1’ has two passbands, the crosstalk also has two values. For example:
{'out1': {'out3': array([-83.03362741, -83.59168257]), 'out4': array([-87.7717227 , -89.87154196]), 'out5': array([-90.2991268 , -91.66997018]), 'out6': array([-87.09402634, -87.20215863]), 'out7': array([-83.25175028, -83.27741616])}, ...}
- near_crosstalk_matrix(bands)
Calculate the crosstalk for every passband in every channel, taking only nearest neighbours into account.
- Parameters:
- bands
Lower and upper bounds of x for the channels. Can be specified manually or calculated by means of
SpectrumAnalyzer.cutoff_passbands()
orSpectrumAnalyzer.width_passbands()
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A matrix in the form of a dictionary that maps two output ports to their corresponding crosstalk. crosstalk_matrix[‘out1’][‘out2’] is the crosstalk in ‘out1’ caused by ‘out2’. Because ‘out1’ has two passbands, the crosstalk also has two values. For example:
{'out1': {'out2': array([-70.78072315, -74.29802755]), 'out8': array([-72.40049707, -75.09575688])}, ...}
- crosstalk_matrix(bands)
Calculate the crosstalk for every passband in every channel.
- Parameters:
- bands
Lower and upper bounds of x for the channels. Can be specified manually or calculated by means of
SpectrumAnalyzer.cutoff_passbands()
orSpectrumAnalyzer.width_passbands()
.Example where channel ‘out1’ has two passbands and ‘out2’ one passband over which we want to calculate the insertion loss:
{'out1': [(x_min1, x_max1), (x_min2, x_max2)], 'out2': [(x_min3, x_max3)]}
- Returns:
- dict
A matrix in the form of a dictionary that maps two output ports to their corresponding crosstalk.
crosstalk_matrix[‘out1’][‘out3’] is the crosstalk in ‘out1’ caused by ‘out3’.
crosstalk_matrix[‘out1’][‘out1’] is the insertion loss variation (ripple) within the passbands of ‘out1’.
Because ‘out1’ has two passbands, the crosstalk also has two values (one for every passband). For example:
{'out1': {'out1': array([-1.7659547 , -2.14301945]), 'out2': array([-70.78072315, -74.29802755]), 'out3': array([-83.03362741, -83.59168257]), 'out4': array([-87.7717227 , -89.87154196]), 'out5': array([-90.2991268 , -91.66997018]), 'out6': array([-87.09402634, -87.20215863]), 'out7': array([-83.25175028, -83.27741616]), 'out8': array([-72.40049707, -75.09575688])}, ...}