PathLength

class ipkiss3.all.PathLength

Calculates the path length between two ports.

PathLength extracts a netlist from the layout and builds a connection graph of the circuit, allowing you to trace the paths between source and target ports. You can then calculate the total length of each of these paths.

By default, the trace_length method will be invoked to calculate the length of a component. If no trace_length method is available the component will be ignored.

The calculation of the path length can be customized by registering custom implementations.

Parameters:
layout: LayoutView or Layout

LayoutView or Layout of the circuit to analyze.

source: str

Identifier of the start port.

target: str

Identifier of the end port.

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)

length = i3.PathLength(layout).trace("opt1", "opt2").total_length()
# outputs: 81.4155365094152
print(length)

@i3.PathLength.register
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    return 20.0

length2 = i3.PathLength(layout).trace("opt1", "opt2").total_length()
# outputs: 121.4155365094152
print(length2)
assert length2 == length + 2 * 20.0

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

report = i3.PathLength(layout).trace("opt1", "opt2").report()
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/ipkiss3-all-PathLength-1.png
import math
import si_fab.all as pdk
import ipkiss3.all as i3

@i3.PathLength.register
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.PathLength(temp_layout).trace("dc1:in1", "dc2:out1").total_length()

            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.PathLength(layout).trace("dc1:in1", "dc2:out1").total_length()
            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/ipkiss3-all-PathLength-2.png
classmethod register(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.PathLength.register
def mmi_length(layout: pdk.MMI1x2.Layout, source, target):
    return 20.0
import si_fab.all as pdk

@i3.PathLength.register
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.PathLength.register
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})
trace(source, target)

Derives the traces between source and target.

Parameters:
source: str

Source Port identifier e.g. “porta” or “instA:port1”.

target: str

Target Port identifier e.g. “porta” or “instA:port1”.

Returns:
ipkiss3.pcell.layout.path_length_tracer.PathLengthTraces