Path Length Extraction

The i3.path_length function enables length extraction.

The i3.path_report function enables extracting a report of all components, paths, and lengths between the specified ports.

ipkiss3.all.path_length(layout, port1, port2)

Get the path length between two ports. If multiple paths are present, it returns the length of the shortest path.

To calculate the length, the trace_length method of each component will be invoked to calculate the length of that component. If no trace_length method is available the component will be ignored.

The calculation of the path length can be customized by registering a custom implementation for the length of a component with the i3.register_length decorator.

Parameters:
layout: LayoutView or Layout or InstanceDict

Circuit to analyze

port1: str

The name of the first port

port2: str

The name of the second port

Returns:
float

The length of the path

Examples

import si_fab.all as pdk
import ipkiss3.all as i3

class Circuit(i3.PCell):
    class Layout(i3.LayoutView):
        def generate(self, layout):
            mmi = pdk.MMI1x2()
            wg = i3.Waveguide().Layout(
                shape=[
                    (0, 0),
                    (20, 0),
                ]
            )
            layout += i3.place_and_route(
                insts={
                    "wg": wg,
                    "mmi1": mmi,
                    "mmi2": mmi,
                },
                specs=[
                    i3.Join("wg:out", "mmi1:in1"),
                    i3.ConnectManhattan("mmi1:out1", "mmi2:out1"),
                    i3.ConnectManhattan("mmi1:out2", "mmi2:out2", control_points=[i3.H(15.0)]),
                    i3.FlipH("mmi2"),
                    i3.Place("mmi2:in1", (100, -10)),
                ],
            )
            layout += i3.expose_ports(layout, {"wg:in": "opt1", "mmi2:in1": "opt2"})
            return layout

layout = Circuit().Layout()
layout.visualize(annotate=True)

length1 = i3.path_length(layout, "opt1", "opt2")
print(length1)  # outputs: 81.4155365094152 (MMI lengths not included)

@i3.register_length  # register a custom length for the MMI
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    return 20.0

length2 = i3.path_length(layout, "opt1", "opt2")
print(length2)  # outputs: 121.4155365094152 (MMI lengths included)
assert length2 == length1 + 2 * 20.0
../../../_images/path-length-1.png
import math
import si_fab.all as pdk
import ipkiss3.all as i3

@i3.register_length
def dc_length(layout: pdk.SiDirectionalCouplerS.Layout, source, target):
    # Extract the length from the directional coupler's waveguides
    top, bottom = layout._get_wg_shapes()
    return top.length()

class Circuit(i3.PCell):
    desired_length = i3.PositiveNumberProperty(default=140.0)

    class Layout(i3.LayoutView):
        def generate(self, layout):
            dc = pdk.SiDirectionalCouplerS()
            y_control = 20
            temp_layout = i3.place_and_route(
                insts={
                    "dc1": dc,
                    "dc2": dc,
                },
                specs=[
                    i3.Place("dc2", (40, 0)),
                    i3.ConnectManhattan("dc1:out1", "dc2:in1", control_points=[i3.H(-y_control)]),
                ],
            )
            length = i3.path_length(temp_layout, "dc1:in1", "dc2:out1")

            y_control = y_control + (self.desired_length - length) / 2
            layout += i3.place_and_route(
                insts={
                    "dc1": dc,
                    "dc2": dc,
                },
                specs=[
                    i3.Place("dc2", (40, 0)),
                    i3.ConnectManhattan("dc1:out1", "dc2:in1", control_points=[i3.H(-y_control)]),
                    i3.ConnectManhattan("dc1:out2", "dc2:in2"),
                ],
            )
            length = i3.path_length(layout, "dc1:in1", "dc2:out1")
            assert math.isclose(length, self.desired_length)  # Check if the final arm length is correct
            return layout

layout = Circuit(desired_length=140.0).Layout()
layout.visualize()
../../../_images/path-length-2.png
ipkiss3.all.path_report(layout, port1, port2)

Outputs a report of all components, paths, and length calculations (results) between the specified ports.

The calculation of the path length can be customized by registering a custom implementation for the length of a component with the i3.register_length decorator.

Parameters:
layout: LayoutView or Layout or InstanceDict

Circuit to analyze

port1: str

The name of the first port

port2: str

The name of the second port

Returns:
dict

A dictionary where each key is a string identifier for a unique trace (e.g., ‘trace0’). The value is another dictionary containing the detailed breakdown of that trace with the following keys:

  • ‘components’ (list): An ordered list of the IPKISS PCell layout objects along the trace.

  • ‘path’ (list): A list of strings describing the port-to-port connections for each component in the format ‘(start_port, end_port)@instance_name’.

  • ‘results’ (list): A list of floats, where each float is the calculated optical path length (in micrometers) corresponding to each component in ‘components’.

Examples

import si_fab.all as pdk
import ipkiss3.all as i3

class Circuit(i3.PCell):
    class Layout(i3.LayoutView):
        def generate(self, layout):
            mmi = pdk.MMI1x2()
            wg = i3.Waveguide().Layout(
                shape=[
                    (0, 0),
                    (20, 0),
                ]
            )
            layout += i3.place_and_route(
                insts={
                    "wg": wg,
                    "mmi1": mmi,
                    "mmi2": mmi,
                },
                specs=[
                    i3.Join("wg:out", "mmi1:in1"),
                    i3.ConnectManhattan("mmi1:out1", "mmi2:out1"),
                    i3.ConnectManhattan("mmi1:out2", "mmi2:out2", control_points=[i3.H(15.0)]),
                    i3.FlipH("mmi2"),
                    i3.Place("mmi2:in1", (100, -10)),
                ],
            )
            layout += i3.expose_ports(layout, {"wg:in": "opt1", "mmi2:in1": "opt2"})
            return layout

layout = Circuit().Layout()
layout.visualize(annotate=True)

@i3.register_length  # register a custom length for the MMI
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    return 20.0

# Print a report of all components, paths, and length calculations (results) between "opt1" and "opt2".

report = i3.path_report(layout, "opt1", "opt2")
print(report)

print(report["trace0"]["results"])
# outputs: [20.0, 20.0, 61.415536509415205, 20.0]

print(report["trace1"]["results"])
# outputs: [20.0, 20.0, 65.41553650941526, 20.0]
../../../_images/path-length-3.png

Registering a custom length

A custom implementation of the length calculation can be registered with i3.register_length.

ipkiss3.all.register_length(func)

Decorator to register a custom implementation to extract the length.

Parameters:
func: Callable

Function to extract the length.

Examples

import si_fab.all as pdk

@i3.register_length
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    return 20.0
import si_fab.all as pdk

@i3.register_length
def dc_length(layout: pdk.SiDirectionalCouplerS.Layout, source, target):
    # Extract the length from the directional coupler's waveguides
    # This way the returned length varies depending on the actual DC length
    top, bottom = layout._get_wg_shapes()
    return top.length()
import si_fab.all as pdk

@i3.register_length
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    # use python's match/case statements
    # to declare the lengths between the port pairs
    match (source, target):
        case ("in1", "out1") | ("out1" | "in1"):
            return 20.0
        case ("in1", "out2") | ("out2" | "in1"):
            return 20.0
        case _:
            raise LookupError(f"unknown port pair ({source}, {target})