add_bundle_astar

add_bundle_astar(
    component: ProtoTKCell,
    ports1: list[PortLike],
    ports2: list[PortLike],
    spacing: Um,
    bend: KCellSpec,
    straight: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Int = 500,
    *,
    fanin_strategy: FanInStrategy = "manhattan",
    fanin_strategy_kwargs: dict[str, Any] | None = None,
) -> list[None]

Add a bundle route using the a-star algorithm.

Parameters:

Name Type Description Default
component ProtoTKCell

The component to add the route into.

required
ports1 list[PortLike]

the start ports

required
ports2 list[PortLike]

the end ports

required
spacing Um

the spacing between the waveguides in the bundle

required
bend KCellSpec

the bend-spec to create bends with

required
straight KCellSpec

the straight-spec to create straights with

required
layers Iterable[LayerLike]

the layers to avoid.

required
grid_unit Int

the discretization unit for the a-star algorithm.

500
fanin_strategy FanInStrategy

how the start and end bundles are formed. "manhattan" uses the default 90°-comb fan-in. "sbend" places a single bend_s per port and is the recommended choice when the bend radius is tight relative to the port pitch. "lbend" forms an L-shaped per-wire fan-in (variable-length straight, 90° bend, variable-length straight) so the bundle direction ends up perpendicular to the port-facing direction.

'manhattan'
fanin_strategy_kwargs dict[str, Any] | None

per-strategy overrides. For "sbend": sbend (KCellSpec, default "bend_s" — any S-bend component that accepts size=(dx, dy)); sbend_length_dbu (int, exact outermost-S-bend forward extent); max_sbend_angle_deg (float, bounds the outermost S-bend midpoint tangent angle — 90° is the tightest, smaller angles are smoother; clamped to the PDK radius as a floor); allow_min_radius_violation (bool, default False — set True to let sbends go below the PDK-declared radius). For "lbend": lbend_side ("left"/"right"/"auto"), margin_dbu (int).

