# 3.5. AWG simulation and analysis¶

## 3.5.1. Simulation¶

At this point we have a complete AWG with a layout, a logical netlist and a simulation model. We can now simply ask Caphe (IPKISS’ built-in circuit simulator) to calculate its S-parameters and save the results. This is done by the simulate_awg function that we have defined in rect_awg/simulate.py. This function takes the AWG PCell and a set of wavelengths as input and returns the simulated S-matrix.

from ipkiss3 import all as i3
import joblib
import os

def simulate_awg(awg, wavelengths, save_dir=None, tag=""):
"""Custom function to simulate an AWG. If you make an AWG, you should write one of your own.
Parameters are the awg cell you want to simulate and things that control your simulation as well as
parameters that control where you save your data.

Parameters
----------
awg : i3.Pcell
AWG PCell
wavelengths: ndarray
Simulation wavelengths
save_dir: str, default: None, optional
If specified, a GDSII file is saved in this directory
tag: str, optional
String used to give a name to saved files

Returns
-------
awg_s:
S-matrix of the simulated AWG

"""
cwdm_awg_cm = awg.get_default_view(i3.CircuitModelView)
print("Simulating AWG...\n")
awg_s = cwdm_awg_cm.get_smatrix(wavelengths)
if save_dir:
smatrix_path = os.path.join(save_dir, "smatrix_{}.z".format(tag))
joblib.dump(awg_s, smatrix_path)
print("{} written".format(smatrix_path))
return awg_s


## 3.5.2. Analysis¶

Next, the function analyze_awg, defined in rect_awg/analyze.py, will take the S-matrix and check it against the figure of merit. In this analysis function we define the AWG specifications and check the behaviour of the AWG according to those specs, using helper functions provided in the IPKISS AWG Designer. Let’s have a look a the function signature.

def analyze_awg(awg_s, plot=True, save_dir=None, tag=""):
"""Custom AWG analysis function. If you make an AWG, you should write an analysis function of your own.
The goal of the analyze_awg function is to answer the question: how does the AWG perform
according to my specs? In an analysis function you define the specs and check the behaviour
of the AWG according to those specs using helper functions provided in the IPKISS AWG Designer.
The parameters are the things you want to vary as well as options to control whether you
plot and where you save your data.

Parameters
----------
awg_s:
S-matrix of the AWG
plot: bool, default: True, optional
If true the spectrum  is plotted
save_dir: str, default: None, optional
If specified, a GDSII file is saved in this directory
tag: str, optional
String used to give a name to saved files

Returns
------
report: dict
Dictionary containing analysis specs
"""



### 3.5.2.1. Specifications¶

The first step of the analysis function is to define the specifications. Here we hard code the specifications of the required transmission channels. If necessary, parametric specifications may be inserted in the signature of the analysis function.

    # Specs
channel_freq = np.array([229000.0, 229800.0, 230600.0, 231400.0, 232200.0, 233000.0, 233800.0, 234600.0, 235400.0])
channel_wav = awg.frequency_to_wavelength(channel_freq)
channel_width = 10e-4
sim_wav = awg_s.sweep_parameter_values
sim_dw = sim_wav[1] - sim_wav[0]
output_ports = ["out{}".format(p+1) for p in range(len(channel_wav))]



### 3.5.2.2. Insertion Loss¶

The insertion loss is calculated by using the IPKISS AWG Designer built-in peak detection functions. In order to account for the “flat-top” behaviour, we detect the peak of the overlap of the transmission with a step function that has the width of the required pass-band.

    # Specs
channel_freq = np.array([229000.0, 229800.0, 230600.0, 231400.0, 232200.0, 233000.0, 233800.0, 234600.0, 235400.0])
channel_wav = awg.frequency_to_wavelength(channel_freq)
channel_width = 10e-4
sim_wav = awg_s.sweep_parameter_values
sim_dw = sim_wav[1] - sim_wav[0]
output_ports = ["out{}".format(p+1) for p in range(len(channel_wav))]

# Peak wavelengths
awg_auto_corr = copy.copy(awg_s)
for p1, p2 in itertools.product(awg_s.term_modes.keys(), awg_s.term_modes.keys()):
data = awg_s[p1, p2]
channel = np.ones(np.ceil(channel_width/sim_dw))
channel = channel/len(channel)
corr = np.correlate(np.abs(data), channel, 'same')
awg_auto_corr[p1, p2, :] = corr

peaks = awg.get_peaks(
awg_auto_corr,
input_pm="in1:0",
output_pms=["{}:0".format(p) for p in output_ports],
channel_wavelengths=channel_wav,
)

peak_error = np.abs(channel_wav - peaks)



### 3.5.2.3. Cross talk¶

Similarly, we calculate the nearest-neighbour and overall crosstalks using built-in analysis functions.

    # Crosstalk
cross_talk = awg.get_crosstalk(
awg_auto_corr,
input_pm="in1:0",
output_pms=["{}:0".format(p) for p in output_ports],
channel_wavelengths=channel_wav,
)

cross_talk_nn = awg.get_nearest_neighbor_crosstalk(
awg_auto_corr,
input_pm="in1:0",
output_pms=["{}:0".format(p) for p in output_ports],
channel_wavelengths=channel_wav,
)



### 3.5.2.4. Plotting and report generation¶

Finally, we plot the transmission spectrum and write a report containing the analyzed specifications.

    report = dict()
for cnt, port in enumerate(output_ports):
channel_report = {
"peak_expected": channel_wav[cnt],
"peak_simulated": peaks[cnt],
"peak_error": peak_error[cnt],
"insertion_loss": insertion_loss[cnt],
"cross_talk": cross_talk[cnt]-insertion_loss[cnt],
"cross_talk_nn": cross_talk_nn[cnt]-insertion_loss[cnt],
}

report[port] = channel_report

fig = plt.figure()
for p, ws, wa in zip(output_ports, channel_wav, peaks):
plt.plot(sim_wav, 10 * np.log10(np.abs(awg_s[p, "in1"]) ** 2), label=p)
plt.axvline(x=ws)
plt.axvline(x=wa)
plt.xlim([sim_wav[0], sim_wav[-1]])
plt.ylim([-80.0, 0.0])
plt.xlabel('Wavelength [um]')
plt.ylabel('Transmission')
plt.legend()
plt.title(tag.capitalize())

if save_dir:
def serialize_ndarray(obj):
return obj.tolist() if isinstance(obj, np.ndarray) else obj

report_path = os.path.join(save_dir, "report_{}.json".format(tag))
with open(report_path, "w") as f:
json.dump(report, f, sort_keys=True, default=serialize_ndarray, indent=0)
print("{} written".format(report_path))

png_path = os.path.join(save_dir, "spectrum_{}.png".format(tag))
fig.savefig(os.path.join(save_dir, png_path), bbox_inches='tight')
print("{} written".format(png_path))

if plot:
# Plot transmission spectrum
plt.show()
plt.close(fig)


Report JSON file: report_finalized.json