freeroute/tests/route/test_room_router.py
Ryan Malloy a06adbee13 Add shove-in-rooms and gate-placement fixtures and tests
rooms_shove_channel is the headline density win: a sealed NET_A wall
leaves no top<->bottom door, so plain room routing drops NET_C; with
shove=True NET_A is nudged aside and both nets route, DRC-clean and
deterministic. rooms_wide_door seats two nets through one wide gap at
distinct projected gate points with no shove.

Cover the occupancy-aware projection math directly, assert the four
foundation fixtures stay byte-identical under shove=True (shoves==0), and
extend the room DRC-clean parametrize with both new fixtures.
2026-07-13 10:27:17 -06:00

122 lines
4.3 KiB
Python

"""Tests for the continuous expansion-room routing track.
The room router decomposes free space into exact rectangles (rooms) joined by
doors, instead of a fixed grid. The headline result is the *continuity win*: a
board with a narrow, off-grid channel that the grid/exact track cannot route
(no cell centre lands in the channel) but the room track can.
"""
from __future__ import annotations
from pathlib import Path
import sys
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import Polyline
from freeroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
SIMPLE = FIXTURES / "simple_2net.dsn"
CROSSING = FIXTURES / "crossing_2net.dsn"
KICAD = FIXTURES / "kicad_routable.dsn"
NARROW = FIXTURES / "narrow_channel.dsn"
SHOVE_CHANNEL = FIXTURES / "rooms_shove_channel.dsn"
WIDE_DOOR = FIXTURES / "rooms_wide_door.dsn"
def _clearance(dsn, scale):
from freeroute.route.pipeline import _rule_clearance_dsn
return round(_rule_clearance_dsn(dsn) * scale)
# --- DRC-clean invariant (same non-negotiable as the exact track) -----------
@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING, KICAD, NARROW, SHOVE_CHANNEL, WIDE_DOOR])
def test_room_router_output_is_drc_clean(fixture):
dsn = parse_dsn(fixture.read_text())
rooms, scale, _ = route_dsn_board_rooms(dsn)
assert rooms.drc_clean
assert rooms.tree.has_violation(_clearance(dsn, scale)) is None
# --- connectivity + geometry ------------------------------------------------
def test_room_router_connects_simple_and_crossing():
for fixture in (SIMPLE, CROSSING):
rooms, _, _ = route_dsn_board_rooms(parse_dsn(fixture.read_text()))
assert rooms.routed_net_numbers == {1, 2}
def test_room_router_uses_a_via_on_the_crossing_board():
rooms, _, _ = route_dsn_board_rooms(parse_dsn(CROSSING.read_text()))
assert rooms.via_count() >= 1
def test_room_router_routes_the_real_boards_multipin_nets():
dsn = parse_dsn(KICAD.read_text())
rooms, _, _ = route_dsn_board_rooms(dsn)
multi = sum(1 for n in dsn.nets if len(n.pins) >= 2)
assert len(rooms.routed_net_numbers) == multi == 4
def test_room_traces_are_orthogonal_and_end_on_pads():
rooms, _, _ = route_dsn_board_rooms(parse_dsn(SIMPLE.read_text()))
board = build_board(parse_dsn(SIMPLE.read_text()))
pins_by_net: dict[int, set[tuple[int, int]]] = {}
for pin in board.get_pins():
for net_no in pin.net_nos:
pins_by_net.setdefault(net_no, set()).add((pin.location.x, pin.location.y))
for net_no, segments in rooms.result.wires.items():
pts = [(p.x, p.y) for _, seg in segments for p in seg]
assert {pts[0], pts[-1]} == pins_by_net[net_no]
for _, points in segments:
for a, b in Polyline(points).segments():
assert a.x == b.x or a.y == b.y
def test_room_engine_emits_valid_ses():
ses = route(SIMPLE.read_text(), engine="room")
assert parse(ses).head == "session"
# --- the headline: continuity beats the grid --------------------------------
def test_room_router_routes_a_channel_the_grid_cannot():
dsn = parse_dsn(NARROW.read_text())
# the exact/grid track cannot fit a centreline in the narrow, off-grid gap
exact, scale, _ = route_dsn_board_exact(dsn)
assert exact.routed_net_numbers == set() # grid quantization drops the net
assert exact.drc_clean
# the continuous room track routes it — and stays DRC-clean
rooms, _, _ = route_dsn_board_rooms(dsn)
assert rooms.routed_net_numbers == {1}
assert rooms.drc_clean
assert rooms.tree.has_violation(_clearance(dsn, scale)) is None
# --- oracle parity ----------------------------------------------------------
@pytest.mark.oracle
@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING])
def test_room_connectivity_parity_with_oracle(fixture):
from oracle import HAS_ORACLE, route_dsn, routed_net_set
if not HAS_ORACLE:
pytest.skip("FreeRouting oracle unavailable")
ours = routed_net_set(route(fixture.read_text(), engine="room"))
theirs = routed_net_set(route_dsn(fixture, max_passes=3, timeout=300))
assert theirs, "oracle routed nothing on a routable board"
assert theirs <= ours