None
Source code in python/doroutes/bundles.py
def add_bundle_astar(
    component: ProtoTKCell,
    ports1: list[PortLike],
    ports2: list[PortLike],
    spacing: Um,
    bend: KCellSpec,
    straight: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Int = 500,
    *,
    fanin_strategy: FanInStrategy = "manhattan",
    fanin_strategy_kwargs: dict[str, Any] | None = None,
) -> list[None]:  # FIXME: GDSFactory expects a list of something...
    """Add a bundle route using the a-star algorithm.

    Args:
        component: The component to add the route into.
        ports1: the start ports
        ports2: the end ports
        spacing: the spacing between the waveguides in the bundle
        bend: the bend-spec to create bends with
        straight: the straight-spec to create straights with
        layers: the layers to avoid.
        grid_unit: the discretization unit for the a-star algorithm.
        fanin_strategy: how the start and end bundles are formed.
            ``"manhattan"`` uses the default 90°-comb fan-in. ``"sbend"`` places
            a single bend_s per port and is the recommended choice when the
            bend radius is tight relative to the port pitch. ``"lbend"``
            forms an L-shaped per-wire fan-in (variable-length straight, 90°
            bend, variable-length straight) so the bundle direction ends up
            perpendicular to the port-facing direction.
        fanin_strategy_kwargs: per-strategy overrides. For ``"sbend"``:
            ``sbend`` (KCellSpec, default ``"bend_s"`` — any S-bend component
            that accepts ``size=(dx, dy)``); ``sbend_length_dbu`` (int, exact
            outermost-S-bend forward extent); ``max_sbend_angle_deg`` (float,
            bounds the outermost S-bend midpoint tangent angle — 90° is the
            tightest, smaller angles are smoother; clamped to the PDK radius
            as a floor); ``allow_min_radius_violation`` (bool, default False
            — set True to let sbends go below the PDK-declared radius).
            For ``"lbend"``: ``lbend_side`` ("left"/"right"/"auto"),
            ``margin_dbu`` (int).

    """
    if len(ports1) != len(ports2):
        msg = "Number of start ports is different than number of end ports"
        raise ValueError(msg)
    num_ports = len(ports1)
    if num_ports == 0:
        msg = "No input/output ports given"
        raise ValueError(msg)
    xyo1 = [validate_position_with_orientation(p) for p in ports1]
    xyo2 = [validate_position_with_orientation(p) for p in ports2]
    os1 = [o for _, _, o in xyo1]
    os2 = [o for _, _, o in xyo2]
    if not all(o == os1[0] for o in os1):
        msg = f"Input port orientations are not all equal. Got: {os1}."
        raise ValueError(msg)
    if not all(o == os2[0] for o in os2):
        msg = f"Output port orientations are not all equal. Got: {os1}."
        raise ValueError(msg)

    o1 = validate_orientation(os1[0])
    o2 = validate_orientation(os2[0])
    if o1 == o2:
        # FIXME: this check seems necessary because the router doesn't
        # seem to find a solution anyway in this case :(
        msg = (
            f"The port orientation at the input needs to be different "
            f"from the port orientation at the output. Got: {o1!r}=={o2!r}."
        )
        raise ValueError(msg)

    if num_ports == 1:
        start = validate_position_with_orientation(ports1[0], invert_orientation=False)
        stop = validate_position_with_orientation(ports2[0], invert_orientation=True)
    else:
        inv_dbu = util.get_inv_dbu(component.kcl)
        spacing_dbu = round(spacing * inv_dbu)

        if fanin_strategy == "lbend":
            # Auto-side resolution needs both port sets (lives here, not in
            # add_fan_in), but the actual lbend geometry is generated by
            # add_fan_in itself.
            kwargs = fanin_strategy_kwargs or {}
            side_spec: LBendSide = kwargs.get("lbend_side", "auto")
            margin_dbu = int(kwargs.get("margin_dbu", 0))
            side1, side2 = _resolve_lbend_sides(
                side_spec,
                ports1,
                ports2,
                o1,
                o2,
                bend=bend,
                component=component,
            )
            starts = add_fan_in(
                c=component,
                inputs=ports1,
                straight=straight,
                bend=bend,
                spacing_dbu=spacing_dbu,
                strategy="lbend",
                strategy_kwargs={"lbend_side": side1, "margin_dbu": margin_dbu},
            )
            stops = add_fan_in(
                c=component,
                inputs=ports2,
                straight=straight,
                bend=bend,
                spacing_dbu=spacing_dbu,
                strategy="lbend",
                strategy_kwargs={"lbend_side": side2, "margin_dbu": margin_dbu},
            )
            effective_o1 = lbend_direction(o1, side1)
            effective_o2 = lbend_direction(o2, side2)
        else:
            starts = add_fan_in(
                c=component,
                inputs=ports1,
                straight=straight,
                bend=bend,
                spacing_dbu=spacing_dbu,
                strategy=fanin_strategy,
                strategy_kwargs=fanin_strategy_kwargs,
            )
            stops = add_fan_in(
                c=component,
                inputs=ports2,
                straight=straight,
                bend=bend,
                spacing_dbu=spacing_dbu,
                strategy=fanin_strategy,
                strategy_kwargs=fanin_strategy_kwargs,
            )
            effective_o1 = o1
            effective_o2 = o2

        # Note: same-direction start/stop orientations (effective_o1 ==
        # effective_o2) are allowed for lbend since the A* handles
        # parallel bundles given a large enough routing area.

        start = (*np.mean(starts, 0), effective_o1)
        stop = (*np.mean(stops, 0), util.invert_orientation(effective_o2))
        bend = partial(pcells.bends, bend, straight, num_ports, spacing)
        straight = partial(pcells.straights, straight, num_ports, spacing)

    try:
        add_route_astar(
            c=component,
            start=start,
            stop=stop,
            layers=layers,
            straight=straight,
            bend=bend,
            grid_unit=grid_unit,
        )
    except RuntimeError as e:
        if fanin_strategy == "manhattan":
            msg = (
                f"A* routing failed after manhattan fan-in ({e}). This is a "
                "common symptom when the composite bundle bend (effective "
                "radius = single_radius + (num_ports-1)*spacing/2) cannot fit "
                "between the fan-in stops and the target. Try "
                "fanin_strategy='sbend' — it compresses each wire smoothly "
                "onto the bundle line."
            )
            raise RuntimeError(msg) from e
        raise
    return [None for _ in range(num_ports)]

