The A* routing functions (add_route_astar and add_bundle_astar) don't search the layout continuously — internally they project everything onto a uniform grid and search over that. Two consequences for you as a user:

  • Routes always step on grid lines. The router won't return a path finer than the grid pitch.
  • Obstacles are taken cell-by-cell. A cell that any obstacle polygon touches is treated as fully blocked.

The grid_unit parameter (in dbu) controls how fine that grid is. There's a trade-off:

  • Coarser grids (larger grid_unit) search faster, but can miss valid routes that thread through narrow gaps and produce more "boxy" paths around obstacles.
  • Finer grids (smaller grid_unit) follow obstacles more accurately and squeeze through tighter spaces, but the search space grows fast — A* slows down quadratically with the grid pitch.

There's also a hard lower bound: grid_unit must be at most half the bend radius. The router enforces this because the bend's geometry needs at least two grid cells along each axis to be representable. A useful starting point is somewhere in [radius / 20, radius / 4] — this notebook shows the difference between the two ends of that range.

Imports

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

PDK.activate()

A Route at a Coarse Grid

Routing on field1 with grid_unit = 2500 dbu (2.5 µm — exactly half the 5 µm bend radius, the coarsest the router will accept). The search has limited freedom: every step has to be a multiple of 2.5 µm, so the route can only make wide detours and the path looks blocky.

c = gf.Component()
ref = c << dr.pcells.field1()
c.add_ports(ref)
dr.add_route_astar(
    c=c,
    start=ref.cell.insts["in"].ports["o2"],
    stop=ref.cell.insts["out"].ports["o1"],
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    layers=["WG"],
    grid_unit=2500,
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.

Same Route at a Finer Grid

Same start, stop, and obstacles, with grid_unit = 250 dbu (0.25 µm — a twentieth of the bend radius). The router has many more grid points to work with, so the path can hug obstacles closely and take a more compact route.

c = gf.Component()
ref = c << dr.pcells.field1()
c.add_ports(ref)
dr.add_route_astar(
    c=c,
    start=ref.cell.insts["in"].ports["o2"],
    stop=ref.cell.insts["out"].ports["o1"],
    straight="straight",
    bend={"component": "bend_euler", "settings": {"radius": 5}},
    layers=["WG"],
    grid_unit=250,
)
dr.util.show_cell(c)
API key for organization 'DoPlayDo' found.

Picking grid_unit

Things to keep in mind when you're tuning it:

  • Hard upper bound: half the bend radius. The router refuses anything coarser. For a 5 µm radius, grid_unit must be ≤ 2.5 µm (2500 dbu).
  • Practical sweet spot: between radius / 20 and radius / 4. Below that lower bound the search slows down without producing meaningfully different routes; above the upper bound the route starts to look boxy.
  • Match it to your obstacle dimensions. If your floorplan has 2 µm routing channels, a grid_unit close to 2 µm risks collapsing them into "obstacle cells" — A* will then refuse to route through them.
  • If A* gives up with RuntimeError: No route found!, halving grid_unit and re-running is the first thing to try. The narrow gap that blocked it may simply have been below the grid resolution.

The same advice applies to the bundle routers — add_bundle_astar takes the same grid_unit parameter.

Next steps

  • For the routing calls this notebook tunes, see A* Routing (single wire) and A* Bundle Routing (multi-wire).
  • If routing fails at any grid size, the route may just not be possible. Check the layout for blocked corridors, or try Corners / Steps to specify the path manually.