freeroute/tests/route/test_diagonal.py
Ryan Malloy accec686f8 Prove the 45-degree win: 29% shorter, DRC-clean, exact clearance load-bearing
Add diagonal_win.dsn: a 45-degree pin-to-pin net with a keepout placed inside the
diagonal's bounding box but clear of the true thin copper. Orthogonal routing
connects it with a Manhattan-length staircase (1600000); diagonal routing
replaces that with a single 45-degree trace at the ideal hypotenuse length
(1131371) -- a 29% reduction -- DRC-clean via the search tree and via an
independent reconstruction of the emitted 45-degree copper's clearance octagon.

test_exact_clearance_is_load_bearing shows the keepout overlaps the coarse
bounding box but not the exact octagon, so the exact clearance (not the box) is
what lets the diagonal route. Determinism, endpoints-on-pads, valid SES, and
diagonal-off-equals-orthogonal (additive, over four fixtures) are covered.
2026-07-13 12:57:13 -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 freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import IntBox, IntPoint, overlaps_2d, segment_octagon
from freeroute.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 freeroute.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