add_fan_in

add_fan_in(
    c: ProtoTKCell,
    inputs: list[PortLike],
    straight: KCellSpec,
    bend: KCellSpec,
    x_bundle_dbu: Dbu | None = None,
    y_bundle_dbu: Dbu | None = None,
    spacing_dbu: Dbu | None = None,
    start_dir: OrientationChar | None = None,
    *,
    strategy: FanInStrategy = "manhattan",
    strategy_kwargs: dict[str, Any] | None = None,
) -> NDArray[int64]

Add a fan-in to a parent component.

Parameters:

Name Type Description Default
c ProtoTKCell

the component to add the fan-in to

required
inputs list[PortLike]

the list of ports to start from

required
straight KCellSpec

the straight-spec to create straights from

required
bend KCellSpec

the bend-spec to create bends from

required
x_bundle_dbu Dbu | None

the x-location where to form the confluence of the bundle will be two bend radiuses from the inputs if not given.

None
y_bundle_dbu Dbu | None

the y-location where to form the confluence of the bundle will be somewhere in the middle if not given.

None
spacing_dbu Dbu | None

the spacing between waveguides in the bundle

None
start_dir OrientationChar | None

the start direction of the bundle (derived from ports if not given)

None
strategy FanInStrategy

fan-in strategy. "manhattan" uses the existing 90°-comb fan-in. "sbend" compresses each wire to the bundle line with a single S-bend per port (useful when the bend radius is tight relative to the port pitch). "lbend" forms an L-shaped per-port fan-in (variable straight + 90° bend + variable straight) so virtual ports land on a common line at spacing_dbu pitch with the bundle direction rotated 90° from the input ports. "lbend" requires an explicit lbend_side ("left" or "right") in strategy_kwargs; "auto" resolution is only available via add_bundle_astar (which sees both port sets).

'manhattan'
strategy_kwargs dict[str, Any] | None

extra keyword arguments forwarded to the strategy implementation. Examples: sbend_length_dbu for "sbend", lbend_side and margin_dbu for "lbend".

None
Source code in python/doroutes/fanin.py
def add_fan_in(
    c: ProtoTKCell,
    inputs: list[PortLike],
    straight: KCellSpec,
    bend: KCellSpec,
    x_bundle_dbu: Dbu | None = None,
    y_bundle_dbu: Dbu | None = None,
    spacing_dbu: Dbu | None = None,
    start_dir: OrientationChar | None = None,
    *,
    strategy: FanInStrategy = "manhattan",
    strategy_kwargs: dict[str, Any] | None = None,
) -> NDArray[np.int64]:
    """Add a fan-in to a parent component.

    Args:
        c: the component to add the fan-in to
        inputs: the list of ports to start from
        straight: the straight-spec to create straights from
        bend: the bend-spec to create bends from
        x_bundle_dbu: the x-location where to form the confluence of the bundle
            will be two bend radiuses from the inputs if not given.
        y_bundle_dbu: the y-location where to form the confluence of the bundle
            will be somewhere in the middle if not given.
        spacing_dbu: the spacing between waveguides in the bundle
        start_dir: the start direction of the bundle (derived from ports if not given)
        strategy: fan-in strategy. ``"manhattan"`` uses the existing 90°-comb
            fan-in. ``"sbend"`` compresses each wire to the bundle line with a
            single S-bend per port (useful when the bend radius is tight
            relative to the port pitch). ``"lbend"`` forms an L-shaped
            per-port fan-in (variable straight + 90° bend + variable
            straight) so virtual ports land on a common line at
            ``spacing_dbu`` pitch with the bundle direction rotated 90°
            from the input ports. ``"lbend"`` requires an explicit
            ``lbend_side`` ("left" or "right") in ``strategy_kwargs``;
            ``"auto"`` resolution is only available via
            ``add_bundle_astar`` (which sees both port sets).
        strategy_kwargs: extra keyword arguments forwarded to the strategy
            implementation. Examples: ``sbend_length_dbu`` for ``"sbend"``,
            ``lbend_side`` and ``margin_dbu`` for ``"lbend"``.

    """
    kc: KCell = util.as_kcell(c)
    d0 = _resolve_start_direction(inputs, start_dir)
    coord = 1 if d0 in "ew" else 0
    starts_with_o = [validate_position_with_orientation(p) for p in inputs]
    starts = np.array(
        sorted([(x, y) for x, y, _ in starts_with_o], key=lambda xy: xy[coord])
    )

    if strategy == "manhattan":
        return _add_fan_in_manhattan(
            kc,
            starts,
            d0,
            straight,
            bend,
            x_bundle_dbu,
            y_bundle_dbu,
            spacing_dbu,
        )
    if strategy == "sbend":
        kwargs = strategy_kwargs or {}
        # Sort the original Port objects by the same key as ``starts`` so the
        # sbend implementation can connect to actual port instances.
        sorted_inputs = sorted(
            inputs,
            key=lambda p: validate_position_with_orientation(p)[coord],
        )
        return _add_fan_in_sbend(
            kc,
            starts,
            sorted_inputs,
            d0,
            straight,
            bend,
            x_bundle_dbu,
            y_bundle_dbu,
            spacing_dbu,
            **kwargs,
        )
    if strategy == "lbend":
        kwargs = strategy_kwargs or {}
        return _add_fan_in_lbend(
            kc,
            inputs,
            d0,
            straight,
            bend,
            spacing_dbu,
            **kwargs,
        )
    msg = f"Unknown fan-in strategy: {strategy!r}"
    raise ValueError(msg)

