Skip to content

Routing Functions

High-level functions for routing waveguide bundles between ports. These wrap the Router class with a one-call interface.

route_bundle

route_bundle(
    start_ports,
    end_ports,
    *,
    min_radius: float | None = None,
    obstacles: list | None = None,
    obstacle_layers: Any = None,
    collision_check_layers: Any = None,
    obstacle_margin: float | None = None,
    cross_section: Any = None,
    context_component: Any = None,
    finetune: FinetuneConfig | bool | None = None,
    fallback_to_iterative: bool = False,
    iterative_bundle_routing: bool = False,
    negotiated_bundle_routing: bool = False,
    backbone_bundle_routing: bool = False,
    route_spacing: float = 3.0,
    route_width: float | None = None,
    sort_ports: bool = False,
    polylines: Any = None,
    waypoints: Any = None,
    scheduler: Any = None,
    max_steps: int = 2000,
    tol: float = 0.0001,
    patience: int | None = None,
    pbar: bool = True,
    **router_kwargs: Any,
) -> Router

Route a bundle of waveguides between port pairs.

Ports are gf.Port objects with center, orientation, layer, and width attributes. Accepts a single Port or a list; nested lists [[p1], [p2, p3]] create independent routing groups.

By default, uses manhattan routing only (fast, no optimization). Pass finetune=FinetuneConfig(...) to run gradient-based optimization afterwards.

When cross_section is provided, routing parameters are derived from it automatically (explicit kwargs override with a warning):

  • min_radiusxs.radius_min
  • target_radiusxs.radius
  • route_widthxs.width
  • route_collision_widthxs.width
  • route_collision_margin ← PDK separation (default 3.0)
  • obstacle_margin ← PDK separation (default 3.0)

Without cross_section, the defaults are: min_radius=5.0, obstacle_margin=3.0, route_width=2.0, route_collision_width=2.0, route_collision_margin=3.0.

Parameters:

Name Type Description Default
start_ports

Start port(s) — gf.Port or list of Ports.

required
end_ports

End port(s) — gf.Port or list of Ports.

required
min_radius float | None

Minimum bend radius (um). Derived from cross_section.radius_min when cross_section is given.

None
obstacles list | None

Keep-out zones. Any objects with xmin, ymin, xmax, ymax attributes (e.g. gdsfactory component references from c << gf.c.rectangle(...)).

None
obstacle_layers Any

Layer spec or list of layer specs to extract from context_component as keep-out zones. Extracted bboxes are appended to any explicit obstacles.

None
collision_check_layers Any

Alias for obstacle_layers for gdsfactory route_bundle() compatibility.

None
obstacle_margin float | None

Clearance from obstacles (um). Derived from PDK separation when cross_section is given.

None
cross_section Any

CrossSection object, string name (e.g. "strip"), or callable. When provided, routing parameters are derived from it.

None
context_component Any

Component to merge with when using build_gds(include_context=True).

None
finetune FinetuneConfig | bool | None

FinetuneConfig(...) to run gradient-based optimization. None (default) for manhattan only.

None
fallback_to_iterative bool

If True, validate shared-backbone bundle initialization and retry with iterative bundle routing when it collides.

False
iterative_bundle_routing bool

If True, use iterative bundle routing directly instead of shared-backbone bundle initialization.

False
backbone_bundle_routing bool

If True, force raw shared-backbone bundle initialization with NO fallback ladder. For debugging/isolating the backbone method — it may produce crossing routes on non-offsettable bundles (the no-flag default escalates past such failures instead).

False
route_spacing float

Center-to-center spacing used by the Manhattan bundle initializer when routing around prior routes.

3.0
route_width float | None

Physical route width used by the Manhattan initializer when converting already-routed skeletons to temporary keepouts. Derived from cross_section.width when cross_section is given.

None
polylines Any

