Routing#

Optical and high-speed RF ports have specific orientation requirements to ensure that routes avoid sharp turns, which can cause signal reflections.

We offer routing functions tailored for these requirements:

  • route_single: Routes a single connection between two ports.

  • route_bundle: Routes multiple connections between two port groups using a bundle/river/bus router. It also accommodates waypoints and routing steps.

The most versatile function is route_bundle, as it handles both single and grouped routes with length matching, ensuring all routes are of equal length.

import gdsfactory as gf
from gdsfactory.component import Component
from gdsfactory.port import Port
c = gf.Component()
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((100, 50))
c
../_images/eaea4916a9980065626104f6668ebbe798dbf5e52ad50da496c44ef11f21be81.png

route_single#

route_single returns a Manhattan route between 2 ports

help(gf.routing.route_single)
Help on function route_single in module gdsfactory.routing.route_single:

route_single(component: 'Component', port1: 'Port', port2: 'Port', cross_section: 'CrossSectionSpec | None' = None, layer: 'LayerSpec | None' = None, bend: 'ComponentSpec' = 'bend_euler', straight: 'ComponentSpec' = 'straight', start_straight_length: 'float' = 0.0, end_straight_length: 'float' = 0.0, waypoints: 'WayPoints | None' = None, steps: "Sequence[Mapping[Literal['x', 'y', 'dx', 'dy'], int | float]] | None" = None, port_type: 'str | None' = None, allow_width_mismatch: 'bool' = False, radius: 'float | None' = None, route_width: 'float | None' = None, auto_taper: 'bool' = True) -> 'ManhattanRoute'
    Returns a Manhattan Route between 2 ports.
    
    The references are straights, bends and tapers.
    `route_single` is an automatic version of `route_single_from_steps`.
    
    Args:
        component: to place the route into.
        port1: start port.
        port2: end port.
        cross_section: spec.
        layer: layer spec.
        bend: bend spec.
        straight: straight spec.
        start_straight_length: length of starting straight.
        end_straight_length: length of end straight.
        waypoints: optional list of points to pass through.
        steps: optional list of steps to pass through.
        port_type: port type to route.
        allow_width_mismatch: allow different port widths.
        radius: bend radius. If None, defaults to cross_section.radius.
        route_width: width of the route in um. If None, defaults to cross_section.width.
        auto_taper: add auto tapers.
    
    .. plot::
        :include-source:
    
        import gdsfactory as gf
    
        c = gf.Component()
        mmi1 = c << gf.components.mmi1x2()
        mmi2 = c << gf.components.mmi1x2()
        mmi2.dmove((40, 20))
        gf.routing.route_single(c, mmi1.ports["o2"], mmi2.ports["o1"], radius=5, cross_section="strip")
        c.plot()
c = gf.Component()
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((100, 50))
route = gf.routing.route_single(
    c,
    port1=mmi1.ports["o2"],
    port2=mmi2.ports["o1"],
    cross_section=gf.cross_section.strip,
)
c
../_images/4e9180f80cc9d74264bd1d5ea34f802fd3d3c16c2bc6cf5d47c0a31e969a4381.png
route
ManhattanRoute(backbone=[15500,625, 25500,625, 25500,50000, 90000,50000], start_port=Port(, dwidth: 0.5, trans: r180 *1 15.5,0.625, layer: WG (1/0), port_type: optical), end_port=Port(, dwidth: 0.5, trans: r0 *1 90,50, layer: WG (1/0), port_type: optical), instances=[Unnamed_4: ports ['o1', 'o2'], bend_euler_R10_A90_P0p5_2f1f5c6d: ports ['o1', 'o2'], 0 instances, Unnamed_4: ports ['o1', 'o2'], straight_L29p375_N2_CSs_6ed2bfdd: ports ['o1', 'o2'], 0 instances, Unnamed_4: ports ['o1', 'o2'], bend_euler_R10_A90_P0p5_2f1f5c6d: ports ['o1', 'o2'], 0 instances, Unnamed_4: ports ['o1', 'o2'], straight_L54p5_N2_CSstrip_WNone: ports ['o1', 'o2'], 0 instances], n_bend90=2, n_taper=0, bend90_radius=10000, taper_length=0, length=83875, length_straights=83875, polygons={})