add_route_astar

add_route_astar(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    straight: KCellSpec,
    bend: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Dbu,
) -> None

Add an a-star route to a component.

Parameters:

Name Type Description Default
c ProtoTKCell

the component to add the route to

required
start PortLike

the start port

required
stop PortLike

the stop port

required
straight KCellSpec

the straight-spec to create straights from

required
bend KCellSpec

the bend-spec to create bends from

required
layers Iterable[LayerLike]

the layers to avoid

required
grid_unit Dbu

the discretization unit for the a-star algorithm

required
Source code in python/doroutes/routing.py
def add_route_astar(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    straight: KCellSpec,
    bend: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Dbu,
) -> None:
    """Add an a-star route to a component.

    Args:
        c: the component to add the route to
        start: the start port
        stop: the stop port
        straight: the straight-spec to create straights from
        bend: the bend-spec to create bends from
        layers: the layers to avoid
        grid_unit: the discretization unit for the a-star algorithm

    """
    corners = find_route_astar(c, start, stop, straight, bend, layers, grid_unit)
    _start = validate_position_with_orientation(start)
    _stop = validate_position_with_orientation(stop)
    add_route_from_corners(
        c=c,
        start=(_start[0], _start[1]),
        stop=(_stop[0], _stop[1]),
        corners=corners,
        straight=straight,
        bend=bend,
    )

add_route_from_corners

add_route_from_corners(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    corners: PointsDbu | None = None,
    *,
    steps: list[StepDbu] | None = None,
    straight: KCellSpec,
    bend: KCellSpec,
) -> None

Add a corners-based route to a component.

Parameters:

Name Type Description Default
c ProtoTKCell

the component to add the route to

required
start PortLike

the start port

required
stop PortLike

the stop port

required
corners PointsDbu | None

the corners in between start and stop.

None
steps list[StepDbu] | None

steps in between start and stop (alternative to corners).

None
straight KCellSpec

the straight-spec to create straights from

required
bend KCellSpec

the bend-spec to create bends from

required
Source code in python/doroutes/routing.py
def add_route_from_corners(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    corners: PointsDbu | None = None,
    *,
    steps: list[StepDbu] | None = None,
    straight: KCellSpec,
    bend: KCellSpec,
) -> None:
    """Add a corners-based route to a component.

    Args:
        c: the component to add the route to
        start: the start port
        stop: the stop port
        corners: the corners in between start and stop.
        steps: steps in between start and stop (alternative to corners).
        straight: the straight-spec to create straights from
        bend: the bend-spec to create bends from

    """
    if corners is not None and steps is not None:
        msg = "Cannot specify both 'corners' and 'steps'."
        raise ValueError(msg)
    _start = validate_position(start)
    _stop = validate_position(stop)
    if steps is not None:
        corners = util.steps_to_corners(util.as_kcell(c), steps, _start)
    elif corners is None:
        corners = []
    radius_dbu = util.extract_bend_radius(c.kcl, bend)
    directive_path = util.corners_to_directive_path(_start, _stop, corners, radius_dbu)
    _add_route_from_directive_path(c, directive_path, straight, bend)

