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.
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
"""Exact-arithmetic tests for points and vectors.
|
||
|
||
Oracles are hand-derived from the geometry defined in FreeRouting's
|
||
``geometry/planar/{IntPoint,IntVector,RationalPoint,RationalVector}.java``.
|
||
Emphasis is on exactness: collinearity, large coordinates that would overflow a
|
||
Java ``long``, and rational equality.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from rpmroute.geometry import (
|
||
IntPoint,
|
||
IntVector,
|
||
RationalPoint,
|
||
RationalVector,
|
||
Side,
|
||
Signum,
|
||
point,
|
||
rational_point,
|
||
)
|
||
|
||
# --- IntVector determinant / side_of ----------------------------------------
|
||
|
||
|
||
def test_determinant_exact():
|
||
# determinant of (1,0),(0,1) = 1
|
||
assert IntVector(1, 0).determinant(IntVector(0, 1)) == 1
|
||
assert IntVector(0, 1).determinant(IntVector(1, 0)) == -1
|
||
assert IntVector(2, 3).determinant(IntVector(4, 6)) == 0 # parallel
|
||
|
||
|
||
def test_determinant_exact_beyond_java_long():
|
||
# 10**12 * 10**12 = 10**24 overflows a Java long (~9.2e18); Python int is exact.
|
||
a = IntVector(10**12, 0)
|
||
b = IntVector(0, 10**12)
|
||
assert a.determinant(b) == 10**24
|
||
|
||
|
||
def test_side_of_collinear_left_right():
|
||
# line from origin toward (1,0): +y is left, -y is right, on axis collinear.
|
||
right_dir = IntVector(1, 0)
|
||
assert IntVector(0, 1).side_of(right_dir) == Side.ON_THE_LEFT
|
||
assert IntVector(0, -1).side_of(right_dir) == Side.ON_THE_RIGHT
|
||
assert IntVector(5, 0).side_of(right_dir) == Side.COLLINEAR
|
||
|
||
|
||
def test_side_of_exact_near_collinear():
|
||
# (10**9, 10**9) vs (10**9, 10**9 + 1): a hair off collinear, exact int detects it.
|
||
base = IntVector(10**9, 10**9)
|
||
assert IntVector(10**9, 10**9 + 1).side_of(base) != Side.COLLINEAR
|
||
assert IntVector(2 * 10**9, 2 * 10**9).side_of(base) == Side.COLLINEAR
|
||
|
||
|
||
def test_vector_add_and_negate():
|
||
assert IntVector(1, 2).add(IntVector(3, 4)) == IntVector(4, 6)
|
||
assert IntVector(1, 2).negate() == IntVector(-1, -2)
|
||
|
||
|
||
def test_turn_90_degree():
|
||
v = IntVector(1, 0)
|
||
assert v.turn_90_degree(1) == IntVector(0, 1)
|
||
assert v.turn_90_degree(2) == IntVector(-1, 0)
|
||
assert v.turn_90_degree(3) == IntVector(0, -1)
|
||
assert v.turn_90_degree(4) == IntVector(1, 0)
|
||
assert v.turn_90_degree(-1) == IntVector(0, -1)
|
||
|
||
|
||
def test_projection_signum():
|
||
assert IntVector(1, 0).projection(IntVector(1, 0)) == Signum.POSITIVE
|
||
assert IntVector(1, 0).projection(IntVector(-1, 0)) == Signum.NEGATIVE
|
||
assert IntVector(1, 0).projection(IntVector(0, 1)) == Signum.ZERO
|
||
|
||
|
||
def test_orthogonal_diagonal():
|
||
assert IntVector(3, 0).is_orthogonal()
|
||
assert IntVector(0, -4).is_orthogonal()
|
||
assert IntVector(2, 2).is_diagonal()
|
||
assert IntVector(-5, 5).is_diagonal()
|
||
assert IntVector(1, 2).is_multiple_of_45_degree() is False
|
||
|
||
|
||
# --- IntPoint ---------------------------------------------------------------
|
||
|
||
|
||
def test_point_difference_and_translate():
|
||
p = IntPoint(3, 4)
|
||
q = IntPoint(1, 1)
|
||
d = p.difference_by(q)
|
||
assert isinstance(d, IntVector)
|
||
assert d == IntVector(2, 3)
|
||
assert q.translate_by(d) == p
|
||
|
||
|
||
def test_point_side_of_directed_line():
|
||
p0 = IntPoint(0, 0)
|
||
p1 = IntPoint(2, 2)
|
||
assert IntPoint(1, 1).side_of(p0, p1) == Side.COLLINEAR
|
||
assert IntPoint(0, 1).side_of(p0, p1) == Side.ON_THE_LEFT
|
||
assert IntPoint(1, 0).side_of(p0, p1) == Side.ON_THE_RIGHT
|
||
|
||
|
||
def test_point_compare():
|
||
assert IntPoint(1, 5).compare_x(IntPoint(3, 2)) == -1
|
||
assert IntPoint(3, 5).compare_x(IntPoint(3, 2)) == 0
|
||
assert IntPoint(3, 5).compare_y(IntPoint(3, 2)) == 1
|
||
assert IntPoint(3, 2).compare_x_y(IntPoint(3, 5)) == -1 # x tie, y smaller
|
||
|
||
|
||
def test_point_turn_90_around_pole():
|
||
pole = IntPoint(1, 1)
|
||
# (2,1) rotated 90 about (1,1) -> (1,2)
|
||
assert IntPoint(2, 1).turn_90_degree(1, pole) == IntPoint(1, 2)
|
||
|
||
|
||
# --- RationalPoint / factory ------------------------------------------------
|
||
|
||
|
||
def test_rational_point_equality_scale_invariant():
|
||
assert RationalPoint(3, 2, 2) == RationalPoint(6, 4, 4)
|
||
assert RationalPoint(3, 2, 2) != RationalPoint(3, 2, 3)
|
||
|
||
|
||
def test_rational_point_negative_denominator_normalized_via_factory():
|
||
p = rational_point(3, 2, -2) # -> (−3/−2)=... factory flips sign -> (-3,-2,2)
|
||
assert isinstance(p, RationalPoint)
|
||
assert p == RationalPoint(-3, -2, 2)
|
||
|
||
|
||
def test_rational_point_reduces_to_int_when_divisible():
|
||
p = rational_point(6, 4, 2)
|
||
assert isinstance(p, IntPoint)
|
||
assert p == IntPoint(3, 2)
|
||
|
||
|
||
def test_rational_point_infinite():
|
||
inf = rational_point(1, 1, 0)
|
||
assert isinstance(inf, RationalPoint)
|
||
assert inf.is_infinite()
|
||
|
||
|
||
def test_rational_point_contained_in_box_exact():
|
||
from rpmroute.geometry import IntBox
|
||
|
||
box = IntBox(0, 0, 2, 2)
|
||
assert RationalPoint(3, 2, 2).is_contained_in(box) # (1.5, 1.0) inside
|
||
assert not RationalPoint(5, 2, 2).is_contained_in(box) # (2.5, 1.0) outside
|
||
|
||
|
||
def test_int_and_rational_difference_promotes():
|
||
p = IntPoint(2, 2)
|
||
q = RationalPoint(3, 2, 2) # (1.5, 1.0)
|
||
d = p.difference_by(q)
|
||
assert isinstance(d, RationalVector)
|
||
# (2,2) - (1.5,1.0) = (0.5, 1.0) == (1,2,2)
|
||
assert d == RationalVector(1, 2, 2)
|
||
|
||
|
||
def test_factory_point_is_intpoint_even_for_huge_coords():
|
||
p = point(10**15, -(10**15))
|
||
assert isinstance(p, IntPoint)
|
||
assert (p.x, p.y) == (10**15, -(10**15))
|