2.3. Controlling the waveguides length

Being able to control the shape of the right arm of the MZI by changing the position of the through point is useful, but it is not an efficient way to control the length of this arm. Because the MZI is an interferometric structure, having good control over the length difference (called delay length) between the left and right arms is very important.

2.3.1. Function to control the delay length

Let’s see how to define a function in IPKISS, using Python code, that allows us to automatically extract the coordinates of the through point based on the desired delay length we want to achieve.

Listing 2.28 luceda-academy/training/topical_training/siepic_mzi_dc_sweep/design_variations_length.py
def get_tp_from_delay_length(delay_length, bend_radius, through_point_y):
    def f(x):
        device = MZI(
            through_point=(x[0], through_point_y),
            bend_radius=bend_radius,
        )
        right_arm_length = device.get_connector_instances()[1].reference.trace_length()
        left_arm_length = device.get_connector_instances()[0].reference.trace_length()
        current_delay_length = right_arm_length - left_arm_length
        cost = current_delay_length - delay_length
        return np.abs(cost)

    from scipy.optimize import minimize
    through_point_x = minimize(f, x0=np.array(70.0), tol=1e-2, bounds=((45.0, 200.0), )).x[0]
    return through_point_x, through_point_y

Let’s have a detailed look at the code:

  1. We provide three arguments to the function: the desired delay length between the two MZI arms, the bend radius to be used for the waveguides (it affects the waveguide length) and the y-coordinate of the through point. The function will take care of finding the x-coordinate that satisfies these conditions.
  2. Inside this function, we define another function f(x) that returns the value we want to minimize. In this case, we instantiate an MZI, calculate the length difference between the two arms and compare this value with the desired delay length. The difference between these two values is the value we want to minimize (cost). When the difference approaches zero then we have obtained the delay length we wanted.
  3. Next, we use the minimize method from SciPy (Python scientific library) to find the value x, which in our case is the x-coordinate of the through point, that allows to minimize the function f(x). In other words, we want to find the x-coordinate that corresponds to the desired delay length, given the desired bend radius. We provide an initial guess for the value x, which is x0. We also provide bounds, so that the waveguide arm doesn’t intersect with the rest of the circuit.

2.3.2. Design variations controlling the delay length

Using the get_tp_frpm_delay_length function, we can now create design variations controlling the delay length. The code is very similar to what you’ve used in the previous section, but instead of providing a list of through points as parameter for the MZI sweep, we provide a list of delay lengths.

When using a for-loop to create the MZI sweep, we iterate over the values contained in delay_lengths. We first use the get_tp_from_delay_length function to extract the coordinates of the through point that corresponds to that delay length, and then use this information to instantiate the MZI.

Listing 2.29 luceda-academy/training/topical_training/siepic_mzi_dc_sweep/design_variations_length.py
# Create the MZI sweep
for ind, delay_length in enumerate(delay_lengths):
    tp = get_tp_from_delay_length(
        delay_length=delay_length,
        bend_radius=bend_radius,
        through_point_y=240.0,
    )

    # Instantiate the MZI
    mzi = MZI(
        name="MZI{}".format(ind),
        through_point=tp,
        bend_radius=bend_radius
    )

    # Calculate the actual delay length and print the results
    right_arm_length = mzi.get_connector_instances()[1].reference.trace_length()
    left_arm_length = mzi.get_connector_instances()[0].reference.trace_length()
    actual_delay_length = right_arm_length - left_arm_length

    print(
        mzi.name,
        "Desired delay length = {} um".format(delay_length),
        "Actual delay length = {} um".format(actual_delay_length),
        "Through point = {}".format(tp)
    )

    # Add the MZI to the child cells dict and place it
    mzi_cell_name = "mzi{}".format(ind)
    child_cells[mzi_cell_name] = mzi
    place_specs.append(i3.Place(mzi_cell_name, (x0, y0)))

    x0 += spacing_x

You can see that in the for-loop we added a print statement that prints the value of delay length with the associated coordinate of the through point. Next time you want to create and simulate this circuit, you don’t need to run the minimization algorithm anymore. You can use the code from the previous section, provide the coordinates of the through points, and you will already know to what delay lengths they correspond.

2.3.3. Layout and circuit simulation

Below are the layout and circuit simulation for delay_lengths=[(60.0, 120.0, 240.0)] and bend_radius=5.0.

Mach-Zehnder interferometer
Mach-Zehnder interferometer

2.3.4. Test your knowledge

The way we have defined the function to relate a delay length to the coordinate of a through point only accounts for the existence of one through point. If you would like to create more complex routes, or if the delay length you would like to use makes your right MZI arm very long, you may consider using more than one through point.

As an exercise, you may try to write a function where you can use more than one through point. Don’t forget to adapt the through_point property of the MZI PCell as well, so it accepts a list of through points.