add_bundle_astar routes N parallel waveguides as a single bundle between two port arrays, letting A* find a path that avoids obstacles while keeping the bundle intact. It ties three pieces together:

  1. a fan-in that converges the source ports onto a tight bundle line,
  2. an A* search that routes the bundle as a single thick wire while avoiding the listed obstacle layers,
  3. a fan-out that re-spreads the bundle onto the destination ports.

Reach for it when you have N input ports and N output ports, you want them routed together as a unit (matched bend and length), and the exact shape of the path doesn't matter as long as it avoids obstacles.

Two helper layouts in dr.pcells cover the two common cases:

  • fanout_frame2 — input and output sides face along the same axis (here transition="ew": inputs east-facing, outputs west-facing).
  • fanout_frame3 — input and output sides are rotated 90° relative to each other, so the bundle has to make a corner.

This is optical routing: bends are bend_euler at the PDK minimum radius (5 µm for cspdk.si220.cband). The bundle's effective radius scales with the port count (single_radius + (N-1)*spacing/2), so wide bundles need more clearance to make a turn.

Imports

import doroutes as dr
import gdsfactory as gf
from cspdk.si220.cband import PDK

PDK.activate()

In-Line Bundle with Obstacles

fanout_frame2(transition="ew") puts inputs on the west edge and outputs on the east edge of the frame. We drop two MMIs into the routing channel as obstacles — the A* path has to weave around them while keeping the bundle intact.

c = gf.Component()
ref = c << dr.pcells.fanout_frame2(transition="ew", add_frame=True)
c.add_ports(ref)

# Drop two MMIs into the routing channel as obstacles.
r = c << gf.components.mmi()
r.dmove((40, 120))
r = c << gf.components.mmi()
r.drotate(90)
r.dmove((60, 40))

dr.add_bundle_astar(
    component=c,
    ports1=[p for p in c.ports if str(p.name).startswith("in")],
    ports2=[p for p in c.ports if str(p.name).startswith("out")],
    spacing=1.0,
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    layers=["WG"],
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.

Bundle Around a 90° Corner

fanout_frame3 rotates the output side by 90°, so the bundle's start and end orientations are perpendicular. The A* search has to turn the bundle through a corner — and the bundle's effective radius (set by port count and spacing) determines how much room that corner needs.

c = gf.Component()
ref = c << dr.pcells.fanout_frame3(transition="ew")
c.add_ports(ref)
dr.add_bundle_astar(
    component=c,
    ports1=[p for p in c.ports if str(p.name).startswith("in")],
    ports2=[p for p in c.ports if str(p.name).startswith("out")],
    spacing=1.0,
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    layers=["WG"],
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.

Next steps