Files
rpmroute/tests/geometry/test_point_vector.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

162 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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))