add_fan_in takes a set of N ports and produces N parallel waveguides
that converge into a tightly-pitched bundle line — the input side
of a bundle route. add_bundle_astar and [add_bundle_from_corners][doroutes.add_bundle_from_corners]
call it internally on each end of a bundle, but you can also call it
directly when you only need the converging stub (for example, to
expose a bundle interface that a downstream router consumes, or to
compress a fibre-array fan-out without a closed-loop A* pass).
There are three fan-in strategies, each with a different sweet spot:
"manhattan"(default) — a 90° comb. The most compact choice when port pitch is comfortable relative to the bend radius."sbend"— per-wire euler s-bend with a corner angle sized to the PDK radius for each wire's jog. The right choice when port pitch falls below 2·radius and manhattan can no longer fit two 90° corners between adjacent wires."lbend"— straight + 90° bend + straight per port, so the bundle exits perpendicular to the input direction. Useful when the downstream bundle needs to turn 90°, or when you need a tunable clearance before the first bend.
This is optical routing: bends are bend_euler at the PDK
minimum radius (5 µm for cspdk.si220.cband).
Imports
A 5-Port Frame
dr.pcells.fanout_frame(orientation="e") is an example layout with
5 east-facing ports spaced 40 µm apart inside a 100 µm-wide frame —
typical of a fibre-array facet. We render the empty frame first as a
reference, then apply each of the three fan-in strategies in turn on
a fresh copy so you can compare the resulting geometries.
c = gf.Component()
ref = c << dr.pcells.fanout_frame(orientation="e")
c.add_ports(ref)
dr.util.show_cell(c)
Manhattan Fan-In (Default)
The classic 90°-comb fan-in. Each port goes straight, then turns 90° onto the bundle line. The bend lands at the port face — predictable and compact, but on tight pitches the bend body can intrude on neighbouring wires.
c = gf.Component()
ref = c << dr.pcells.fanout_frame(orientation="e")
c.add_ports(ref)
dr.add_fan_in(
c=c,
inputs=c.ports,
straight="straight",
bend={"component": "bend_euler", "settings": {"radius": 5}},
)
dr.util.show_cell(c)
S-Bend Fan-In (Tight Pitch)
S-bend is the right choice when port pitch < 2·radius — the regime where the manhattan staircase can no longer fit two 90° corners without adjacent bend bodies overlapping. For looser pitches, manhattan is strictly more compact, so prefer it there.
The frame below uses a 7 µm port pitch with the same 5 µm bend radius
(2·R = 10 µm > 7 µm pitch). Each port is routed via two euler bends
connected by an angled straight: straight → bend_euler(θ) → straight
→ bend_euler(θ) → straight. The angle θ is computed per wire:
- Wires with
|jog| ≥ 2·radiususe θ = 90° (full L-bend pair). - Wires with smaller jogs use the largest geometrically-feasible angle
(
θ = arccos(1 − |jog|/2R)) that keeps the corner at the PDK radius floor. - The centerline wire (zero jog) is a pure straight.
Adjacent wires are staggered so their diagonals stay spacing_dbu
apart perpendicular to the bend, so outer wires never cross inner ones.
Tunable via strategy_kwargs:
- max_sbend_angle_deg — uniform cap on the per-wire corner angle
(default 90°). Lower values produce smoother bends across all wires
at the cost of forward extent.
- sbend_length_dbu — minimum forward extent (port → bundle line) in
dbu. Useful when the downstream A* needs more clearance to bend the
bundle.
c = gf.Component()
ref = c << dr.pcells.fanout_frame(
orientation="e",
num_inputs=5,
input_spacing=7, # < 2*radius = 10 µm — manhattan would not fit
width=30,
)
c.add_ports(ref)
dr.add_fan_in(
c=c,
inputs=c.ports,
straight="straight",
bend={"component": "bend_euler", "settings": {"radius": 5}},
strategy="sbend",
)
dr.util.show_cell(c)
/home/runner/work/DoRoutes/DoRoutes/.venv/lib/python3.12/site-packages/cachetools/_cached.py:185: UserWarning: bend_euler angle should be 90 or 180. Got 67.04550059860719. Use bend_euler_all_angle instead.
v = func(*args, **kwargs)
L-Bend Fan-In
Each port emits straight → 90° bend → straight, with the pre/post
straight lengths chosen so all wires land on a common bundle line at
spacing_dbu pitch. The bundle direction ends up perpendicular to
the input direction.
Use this when the bundle needs to exit the fan-in turning 90° from
the input ports — for example, when you're routing toward a
destination on a perpendicular edge of the layout, or when you need
a tunable clearance segment before the first bend (set via
margin_dbu in strategy_kwargs).
When using add_fan_in directly you pass lbend_side ("left"
or "right") explicitly. The full add_bundle_astar call sees both
port sets at once and can pick the side automatically with
lbend_side="auto".
c = gf.Component()
ref = c << dr.pcells.fanout_frame(orientation="e")
c.add_ports(ref)
dr.add_fan_in(
c=c,
inputs=c.ports,
straight="straight",
bend={"component": "bend_euler", "settings": {"radius": 5}},
strategy="lbend",
strategy_kwargs={"lbend_side": "left"},
)
dr.util.show_cell(c)
Next steps
- The fan-in is one stage of a full bundle route; see A* Bundle Routing for the end-to-end call.
- For a side-by-side view of how each strategy looks on the same topology, see Bundle Routing Comparison.