⚠️ Note: You can also get the route length, but keep the following in mind:

  • The route length is in DBU (Database Units). Usually, 1 DBU = 1 nm.

  • It only accounts for straight segments, not bends.

For the total length, including bends and straights, you can use Component.info['length'] or Instance.cell.info['length]

print(f"route length = {route.length} DBU, {route.length/1000} um")
route length = 83875 DBU, 83.875 um
route_length = 0
for instance in route.instances:
    route_length += instance.info['length']

print(f"total route length = {route_length} um")
total route length = 117.149 um
c = gf.Component()
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((100, 50))
route = gf.routing.route_single(
    c, port1=mmi1.ports["o2"], port2=mmi2.ports["o1"], layer=(1, 0), route_width=2
)
c
../_images/c9ed55ed63e0f9662882b21cb5136f2ef09285d8b4db6ffbd12f5e0f204c7e28.png

Problem: route_single with obstacles

sometimes there are obstacles that connect strip does not see!

c = gf.Component()
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((110, 50))
x = c << gf.components.cross(length=20)
x.move((135, 20))
route = gf.routing.route_single(
    c, mmi1.ports["o2"], mmi2.ports["o2"], cross_section="strip"
)
c
../_images/171cc7062424072d37aa6ec95808329bf5907eb225fdc1c4d1b729d7b020a49e.png

Solution: specify the route steps

route_single allows you to define relative or absolute steps x or y for the route as well as with increments dx or dy.

c = gf.Component()
w = gf.components.straight()
left = c << w
right = c << w
right.move((100, 80))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 25

port1 = left.ports["o2"]
port2 = right.ports["o2"]

routes = gf.routing.route_single(
    c,
    port1=port1,
    port2=port2,
    cross_section="strip",
    steps=[
        {"x": 20, "y": 0},
        {"x": 20, "y": 20},
        {"x": 120, "y": 20},
        {"x": 120, "y": 80},
    ],
)
c
../_images/28a0e49ac9c9d710798d1fccc47be9ef1b43c19d3349ee481b3cac978f48609a.png
c = gf.Component()
w = gf.components.straight()
left = c << w
right = c << w
right.move((100, 80))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 25

port1 = left.ports["o2"]
port2 = right.ports["o2"]

routes = gf.routing.route_single(
    c,
    port1=port1,
    port2=port2,
    cross_section="strip",
    steps=[
        {"x": 20},
        {"y": 20},
        {"x": 120},
        {"y": 80},
    ],
)
c
../_images/28a0e49ac9c9d710798d1fccc47be9ef1b43c19d3349ee481b3cac978f48609a.png

route_bundle#

To route groups of ports avoiding waveguide collisions, you should use route_bundle instead of route_single.

route_bundle uses a river/bundle/bus router.

At the moment it works only when each group of ports have the same orientation.

ys_right = [0, 10, 20, 40, 50, 80]
pitch = 127.0
N = len(ys_right)
ys_left = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

right_ports = [
    gf.Port(f"R_{i}", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)
    for i in range(N)
]
left_ports = [
    gf.Port(f"L_{i}", center=(-200, ys_left[i]), width=0.5, orientation=0, layer=layer)
    for i in range(N)
]

# you can also mess up the port order and it will sort them by default
left_ports.reverse()

c = gf.Component()
routes = gf.routing.route_bundle(
    c,
    left_ports,
    right_ports,
    start_straight_length=50,
    sort_ports=True,
    cross_section="strip",
)
c.add_ports(left_ports)
c.add_ports(right_ports)
c
../_images/aecffb83cac9479f8f1044f59e8497949f7d187bef6babda4509bfe4bed1d593.png
xs_top = [0, 10, 20, 40, 50, 80]
pitch = 127.0
N = len(xs_top)
xs_bottom = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

top_ports = [
    gf.Port(f"top_{i}", center=(xs_top[i], 0), width=0.5, orientation=270, layer=layer)
    for i in range(N)
]

bot_ports = [
    gf.Port(
        f"bot_{i}",
        center=(xs_bottom[i], -300),
        width=0.5,
        orientation=90,
        layer=layer,
    )
    for i in range(N)
]

c = gf.Component()
routes = gf.routing.route_bundle(
    c,
    top_ports,
    bot_ports,
    separation=5.0,
    end_straight_length=100,
    cross_section="strip",
)
c
../_images/39ee24b42eca32fcd8cdb34154c9300bf16566c1099f5d432cc8ae4c12ce6e67.png

route_bundle can also route bundles through corners

@gf.cell(cache={})
def test_connect_corner(N=6, config="A"):
    d = 10.0
    sep = 5.0
    c = gf.Component()
    layer = (1, 0)

    if config in ["A", "B"]:
        a = 100.0
        ports_A_TR = [
            Port(
                f"A_TR_{i}",
                center=(d, a / 2 + i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_TL = [
            Port(
                f"A_TL_{i}",
                center=(-d, a / 2 + i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BR = [
            Port(
                f"A_BR_{i}",
                center=(d, -a / 2 - i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BL = [
            Port(
                f"A_BL_{i}",
                center=(-d, -a / 2 - i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]

        ports_B_TR = [
            Port(
                f"B_TR_{i}",
                center=(a / 2 + i * sep, d),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_TL = [
            Port(
                f"B_TL_{i}",
                center=(-a / 2 - i * sep, d),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BR = [
            Port(
                f"B_BR_{i}",
                center=(a / 2 + i * sep, -d),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BL = [
            Port(
                f"B_BL_{i}",
                center=(-a / 2 - i * sep, -d),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]

    elif config in ["C", "D"]:
        a = N * sep + 2 * d
        ports_A_TR = [
            Port(
                f"A_TR_{i}",
                center=(a, d + i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_TL = [
            Port(
                f"A_TL_{i}",
                center=(-a, d + i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BR = [
            Port(
                f"A_BR_{i}",
                center=(a, -d - i * sep),
                width=0.5,
                orientation=0,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A_BL = [
            Port(
                f"A_BL_{i}",
                center=(-a, -d - i * sep),
                width=0.5,
                orientation=180,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_A = [ports_A_TR, ports_A_TL, ports_A_BR, ports_A_BL]

        ports_B_TR = [
            Port(
                f"B_TR_{i}",
                center=(d + i * sep, a),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_TL = [
            Port(
                f"B_TL_{i}",
                center=(-d - i * sep, a),
                width=0.5,
                orientation=90,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BR = [
            Port(
                f"B_BR_{i}",
                center=(d + i * sep, -a),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B_BL = [
            Port(
                f"B_BL_{i}",
                center=(-d - i * sep, -a),
                width=0.5,
                orientation=270,
                layer=layer,
            )
            for i in range(N)
        ]

        ports_B = [ports_B_TR, ports_B_TL, ports_B_BR, ports_B_BL]

    if config in ["A", "C"]:
        for ports1, ports2 in zip(ports_A, ports_B):
            gf.routing.route_bundle(
                c, ports1, ports2, radius=5, sort_ports=True, cross_section="strip"
            )

    elif config in ["B", "D"]:
        for ports1, ports2 in zip(ports_A, ports_B):
            gf.routing.route_bundle(
                c, ports2, ports1, radius=5, sort_ports=True, cross_section="strip"
            )

    return c


c = test_connect_corner(config="A")
c
../_images/0d9ce8bd9791f4548168af3c21a28784665b9617df6f6f39dfc146d9be5948e8.png
c = test_connect_corner(config="C")
c
../_images/cfea2260e2eb08932c93f6090f57cee0b48a2984c4453db5780681c03cc17d1a.png
@gf.cell(cache={})
def test_connect_bundle_udirect(dy=200, orientation=270, layer=(1, 0)):
    xs1 = [-100, -90, -80, -55, -35, 24, 0] + [200, 210, 240]
    axis = "X" if orientation in [0, 180] else "Y"
    pitch = 10.0
    N = len(xs1)
    xs2 = [70 + i * pitch for i in range(N)]

    if axis == "X":
        ports1 = [
            Port(
                f"top_{i}",
                center=(0, xs1[i]),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bottom_{i}",
                center=(dy, xs2[i]),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

    else:
        ports1 = [
            Port(
                f"top_{i}",
                center=(xs1[i], 0),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bottom_{i}",
                center=(xs2[i], dy),
                width=0.5,
                orientation=orientation,
                layer=layer,
            )
            for i in range(N)
        ]

    c = Component()
    gf.routing.route_bundle(
        c, ports1, ports2, radius=10.0, sort_ports=True, cross_section="strip"
    )
    return c


c = test_connect_bundle_udirect()
c
../_images/9a901949219dbd823c819f4bb8a849eca0774e029e184e648e150af2c07bb17b.png
@gf.cell
def test_connect_bundle_u_indirect(dy=-200, orientation=180, layer=(1, 0)):
    xs1 = [-100, -90, -80, -55, -35] + [200, 210, 240]
    axis = "X" if orientation in [0, 180] else "Y"
    pitch = 10.0
    N = len(xs1)
    xs2 = [50 + i * pitch for i in range(N)]

    a1 = orientation
    a2 = a1 + 180

    if axis == "X":
        ports1 = [
            Port(f"top_{i}", center=(0, xs1[i]), width=0.5, orientation=a1, layer=layer)
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bot_{i}",
                center=(dy, xs2[i]),
                width=0.5,
                orientation=a2,
                layer=layer,
            )
            for i in range(N)
        ]

    else:
        ports1 = [
            Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
            for i in range(N)
        ]

        ports2 = [
            Port(
                f"bot_{i}",
                center=(xs2[i], dy),
                width=0.5,
                orientation=a2,
                layer=layer,
            )
            for i in range(N)
        ]

    c = Component()
    gf.routing.route_bundle(
        c,
        ports1,
        ports2,
        bend=gf.components.bend_euler,
        radius=5,
        cross_section="strip",
    )

    return c


c = test_connect_bundle_u_indirect(orientation=0)
c
../_images/ee4817bf020b045a3197939a617afb6516ac50578f4e783f93dbcf4c3d54adab.png
@gf.cell
def test_north_to_south(layer=(1, 0)):
    dy = 200.0
    xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

    pitch = 10.0
    N = len(xs1)
    xs2 = [-20 + i * pitch for i in range(N // 2)]
    xs2 += [400 + i * pitch for i in range(N // 2)]

    a1 = 90
    a2 = a1 + 180

    ports1 = [
        gf.Port(f"top_{i}", center=(xs1[i], 0), width=0.5, orientation=a1, layer=layer)
        for i in range(N)
    ]

    ports2 = [
        gf.Port(f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=layer)
        for i in range(N)
    ]

    c = gf.Component()
    gf.routing.route_bundle(c, ports1, ports2, cross_section="strip")
    return c


c = test_north_to_south()
c
../_images/0fd8e659665f8ce418a6b26df5a5d9eb921a3adec2256fc63780519cb87b90a8.png
@gf.cell
def demo_connect_bundle():
    """Combines all the connect_bundle tests."""
    y = 400.0
    x = 500
    y0 = 900
    dy = 200.0
    c = gf.Component()
    for j, s in enumerate([-1, 1]):
        for i, orientation in enumerate([0, 90, 180, 270]):
            ref = c << test_connect_bundle_u_indirect(
                dy=s * dy, orientation=orientation
            )
            ref.dcenter = (i * x, j * y)

            ref = c << test_connect_bundle_udirect(dy=s * dy, orientation=orientation)
            ref.dcenter = (i * x, j * y + y0)

    for i, config in enumerate(["A", "B", "C", "D"]):
        ref = c << test_connect_corner(config=config)
        ref.dcenter = (i * x, 1700)

    return c


c = demo_connect_bundle()
c.show()
c
../_images/d2e3be400100c4f26b3e1fcb70f385e2bef6acc6ba41ad6289a78566091eabd0.png
import gdsfactory as gf
c = gf.Component()
c1 = c << gf.components.mmi2x2()
c2 = c << gf.components.mmi2x2()

c2.move((100, 50))
routes = gf.routing.route_bundle(
    c,
    [c1.ports["o4"], c1.ports["o3"]],
    [c2.ports["o1"], c2.ports["o2"]],
    radius=5,
    cross_section="strip",
)
c
../_images/f75a8a55f00e3bca8f6ae53aea732421f8642491e089e95a3ad5bbe9148e0509.png
c = gf.Component()
c1 = c << gf.components.pad()
c2 = c << gf.components.pad()
c2.move((200, 100))
routes = gf.routing.route_bundle_electrical(
    c,
    [c1.ports["e3"]],
    [c2.ports["e1"]],
    cross_section=gf.cross_section.metal3,
)
c
../_images/569d0b124aae027c53e7e01ebebfafb18ee2a7564f2799f49192b271e19216a5.png

Problem

Sometimes 90 degrees routes do not have enough space for a Manhattan route

c = gf.Component()
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
c
../_images/ce4567c53c45050a0a91216a903f4266d986a9bd83b961ef00068593e3044c1f.png
c = gf.Component()
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
routes = gf.routing.route_bundle(
    c,
    list(c1.ports.filter(orientation=0)),
    list(c2.ports.filter(orientation=180)),
    on_collision=None,
    cross_section="strip",
)
c
../_images/24f54c4eaeff8f40e09df3e577cd9183ce558c448af6eb60fb1164a07ad5f460.png
c = gf.Component()
pitch = 2.0
ys_left = [0, 10, 20]
N = len(ys_left)
ys_right = [(i - N / 2) * pitch for i in range(N)]
layer = (1, 0)

right_ports = [
    gf.Port(f"R_{i}", center=(0, ys_right[i]), width=0.5, orientation=180, layer=layer)
    for i in range(N)
]
left_ports = [
    gf.Port(f"L_{i}", center=(-50, ys_left[i]), width=0.5, orientation=0, layer=layer)
    for i in range(N)
]
left_ports.reverse()
routes = gf.routing.route_bundle(
    c, right_ports, left_ports, radius=5, on_collision=None, cross_section="strip"
)

c
../_images/2ff984ada8a40043674a2ea3267aa5da154d51dfeb37814e61beb2e2287ae8bd.png

Solution

Add Sbend routes using route_bundle_sbend

c = gf.Component()
c1 = c << gf.components.nxn(east=3, ysize=20)
c2 = c << gf.components.nxn(west=3)
c2.move((80, 0))
routes = gf.routing.route_bundle_sbend(
    c,
    c1.ports.filter(orientation=0),
    c2.ports.filter(orientation=180),
    enforce_port_ordering=False,
)
c
../_images/81173a6cc98d3362290c3f4f5a060cf696cafec8f76d82231a049ba86cfff22c.png
import gdsfactory as gf
from gdsfactory.samples.big_device import big_device

c = gf.Component()
c1 = big_device()
c2 = gf.routing.add_fiber_array(c1)
c2.plot()
../_images/c9f4916b11d24275134e669a8a3b6ad1e5a2d049e6a4125a6ee1ecc51ae66ec6.png
c = gf.Component()
w = gf.components.straight()
left = c << w
right = c << w
right.move((100, 80))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 25

port1 = left.ports["o2"]
port2 = right.ports["o2"]

routes = gf.routing.route_bundle(
    c,
    [port1],
    [port2],
    cross_section="strip",
    steps=[
        {"dy": 30, "dx": 50},
        {"dx": 100},
    ],
)
c
../_images/4af836465051c85a71a8e2d2d3be3b63470e08e107efe158b7422231f768788c.png
c = gf.Component()
w = gf.components.array(gf.c.straight, columns=1, rows=3, row_pitch=3)
left = c << w
right = c << w
right.move((100, 100))

obstacle = gf.components.rectangle(size=(100, 10))
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle1.ymin = 40
obstacle2.xmin = 35

ports1 = left.ports.filter(orientation=0)
ports2 = right.ports.filter(orientation=180)

routes = gf.routing.route_bundle(
    c,
    ports1,
    ports2,
    cross_section="strip",
    sort_ports=True,
    steps=[
        {"dy": 30, "dx": 50},
        {"dx": 90},
    ],
)
c
../_images/1b7b5a91a531bfa7579e1c143bf47456b3ba449347fc7f1fd4e28426d9334bcf.png

route_astar#

You can navigate around bounding boxes when routing if you pass the bboxes of all the objects that you want to avoid.

Currently the router only respects any (merged) bounding boxes which overlap with start or end port bundles

import gdsfactory as gf

c = gf.Component()
cross_section = "strip"
port_prefix = "o"
bend = gf.components.bend_euler

cross_section = gf.get_cross_section(cross_section, radius=5)
w = gf.components.straight(cross_section=cross_section)
left = c << w
right = c << w
right.rotate(90)
right.move((168, 63))

obstacle = gf.components.rectangle(size=(250, 3), layer="M2")
obstacle1 = c << obstacle
obstacle2 = c << obstacle
obstacle3 = c << obstacle
obstacle4 = c << obstacle
obstacle4.rotate(90)
obstacle1.ymin = 50
obstacle1.xmin = -10
obstacle2.xmin = 35
obstacle3.ymin = 42
obstacle3.xmin = 72.23
obstacle4.xmin = 200
obstacle4.ymin = 55
port1 = left.ports[f"{port_prefix}1"]
port2 = right.ports[f"{port_prefix}2"]

route = gf.routing.route_astar(
    component=c,
    port1=port1,
    port2=port2,
    cross_section=cross_section,
    resolution=15,
    distance=12,
    avoid_layers=("M2",),
    bend=bend,
)
c
../_images/b683cf619fa535d420abbdd05a1d3337d0d5c35ef73c6cc86d19c0b814cbdc42.png

route_bundle with collisions#

The route bundle with collision avoidance is not yet supported.

import gdsfactory as gf

c = gf.Component()
columns = 2
ptop = c << gf.components.pad_array(columns=columns, port_orientation=270)
pbot = c << gf.components.pad_array(port_orientation=270, columns=columns)
ptop.movex(300)
ptop.movey(300)

obstacle = c << gf.c.rectangle(size=(300, 100), layer="M3")
obstacle.ymin = pbot.ymax - 10
obstacle.xmin = pbot.xmax - 10


routes = gf.routing.route_bundle_electrical(
    c,
    pbot.ports,
    ptop.ports,
    start_straight_length=100,
    separation=20,
    cross_section="metal_routing",
    bboxes=[
        obstacle.bbox(),
        pbot.bbox(),
        ptop.bbox(),
    ],  # obstacles to avoid
    sort_ports=True,
)

c
../_images/efbf27812037715de1555523559591ca3ec97f6a0be8ba5ac3d18e4f485c0215.png
import gdsfactory as gf

c = gf.Component()
columns = 2
ptop = c << gf.components.pad_array(columns=columns, port_orientation=270)
pbot = c << gf.components.pad_array(port_orientation=270, columns=columns)
ptop.movex(300)
ptop.movey(300)

obstacle = c << gf.c.rectangle(size=(300, 100), layer="M3", centered=True)
obstacle.ymin = pbot.ymax - 10
obstacle.xmin = pbot.xmax + 10

c2 = gf.Component()  # create a dummy component to get the size of the obstacle
obstacle_sized = c2 << gf.c.rectangle(size=(340, 140), layer="M3", centered=True)
obstacle_sized.dcenter = obstacle.dcenter


routes = gf.routing.route_bundle_electrical(
    c,
    pbot.ports,
    ptop.ports,
    start_straight_length=100,
    separation=20,
    cross_section="metal_routing",
    bboxes=[
        obstacle_sized.bbox(),
        pbot.bbox(),
        ptop.bbox(),
    ],  # obstacles to avoid not working yet
    sort_ports=True,
)

c
../_images/437ea962654559566204a3aff1da32258a82f8a638ed36e5b8688efd49f581cf.png

route_bundle_all_angle#

You can also route using diagonal routes.

import gdsfactory as gf

c = gf.Component()
rows = 3
straight = gf.c.straight
w1 = c << gf.c.array(straight, rows=rows, columns=1, row_pitch=10)
w2 = c << gf.c.array(straight, rows=rows, columns=1, row_pitch=10)
w2.drotate(-30)
w2.movex(140)
p1 = list(w1.ports.filter(orientation=0))
p2 = list(w2.ports.filter(orientation=150))
p1.reverse()
p2.reverse()

gf.routing.route_bundle_all_angle(
    c,
    p1,
    p2,
    separation=3,
)
c
../_images/904423c15226c5d28afc9bd3e79852da51ca25d15c1c6bfbc5d3fdd10fa3ad28.png

You can also use it to connect rotated components that do not have a manhattan orientation (0, 90, 180, 270)

c = gf.Component()

mmi = gf.components.mmi2x2(width_mmi=10, gap_mmi=3)
mmi1 = c.create_vinst(mmi)  # create a virtual instance
mmi2 = c.create_vinst(mmi)  # create a virtual instance

mmi2.move((100, 10))
mmi2.drotate(30)

routes = gf.routing.route_bundle_all_angle(
    c,
    mmi1.ports.filter(orientation=0),
    [mmi2.ports["o2"], mmi2.ports["o1"]],
)
c.show()
c
../_images/239320a2e0b51f2991ce0c53e880c0a973b1138a74c8fb8a419eb7cb4a2a66c4.png

Dubin paths#

If you’re working with PIC layouts and looking for a straightforward way to optimize waveguide paths, Dubins paths offer an effective solution by ensuring the shortest path with minimal bending and loss.

Using Dubins paths for waveguide routing can simplify your design process significantly. Compared to traditional interconnects, Dubins paths offer shorter, more reliable routes that avoid unnecessary bending and intersections. For PIC layouts, this translates into denser, cleaner designs with improved performance.

See blog

c = gf.Component()

# Create two straight waveguides with different orientations
wg1 = c << gf.components.straight(length=100, width=3.2)
wg2 = c << gf.components.straight(length=100, width=3.2)

# Move and rotate the second waveguide
wg2.move((300, 50))
wg2.rotate(45)

# Route between the output of wg1 and input of wg2
route = gf.routing.route_dubin(
    c,
    port1=wg1.ports["o2"],
    port2=wg2.ports["o1"],
    cross_section=gf.cross_section.strip(width=3.2, radius=100),
)
c
../_images/911b75aae62e8d7cb279cf220981a71db3b457b6760cf9880df9561ac73d481a.png
c = gf.Component()

# Create two multi-port components
comp1 = c << gf.components.nxn(west=0, east=10, xsize=10, ysize=100, wg_width=3.2)
comp2 = c << gf.components.nxn(west=0, east=10, xsize=10, ysize=100, wg_width=3.2)

# Position second component
comp2.drotate(30)
comp2.move((500, -100))

# Route between corresponding ports
for i in range(10):
    port1_name = f"o{10-i}"  # Inverted port id for port1
    port2_name = f"o{i+1}"  # Adjusted to match available ports
    gf.routing.route_dubin(
        c,
        port1=comp1.ports[port1_name],
        port2=comp2.ports[port2_name],
        cross_section=gf.cross_section.strip(width=3.2, radius=100 + i * 10),
    )
c
../_images/f4da670cf38f657e1ca9cff06e667859b7e01d834c6c798a9abd1e89f956f5ca.png

auto_taper#

Both route_single and route_bundle have a auto_taper parameter.

For auto_taper to work you need to define how to transition different between different layers and widths.


layer_transitions = {
    LAYER.WG: partial(gf.c.taper, cross_section="strip", length=10),
    (LAYER.WG, LAYER.WGN): "taper_sc_nc",
    (LAYER.WGN, LAYER.WG): "taper_nc_sc",
    LAYER.M3: "taper_electrical",
}

return Pdk(
    name="generic",
    cells=cells,
    cross_sections=cross_sections,
    layers=LAYER,
    layer_stack=LAYER_STACK,
    layer_views=LAYER_VIEWS,
    layer_transitions=layer_transitions,
    materials_index=materials_index,
    constants=constants,
    connectivity=LAYER_CONNECTIVITY,
)

For example, in the code below if you have a width mismatch between two ports, the router will automatically add a taper to transition between the two widths, only if auto_taper=True, otherwise it will raise an error.

c = gf.Component()
s1 = c << gf.components.straight()
s2 = c << gf.components.straight(width=2)
s2.move((40, 50))
route = gf.routing.route_single(
    c,
    port1=s1.ports["o2"],
    port2=s2.ports["o1"],
    cross_section="strip",
    auto_taper=True,
)
c
../_images/5c97964681a0fbfcd33580ea775003d0b7572d4af7dd1f9f8f612e89dc0b8c9b.png
import gdsfactory as gf
from gdsfactory.routing.auto_taper import auto_taper_to_cross_section


@gf.cell
def silicon_nitride_strip(width_nitride: float = 1) -> gf.Component:
    c = gf.Component()
    ref = c << gf.c.straight(
        cross_section=gf.cross_section.nitride, width=width_nitride
    )
    port1 = auto_taper_to_cross_section(
        c, port=ref["o1"], cross_section=gf.cross_section.strip
    )
    c.add_port(name="o1", port=port1)
    c.add_port(name="o2", port=ref["o2"])
    return c


c = silicon_nitride_strip(width_nitride=1)
c
../_images/d511def72733a0bf468dd599a06eef8a8afefa3d1be12b5119992698bef6a5cc.png
c = silicon_nitride_strip(width_nitride=4)
c
../_images/eb815fb30ef52b1186fa2569a301b8b64f73fa7c23e8dc27fe5f414aceb070fc.png