Generate measurements#

For this tutorial you will generate some sample (fake) measurement data so you can post it to your project.

You’re going to create a new folder and populate it with JSON files containing the fake measurement data for the whole wafer.

import json
import pandas as pd
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt
df = pd.read_csv("design_manifest.csv")
df
cell x y width_um length_um analysis analysis_parameters
0 cutback_rib_assembled_MFalse_W0p3_L0 20150 60150 0.3 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
1 cutback_rib_assembled_MTrue_W0p3_L25000 1039250 60150 0.3 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
2 cutback_rib_assembled_MFalse_W0p3_L5000 20150 204150 0.3 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
3 cutback_rib_assembled_MTrue_W0p3_L20000 1039250 204150 0.3 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
4 cutback_rib_assembled_MFalse_W0p3_L10000 20150 348150 0.3 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
5 cutback_rib_assembled_MTrue_W0p3_L15000 1039250 348150 0.3 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
6 cutback_rib_assembled_MFalse_W0p5_L0 20250 492250 0.5 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
7 cutback_rib_assembled_MTrue_W0p5_L25000 1058750 492250 0.5 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
8 cutback_rib_assembled_MFalse_W0p5_L5000 20250 646250 0.5 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
9 cutback_rib_assembled_MTrue_W0p5_L20000 1058750 646250 0.5 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
10 cutback_rib_assembled_MFalse_W0p5_L10000 20250 800250 0.5 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
11 cutback_rib_assembled_MTrue_W0p5_L15000 1058750 800250 0.5 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
12 cutback_rib_assembled_MFalse_W0p8_L0 20400 954400 0.8 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
13 cutback_rib_assembled_MTrue_W0p8_L25000 1088000 954400 0.8 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
14 cutback_rib_assembled_MFalse_W0p8_L5000 20400 1123400 0.8 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
15 cutback_rib_assembled_MTrue_W0p8_L20000 1088000 1123400 0.8 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
16 cutback_rib_assembled_MFalse_W0p8_L10000 20400 1292400 0.8 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
17 cutback_rib_assembled_MTrue_W0p8_L15000 1088000 1292400 0.8 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
18 cutback_ridge_assembled_MFalse_W0p3_L0 20150 60150 0.3 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
19 cutback_ridge_assembled_MTrue_W0p3_L25000 1037250 60150 0.3 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
20 cutback_ridge_assembled_MFalse_W0p3_L5000 20150 203150 0.3 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
21 cutback_ridge_assembled_MTrue_W0p3_L20000 1037250 203150 0.3 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
22 cutback_ridge_assembled_MFalse_W0p3_L10000 20150 346150 0.3 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
23 cutback_ridge_assembled_MTrue_W0p3_L15000 1037250 346150 0.3 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
24 cutback_ridge_assembled_MFalse_W0p5_L0 20250 489250 0.5 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
25 cutback_ridge_assembled_MTrue_W0p5_L25000 1056750 489250 0.5 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
26 cutback_ridge_assembled_MFalse_W0p5_L5000 20250 642250 0.5 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
27 cutback_ridge_assembled_MTrue_W0p5_L20000 1056750 642250 0.5 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
28 cutback_ridge_assembled_MFalse_W0p5_L10000 20250 795250 0.5 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
29 cutback_ridge_assembled_MTrue_W0p5_L15000 1056750 795250 0.5 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
30 cutback_ridge_assembled_MFalse_W0p8_L0 20400 948400 0.8 0 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
31 cutback_ridge_assembled_MTrue_W0p8_L25000 1086000 948400 0.8 25000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
32 cutback_ridge_assembled_MFalse_W0p8_L5000 20400 1116400 0.8 5000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
33 cutback_ridge_assembled_MTrue_W0p8_L20000 1086000 1116400 0.8 20000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
34 cutback_ridge_assembled_MFalse_W0p8_L10000 20400 1284400 0.8 10000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
35 cutback_ridge_assembled_MTrue_W0p8_L15000 1086000 1284400 0.8 15000 [power_envelope] [{"n": 10, "wvl_of_interest_nm": 1550}]
def gaussian_grating_coupler_response(
    peak_power: float, center_wavelength: float, bandwidth_1dB, wavelength
):
    """Calculate the response of a Gaussian grating coupler.

    Args:
        peak_power: The peak power of the response.
        center_wavelength: The center wavelength of the grating coupler.
        bandwidth_1dB: The 1 dB bandwidth of the coupler.
        wavelength: The wavelength at which the response is evaluated.

    Returns:
    - The power of the grating coupler response at the given wavelength.
    """
    # Convert 1 dB bandwidth to standard deviation (sigma)
    sigma = bandwidth_1dB / (2 * np.sqrt(2 * np.log(10)))

    # Gaussian response calculation
    response = peak_power * np.exp(
        -0.5 * ((wavelength - center_wavelength) / sigma) ** 2
    )
    return response


nm = 1e-3
# Parameters
peak_power = 1.0
center_wavelength = 1550 * nm  # Center wavelength in micrometers
bandwidth_1dB = 100 * nm

# Wavelength range: 100 nm around the center wavelength, converted to micrometers
wavelength_range = np.linspace(center_wavelength - 0.05, center_wavelength + 0.05, 121)