add_route_from_steps

add_route_from_steps(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    steps: list[StepDbu],
    straight: KCellSpec,
    bend: KCellSpec,
) -> None

Add a steps-based route to a component.

Parameters:

Name Type Description Default
c ProtoTKCell

the component to add the route to

required
start PortLike

the start port

required
stop PortLike

the stop port

required
steps list[StepDbu]

the steps in between start and stop.

required
straight KCellSpec

the straight-spec to create straights from

required
bend KCellSpec

the bend-spec to create bends from

required
Source code in python/doroutes/routing.py
def add_route_from_steps(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    steps: list[StepDbu],
    straight: KCellSpec,
    bend: KCellSpec,
) -> None:
    """Add a steps-based route to a component.

    Args:
        c: the component to add the route to
        start: the start port
        stop: the stop port
        steps: the steps in between start and stop.
        straight: the straight-spec to create straights from
        bend: the bend-spec to create bends from

    """
    add_route_from_corners(
        c=c, start=start, stop=stop, steps=steps, straight=straight, bend=bend
    )

find_route_astar

find_route_astar(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    straight: KCellSpec,
    bend: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Dbu,
) -> PointsDbu

Find an a-star route without adding it to the component.

Parameters:

Name Type Description Default
c ProtoTKCell

the component to find the route in

required
start PortLike

the start port

required
stop PortLike

the stop port

required
straight KCellSpec

the straight-spec to create straights from

required
bend KCellSpec

the bend-spec to create bends from

required
layers Iterable[LayerLike]

the layers to avoid

required
grid_unit Dbu

the discretization unit for the a-star algorithm

required

Returns:

Type Description
PointsDbu

The corners of the route as a list of points in dbu.

Source code in python/doroutes/routing.py
def find_route_astar(
    c: ProtoTKCell,
    start: PortLike,
    stop: PortLike,
    straight: KCellSpec,
    bend: KCellSpec,
    layers: Iterable[LayerLike],
    grid_unit: Dbu,
) -> PointsDbu:
    """Find an a-star route without adding it to the component.

    Args:
        c: the component to find the route in
        start: the start port
        stop: the stop port
        straight: the straight-spec to create straights from
        bend: the bend-spec to create bends from
        layers: the layers to avoid
        grid_unit: the discretization unit for the a-star algorithm

    Returns:
        The corners of the route as a list of points in dbu.

    """
    kc: KCell = util.as_kcell(c)
    _layers = [validate_layer(kc.kcl, layer) for layer in layers]
    width_dbu = util.extract_waveguide_width(kc.kcl, straight)
    radius_dbu = util.extract_bend_radius(kc.kcl, bend)
    if grid_unit > 0.5 * radius_dbu:
        msg = "bend radius should at least be twice the grid unit."
        raise ValueError(msg)
    _grid_unit = int(radius_dbu / int(radius_dbu / grid_unit))
    bbox = kc.bbox()
    straight_width = width_dbu // grid_unit + 1
    straight_width += (straight_width + 1) % 2
    _bend = util.discretize_bend(kc.kcl, bend, _grid_unit, _layers)
    _start = validate_position_with_orientation(start)
    _stop = validate_position_with_orientation(
        stop, invert_orientation=isinstance(stop, Port | DPort)
    )
    return _doroutes.show(
        polys=util.extract_polys(kc, _layers),
        bbox=(
            bbox.top + 2 * radius_dbu,
            bbox.right + 2 * radius_dbu,
            bbox.bottom - 2 * radius_dbu,
            bbox.left - 2 * radius_dbu,
        ),
        start=_start,
        stop=_stop,
        grid_unit=_grid_unit,
        straight_width=straight_width,
        discretized_bend_east_to_north=_bend,
    )

place_auto

