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.

../../../../_images/filter_measures.png
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()
../../../../_images/ipkiss3-all-SpectrumAnalyzer-1_00.png
../../../../_images/ipkiss3-all-SpectrumAnalyzer-1_01.png
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 or 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 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 or 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 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 or 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 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 or 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 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 or 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’. 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() or SpectrumAnalyzer.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() or SpectrumAnalyzer.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])},
...}