# Calculate the response for each wavelength
responses = [
    gaussian_grating_coupler_response(peak_power, center_wavelength, bandwidth_1dB, wl)
    for wl in wavelength_range
]

# Plotting
plt.figure(figsize=(10, 6))
plt.plot(wavelength_range, responses)
plt.title("Gaussian Grating Coupler Response")
plt.xlabel("Wavelength (micrometers)")
plt.ylabel("Response")
plt.grid(True)
plt.show()
../../../_images/0fcb1fc22a6d9a0cf8352e8845c9c218c1b934f29bb5e815bfe53fbb1fdac431.png
widths_um = [0.3, 0.5, 0.8]
loss_dB = [3, 2.5, 1]
loss_vs_width_model = np.polyfit(widths_um, loss_dB, deg=1)
plt.plot(widths_um, loss_dB, ".")
plt.xlabel("width (um)")
plt.ylabel("loss (dB/cm)")
Text(0, 0.5, 'loss (dB/cm)')
../../../_images/4a9c83f244d491409b6375a9b0329fdbade865029ea1ae7e2e2713300bfddc64.png

Generate wafer definitions#

You can define different wafer maps for each wafer.

wafer_map

wafer_definitions = Path("wafer_definitions.json")
wafers = ["6d4c615ff105"]
dies = [
    {"x": x, "y": y}
    for y in range(-2, 3)
    for x in range(-2, 3)
    if not (abs(y) == 2 and abs(x) == 2)
]

# Wrap in a list with the wafer information
data = [
    {
        "wafer": wafer_pkey,
        "dies": dies,
        "lot_id": "lot1",
        "attributes": {"doping_n": 1e-18},
        "description": "low doping",
    }
    for wafer_pkey in wafers
]

with open(wafer_definitions, "w") as f:
    json.dump(data, f, indent=2)

Generate and write spectrums#

You can easily generate some spectrum data and add some noise to make it look like a real measurement.

cwd = Path(".")
metadata = {"measurement_type": "Spectral MEAS", "temperature": 25}
noise_peak_to_peak_dB = 1
grating_coupler_loss_dB = 3

for wafer in wafers:
    wafer_per_cent_variation = 0.20  # 20% variation
    wafer_variation_factor = 1 + wafer_per_cent_variation * (2 * np.random.rand() - 1)

    for die in dies:
        die = f"{(die['x'])}_{(die['y'])}"
        for (_, row), (_, device_row) in zip(df.iterrows(), df.iterrows()):
            cell_id = row["cell"]
            if "ridge" in cell_id:
                continue
            top_cell_id = "RibLoss" if "rib" in cell_id else "RidgeLoss"
            device_id = f"{top_cell_id}_{cell_id}_{device_row['x']}_{device_row['y']}"
            dirpath = cwd / wafer / die / device_id
            dirpath.mkdir(exist_ok=True, parents=True)
            data_file = dirpath / "data.json"
            metadata_file = dirpath / "attributes.json"
            metadata_file.write_text(json.dumps(metadata))
            loss_dB = (
                np.polyval(loss_vs_width_model, row["width_um"])
                * row["length_um"]
                * 1e-6
                * 1e2
            )  # convert um to cm
            loss_dB += 2 * grating_coupler_loss_dB
            peak_power = 10 ** (-loss_dB / 10)
            device_per_cent_variation = 0.05  # 5% variation
            device_variation_factor = 1 + device_per_cent_variation * (
                2 * np.random.rand() - 1
            )
            peak_power *= wafer_variation_factor * device_variation_factor

            output_power = [
                gaussian_grating_coupler_response(
                    peak_power, center_wavelength, bandwidth_1dB, wl
                )
                for wl in wavelength_range
            ]
            output_power *= 10 ** (
                noise_peak_to_peak_dB * np.random.rand(len(wavelength_range)) / 10
            )
            # output_power_dB = 10*np.log10(output_power)
            d = {
                "wavelength": wavelength_range * 1e3,
                "output_power": output_power,
                "polyfit": loss_dB,
            }
            data = pd.DataFrame(d)
            json_converted_file = data.reset_index(drop=True).to_dict(orient="split")
            json.dump(
                json_converted_file,
                open(data_file.with_suffix(".json"), "w+"),
                indent=2,
            )
dirpath
PosixPath('6d4c615ff105/1_2/RibLoss_cutback_rib_assembled_MTrue_W0p8_L15000_1088000_1292400')
plt.plot(wavelength_range, output_power)
plt.title(dirpath.stem)
plt.ylabel("Power (mW)")
plt.xlabel("wavelength (um)")
Text(0.5, 0, 'wavelength (um)')
../../../_images/75465c09d86ee56d4aa21aac34478f1523eb95dd621416c0982307c75c646452.png
f"{len(list(dirpath.parent.glob('*/*.json')))//2} measurements"
'18 measurements'
dirpath
PosixPath('6d4c615ff105/1_2/RibLoss_cutback_rib_assembled_MTrue_W0p8_L15000_1088000_1292400')
f"{len(list(dirpath.parent.parent.glob('*')))} dies"
'21 dies'