Complete routes to use instead of running the initializer — one (M_i, 2) array of corner coordinates per port pair, in the same order as the port lists. Hand-authored, or saved from a previous router.get_polylines(). Crossings are kept as given. With finetune=None the routes are drawn exactly as supplied, which is what makes GDS re-rendering reproducible; with a FinetuneConfig they are refined — axis-aligned polylines stay axis-aligned, while polylines that are not get the unconstrained finetune (with a warning, since that path moves each route's first and last interior point onto the port tangent lines). Cannot be combined with match_ports or sort_ports, which would re-pair the routes.

Supplied straights and L-routes are held fixed by finetune, since they own no tunable rail and playdough will not invent a detour you did not draw. If you handed in a sketch and want it reshaped, pass finetune=FinetuneConfig(..., ensure_min_dof=True).

This is only the input half of a cache: nothing is stored or invalidated for you. Stale geometry is caught at the ports — an endpoint that no longer reaches its port, or a terminal segment no longer leaving along the port axis, raises ValueError naming the route. Obstacles are not re-checked, so a saved route that now crosses a new keep-out is accepted silently; re-route when the surrounding layout moves.

None
waypoints Any

Incomplete positional hints the manhattan initializer routes through — the middle of the family. polylines supply whole routes and skip the initializer; anchors are soft targets the finetune is merely pulled toward; waypoints are hard points the initializer must pass through while still solving everything else.

The nesting mirrors start_ports. A flat list of points hints the whole bundle; a list of lists gives one list per port pair, or per port group when the ports were passed grouped. None in a per-route list means "route that pair normally". A hint is (x, y), a kdb.DPoint, a kdb.DTrans, or ((x, y), degrees) — the last two pin the heading the route takes through the point, which is beyond what kfactory can express (it has one heading for a whole bundle). Note a pinned hint is always a pass-through on that heading, never a corner.

A hint costs no extra bend when the route can run straight through it: the initializer tries a pass-through first and only turns at the point when that does not work. So hinting somewhere a route already goes leaves it unchanged.

A hint shared by several routes belongs to the bundle: the representative route is routed through it and the others are derived as parallel offsets, so the group cannot cross itself. Use per-route hints when a specific route must hit a specific point.

Hints are honoured exactly by the initializer. finetune may then move a route off them, so they are also handed to it as anchor targets (waypoints_as_anchors, on by default). Anchoring is a soft pull that needs a non-zero anchor_proximity weight and competes with the other costs — on a length-matched bundle the two directly oppose each other, so choose the weight rather than expecting both. Unreachable hints are dropped with a warning rather than producing a broken route; the initializer never raises for them.

None
sort_ports bool

If True, reorder each port list using the same KFactory/gdsfactory bundle sorting convention used by gf.routing.route_bundle(..., sort_ports=True). This can change which start port connects to which end port — i.e. the netlist — because the two lists are sorted independently and then paired by index; use it only when the assignment is free (e.g. a fan-out into an interchangeable fiber array). A UserWarning reports how many connections were rewired, and router.port_permutation records the mapping back to the requested pairs. This is different from match_ports=True: sort_ports sorts both lists in a gdsfactory-compatible order and pairs by index, while match_ports computes pairings from route geometry to reduce crossings.

False
**router_kwargs Any

Forwarded to Router(). Common ones:

  • target_radius (float): preferred bend radius (um).
  • internal_pivots (int): corner count per route.
  • route_width (float): physical width used for manhattan initialization keepouts.
  • route_collision_width (float): route width used by the differentiable route-collision cost during finetuning.
  • route_collision_margin (float): min clearance between routes.
  • match_ports (bool): Playdough-specific geometry heuristic that changes which start port connects to which end port (the netlist) to minimize crossings. Only for free assignments.
  • sort_ports (bool): gdsfactory-compatible port-list sorting before index-based pairing; also changes the netlist.
  • maximize_radius (bool): prefer largest allowed radius.
  • bend_type (str): "euler" (default) or "arc".
{}

Returns:

Type Description
Router

A Router with pivots set. Key methods:

Router
  • router.build_gds() → gdsfactory Component.
Router
  • router.get_polylines() → list of (N, 2) numpy arrays.
Router
  • router.finetune(config) → refine with optimization.

Signature examples::

pld.route_bundle(start_ports, end_ports)
pld.route_bundle(start_ports, end_ports, cross_section="strip")
pld.route_bundle(start_ports, end_ports, obstacles=[obs], finetune=pld.FinetuneConfig(lr=1.0))

route_polylines

route_polylines(start_ports, end_ports, *, smooth: bool = False, **kwargs: Any) -> list

Route a bundle and return polyline coordinates as numpy arrays.

Lightweight alternative to route_bundle() — returns polyline coordinates directly without building GDS components. Accepts all the same keyword arguments as route_bundle() (obstacles, cross_section, finetune, min_radius, etc.).

Passing polylines= round-trips: the returned list can be handed straight back on a later call to redraw the same routes without re-running the router. Note the returned coordinates are snapped and collinear-reduced, so they can differ from the first call's internal pivots by a few database units; from the second call on it is a fixed point.

Parameters:

Name Type Description Default
start_ports

Start port(s) — gf.Port or list of Ports.

required
end_ports

End port(s) — gf.Port or list of Ports.

required
smooth bool

If True, return dense smooth paths through euler/arc bends instead of raw corner-tip pivots. Only the smooth=False form can be fed back as polylines=.

False
**kwargs Any

Forwarded to route_bundle(). See its docstring for the full list (obstacles, cross_section, finetune, min_radius, target_radius, internal_pivots, polylines, etc.).

{}

Returns:

Type Description
list

List of (N_i, 2) numpy arrays, one per route. Each array

list

contains the corner-point coordinates in um.

Signature examples::

pld.route_polylines(start_ports, end_ports)
pld.route_polylines(start_ports, end_ports, cross_section=xs, obstacles=[obs])
pld.route_polylines(start_ports, end_ports, finetune=pld.FinetuneConfig(non_manhattan=0.0))

add_route_bundle

add_route_bundle(
    component,
    start_ports,
    end_ports,
    *,
    as_single: bool = True,
    add_overlays: bool = False,
    include_context: bool = False,
    **kwargs: Any,
) -> Router

Route a bundle and add the result to an existing component.

In-place API: routes are added to component as instances. Returns the Router for inspection and optional finetuning. Accepts all the same keyword arguments as route_bundle() (obstacles, cross_section, finetune, etc.).

Parameters:

Name Type Description Default
component

The gdsfactory Component to add routes to.

required
start_ports

Start port(s) — gf.Port or list of Ports.

required
end_ports

End port(s) — gf.Port or list of Ports.

required
as_single bool

Combine all routes into one sub-component.

True
add_overlays bool

Whether to include diagnostic polyline/smooth-path overlay layers in the inserted route geometry.

False
include_context bool

Deprecated for this in-place API. Use route_bundle(...).build_gds(include_context=True) when you want a separate combined copy of context plus routes.

False
**kwargs Any

Forwarded to route_bundle(). See its docstring for the full list.

{}

Returns:

Type Description
Router

The Router instance. Call router.finetune(config) to

Router

refine, then router.build_gds() to update the inserted routes.

Signature examples::

pld.add_route_bundle(c, start_ports, end_ports, obstacles=[obs])
pld.add_route_bundle(c, start_ports, end_ports, cross_section="strip")
pld.add_route_bundle(c, start_ports, end_ports, finetune=pld.FinetuneConfig(lr=1.0))