place_auto(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec] | list[object],
    *,
    mode: PlacementMode = "auto",
    objective: PlacementObjective | None = None,
    constraints: PlacementConstraints | None = None,
    routing_feedback: RoutingFeedbackConfig | None = None,
    apply: bool = True,
    iterations: int = 30,
) -> PlacementReport

Automatically place instances using openroad-style or tiling mode.

Parameters:

Name Type Description Default
c ProtoTKCell

component/KCell to place instances in.

required
instances list[str] | list[PlacedInstanceSpec]

list of instance names or specs.

required
nets list[PlacementNetSpec] | list[object]

placement netlist — either PlacementNetSpec dicts (pins refer to instance names) or RouteNetSpec objects. When RouteNetSpec objects are given they are auto-converted via derive_placement_nets().

required
mode PlacementMode

"openroad", "tiling", or "auto".

'auto'
objective PlacementObjective | None

objective weights.

None
constraints PlacementConstraints | None

placement constraints.

None
routing_feedback RoutingFeedbackConfig | None

reserved for future routing-in-loop strategies.

None
apply bool

apply placement in-place when True.

True
iterations int

optimizer iteration budget.

30

Returns:

Type Description
PlacementReport

Structured placement report with final locations and metrics.

Source code in python/doroutes/placement.py
def place_auto(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec] | list[object],
    *,
    mode: PlacementMode = "auto",
    objective: PlacementObjective | None = None,
    constraints: PlacementConstraints | None = None,
    routing_feedback: RoutingFeedbackConfig | None = None,
    apply: bool = True,
    iterations: int = 30,
) -> PlacementReport:
    """Automatically place instances using openroad-style or tiling mode.

    Args:
        c: component/KCell to place instances in.
        instances: list of instance names or specs.
        nets: placement netlist — either ``PlacementNetSpec`` dicts (pins
            refer to instance names) or ``RouteNetSpec`` objects.  When
            ``RouteNetSpec`` objects are given they are auto-converted via
            ``derive_placement_nets()``.
        mode: "openroad", "tiling", or "auto".
        objective: objective weights.
        constraints: placement constraints.
        routing_feedback: reserved for future routing-in-loop strategies.
        apply: apply placement in-place when True.
        iterations: optimizer iteration budget.

    Returns:
        Structured placement report with final locations and metrics.

    """
    del routing_feedback  # v1 uses congestion-proxy feedback internally.

    kc = _as_kcell(c)

    constraints = constraints or {}
    objective = objective or {}

    # Auto-convert RouteNetSpec objects to PlacementNetSpec dicts.
    if nets and hasattr(nets[0], "start") and hasattr(nets[0], "stop"):
        # Collect instance names for derive_placement_nets.
        inst_names: list[str] = []
        if instances and isinstance(instances[0], str):
            inst_names = list(instances)  # type: ignore[arg-type]
        else:
            inst_names = [spec["name"] for spec in instances]  # type: ignore[index]
        nets = derive_placement_nets(nets, inst_names)  # type: ignore[arg-type]

    pnets = cast(list[PlacementNetSpec], nets)

    # Normalize instance specs.
    inst_specs: list[PlacedInstanceSpec] = []
    if instances and isinstance(instances[0], str):
        inst_specs = [{"name": str(name)} for name in instances]  # type: ignore[arg-type]
    else:
        inst_specs = list(instances)  # type: ignore[assignment]

    rust_instances: list[tuple[str, int, int, int, int, bool, int, str]] = []
    name_to_idx: dict[str, int] = {}
    current_centers: dict[str, tuple[int, int]] = {}
    inst_sizes: dict[str, tuple[int, int]] = {}

    for idx, spec in enumerate(inst_specs):
        name = spec["name"]
        try:
            inst = kc.insts[name]
        except (
            Exception
        ) as e:  # pragma: no cover - kfactory error type depends on backend
            msg = f"Instance '{name}' not found in component."
            raise ValueError(msg) from e

        x, y, width, height = _instance_center_and_size(inst)
        fixed = bool(spec.get("fixed", False))
        keepout = int(spec.get("keepout", 0))
        group = str(spec.get("group", ""))
        rust_instances.append((name, x, y, width, height, fixed, keepout, group))
        name_to_idx[name] = idx
        current_centers[name] = (x, y)
        inst_sizes[name] = (width, height)

    # Normalize net specs to rust format: pins reference instance indices.
    rust_nets: list[tuple[str, list[tuple[int, float]], float]] = []
    for net in pnets:
        pin_refs: list[tuple[int, float]] = []
        net_weight = float(net.get("weight", 1.0))
        for pin in net["pins"]:
            inst_name = _pin_to_instance_name(pin)
            if inst_name not in name_to_idx:
                msg = f"Net '{net['name']}' references unknown instance '{inst_name}'."
                raise ValueError(msg)
            pin_refs.append((name_to_idx[inst_name], 1.0))
        if len(pin_refs) >= 2:
            rust_nets.append((net["name"], pin_refs, net_weight))

    if "bbox" in constraints:
        bbox = constraints["bbox"]
    else:
        cb = kc.bbox()
        bbox = (int(cb.top), int(cb.right), int(cb.bottom), int(cb.left))

    if not hasattr(_doroutes, "place_batch"):
        msg = (
            "doroutes extension does not expose place_batch yet. "
            "Rebuild the Rust extension (e.g. `maturin develop`) and retry."
        )
        raise RuntimeError(msg)

    placements, mode_used, hpwl, congestion, density_ovf, legalized, iter_metrics = (
        _doroutes.place_batch(
            instances=rust_instances,
            nets=rust_nets,
            bbox=bbox,
            mode=mode,
            min_spacing=int(constraints.get("min_spacing", 0)),
            density_target=float(constraints.get("density_target", 0.7)),
            iterations=max(1, int(iterations)),
            hpwl_weight=float(objective.get("hpwl_weight", 1.0)),
            congestion_weight=float(objective.get("congestion_weight", 1.2)),
            density_weight=float(objective.get("density_weight", 1.0)),
            displacement_weight=float(objective.get("displacement_weight", 0.15)),
            grouping_weight=float(objective.get("grouping_weight", 0.0)),
            timing_driven=bool(objective.get("timing_driven", False)),
            routability_driven=bool(objective.get("routability_driven", False)),
            max_displacement=int(constraints.get("max_displacement", 0)),
        )
    )

    placement_map = {name: (x, y, orient) for name, x, y, orient in placements}

    # Post-process: abut same-group instances with min_spacing gap.
    placement_map = _abut_groups(
        placement_map,
        inst_specs,
        inst_sizes,
        min_gap=int(constraints.get("min_spacing", 0)),
    )

    if apply:
        inv_dbu = util.get_inv_dbu(kc.kcl)
        for name, (x, y, _orient) in placement_map.items():
            inst = kc.insts[name]
            curx, cury = current_centers[name]
            dx = x - curx
            dy = y - cury
            inst.dmove((float(dx / inv_dbu), float(dy / inv_dbu)))

    return {
        "mode_used": mode_used,
        "placements": placement_map,
        "hpwl": float(hpwl),
        "congestion": float(congestion),
        "density_overflow": float(density_ovf),
        "legalized": bool(legalized),
        "iterations": [
            (int(i), float(h), float(cg), float(d)) for i, h, cg, d in iter_metrics
        ],
    }

place_openroad_style

place_openroad_style(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec],
    **kwargs: Any,
) -> PlacementReport

Run place_auto with mode="openroad".

Source code in python/doroutes/placement.py
def place_openroad_style(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec],
    **kwargs: Any,
) -> PlacementReport:
    """Run `place_auto` with ``mode="openroad"``."""
    return place_auto(c, instances, nets, mode="openroad", **kwargs)

place_tiling

place_tiling(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec],
    **kwargs: Any,
) -> PlacementReport

Run place_auto with mode="tiling".

Source code in python/doroutes/placement.py
def place_tiling(
    c: ProtoTKCell,
    instances: list[str] | list[PlacedInstanceSpec],
    nets: list[PlacementNetSpec],
    **kwargs: Any,
) -> PlacementReport:
    """Run `place_auto` with ``mode="tiling"``."""
    return place_auto(c, instances, nets, mode="tiling", **kwargs)