Files
rpmroute/tests/route/test_room_router.py
T
rsp2k a6aa1991a1 Rename to rpmroute
The router began as a port of FreeRouting and has grown its own engine
(negotiated congestion, tree nets, exact checks, axis escapes), and
'freeroute' was one letter from the project it no longer follows. The
package, module and command are now rpmroute; the freeroute command stays
as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back
to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a
PCBench board routes to a byte-identical session either way.
2026-10-07 14:10:36 -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 rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import Polyline
from rpmroute.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 rpmroute.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