Files
rpmroute/tests/route/test_diagonal.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

202 lines
7.8 KiB
Python

"""Tests for 45-degree (diagonal) routing on the exact track.
The orthogonal exact router connects a 45-degree pin-to-pin net with a Manhattan
staircase (length ~ dx + dy). ``diagonal=True`` replaces it with a single
45-degree trace (length ~ hypot(dx, dy)) whose copper clearance is checked with
the exact integer octagon, not the coarse bounding box -- so it fits past an
obstacle that sits inside the diagonal's bounding box but clears the true thin
copper. ``diagonal_win.dsn`` places exactly such an obstacle, making the exact
clearance load-bearing rather than cosmetic.
"""
from __future__ import annotations
import math
from pathlib import Path
import pytest
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import IntBox, IntPoint, overlaps_2d, segment_octagon
from rpmroute.route import route, route_dsn_board_exact
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
DIAGONAL_WIN = FIXTURES / "diagonal_win.dsn"
SIMPLE = FIXTURES / "simple_2net.dsn"
CROSSING = FIXTURES / "crossing_2net.dsn"
KICAD = FIXTURES / "kicad_routable.dsn"
def _length(result):
return sum(
math.hypot(q.x - p.x, q.y - p.y)
for _n, segs in result.result.wires.items()
for _layer, pts in segs
for p, q in zip(pts, pts[1:], strict=False)
)
def _wire_key(result):
return {
n: [(layer, [(p.x, p.y) for p in pts]) for layer, pts in segs]
for n, segs in result.result.wires.items()
}
def _pins_by_net(fixture):
board = build_board(parse_dsn(fixture.read_text()))
out: dict[int, set[tuple[int, int]]] = {}
for pin in board.get_pins():
for net_no in pin.net_nos:
out.setdefault(net_no, set()).add((pin.location.x, pin.location.y))
return out
def _rule_clearance_board(fixture):
from rpmroute.route.pipeline import _rule_clearance_dsn
dsn = parse_dsn(fixture.read_text())
return round(_rule_clearance_dsn(dsn) * max(dsn.resolution.value, 1))
# --- the falsifiable length win ----------------------------------------------
def test_orthogonal_baseline_is_manhattan_length():
"""Orthogonal (diagonal off) connects the net with a Manhattan-length route --
the staircase the exact grid produces for a 45-degree ratsnest."""
dsn = parse_dsn(DIAGONAL_WIN.read_text())
ortho, _, _ = route_dsn_board_exact(dsn)
assert ortho.routed_net_numbers == {1}
# pins are 80000 x 80000 DSN units apart -> Manhattan = 160000, scaled x10
assert _length(ortho) == pytest.approx(1_600_000.0)
def test_diagonal_is_strictly_shorter_and_ideal():
"""Diagonal on connects the same net with a single 45-degree trace at the ideal
hypotenuse length -- strictly shorter than the orthogonal staircase."""
dsn = parse_dsn(DIAGONAL_WIN.read_text())
ortho, _, _ = route_dsn_board_exact(dsn)
diag, _, _ = route_dsn_board_exact(dsn, diagonal=True)
assert diag.routed_net_numbers == {1}
ideal = math.hypot(800_000, 800_000) # board units
assert _length(diag) == pytest.approx(ideal)
assert _length(diag) < _length(ortho)
# a real, sizeable win, not a rounding artifact
assert _length(diag) / _length(ortho) < 0.72
def test_diagonal_route_is_a_single_45_degree_segment():
dsn = parse_dsn(DIAGONAL_WIN.read_text())
diag, _, _ = route_dsn_board_exact(dsn, diagonal=True)
segs = diag.result.wires[1]
assert len(segs) == 1
_layer, pts = segs[0]
assert len(pts) == 2
(ax, ay), (bx, by) = (pts[0].x, pts[0].y), (pts[1].x, pts[1].y)
assert abs(bx - ax) == abs(by - ay) != 0 # exactly 45 degrees
def test_diagonal_endpoints_stay_on_pads():
dsn = parse_dsn(DIAGONAL_WIN.read_text())
diag, _, _ = route_dsn_board_exact(dsn, diagonal=True)
pins = _pins_by_net(DIAGONAL_WIN)
for net_no, segments in diag.result.wires.items():
pts = [(p.x, p.y) for _layer, run in segments for p in run]
assert {pts[0], pts[-1]} == pins[net_no]
# --- DRC-clean, verified exactly and independently ---------------------------
def test_diagonal_is_drc_clean_via_tree():
dsn = parse_dsn(DIAGONAL_WIN.read_text())
clearance = _rule_clearance_board(DIAGONAL_WIN)
diag, _, _ = route_dsn_board_exact(dsn, diagonal=True)
assert diag.drc_clean
assert diag.tree.has_violation(clearance) is None
def test_diagonal_is_drc_clean_independently_of_the_tree():
"""Reconstruct the 45-degree copper's clearance octagon from the emitted route
and confirm it clears every different-net static copper (pads, the keepout) --
a check that never consults the search tree the router verified against."""
dsn = parse_dsn(DIAGONAL_WIN.read_text())
board = build_board(dsn)
clearance = _rule_clearance_board(DIAGONAL_WIN)
half_width = 10_000 # trace_width 2000 * scale 10 / 2
diag, _, _ = route_dsn_board_exact(dsn, diagonal=True)
# different-net static copper: the keepout (net -1); pads share NET_A here
obstacles = []
for obstacle in board.get_obstacle_areas():
for tile in obstacle.tiles:
obstacles.append(tile.bounding_box())
assert diag.result.wires # sanity: the diagonal net was actually emitted
conflicts = 0
for _net_no, segments in diag.result.wires.items():
for _layer, pts in segments:
for p, q in zip(pts, pts[1:], strict=False):
grown = segment_octagon(p, q, half_width + clearance)
for box in obstacles:
if overlaps_2d(grown, box):
conflicts += 1
assert conflicts == 0
def test_exact_clearance_is_load_bearing():
"""The keepout sits INSIDE the diagonal's bounding box (so the coarse bbox
clearance the plain router uses would reject the diagonal) yet clears the exact
octagon -- proving the octagon, not the box, is what lets the trace route."""
a, b = IntPoint(200_000, -200_000), IntPoint(1_000_000, -1_000_000)
half_width, clearance = 10_000, 20_000
bbox = IntBox(a.x - half_width, b.y - half_width, b.x + half_width, a.y + half_width)
keepout = IntBox(220_000, -980_000, 400_000, -800_000)
assert not keepout.intersection(bbox).is_empty() # inside the coarse cover
assert not overlaps_2d(segment_octagon(a, b, half_width + clearance), keepout) # clear exactly
def test_diagonal_is_deterministic():
dsn = parse_dsn(DIAGONAL_WIN.read_text())
a, _, _ = route_dsn_board_exact(dsn, diagonal=True)
b, _, _ = route_dsn_board_exact(dsn, diagonal=True)
assert _wire_key(a) == _wire_key(b)
assert a.routed_net_numbers == b.routed_net_numbers
def test_diagonal_valid_ses():
ses = route(DIAGONAL_WIN.read_text(), engine="exact", diagonal=True)
top = parse(ses)
assert top.head == "session"
nets = top.child("routes").child("network_out").children("net")
assert {n.values()[0].text for n in nets} == {"NET_A"}
# --- additive: diagonal off changes nothing on the orthogonal track ----------
@pytest.mark.parametrize("fixture", [DIAGONAL_WIN, SIMPLE, CROSSING, KICAD])
def test_diagonal_off_matches_orthogonal(fixture):
dsn = parse_dsn(fixture.read_text())
base, _, _ = route_dsn_board_exact(dsn)
off, _, _ = route_dsn_board_exact(dsn, diagonal=False)
assert _wire_key(base) == _wire_key(off)
@pytest.mark.parametrize("fixture", [SIMPLE, CROSSING, KICAD])
def test_diagonal_on_keeps_existing_fixtures_clean_and_connected(fixture):
"""Turning diagonal on never dirties the DRC or drops a net that orthogonal
routed -- it only ever shortens."""
dsn = parse_dsn(fixture.read_text())
clearance = _rule_clearance_board(fixture)
base, _, _ = route_dsn_board_exact(dsn)
on, _, _ = route_dsn_board_exact(dsn, diagonal=True)
assert on.drc_clean
assert on.tree.has_violation(clearance) is None
assert on.routed_net_numbers >= base.routed_net_numbers
assert _length(on) <= _length(base) + 1e-6