"""Invariant tests for TileShape / Simplex. There is no external oracle (no JVM; FreeRouting ships no unit tests for ``geometry.planar``), so these assert properties that must hold regardless of implementation, per the phase-2 brief: * corners lie exactly on their two border lines (exact ``Line.side_of == 0``), * ``IntBox -> Simplex -> region`` preserves the region, * ``Simplex.intersection`` is contained in both operands and a point is in the result iff it is in both, * ``get_instance`` normalization drops redundant lines without changing the region. Source: ``geometry/planar/{TileShape,Simplex}.java``. """ from __future__ import annotations from freeroute.geometry import IntBox, IntPoint, Line, Side, Simplex def _grid(lo: int, hi: int): for x in range(lo, hi + 1): for y in range(lo, hi + 1): yield IntPoint(x, y) def right_triangle() -> Simplex: # legs of length 10 along the axes; hypotenuse x + y = 10 return Simplex.get_instance( [ Line.from_coords(0, 0, 10, 0), Line.from_coords(10, 0, 0, 10), Line.from_coords(0, 10, 0, 0), ] ) def unit_box_simplex() -> Simplex: return IntBox(0, 0, 10, 10).to_simplex() # --- corners lie exactly on their border lines ------------------------------ def test_corners_are_exact_on_border_lines(): for shape in (unit_box_simplex(), right_triangle()): n = shape.border_line_count() for i in range(n): corner = shape.corner(i) prev = shape.border_line(n - 1 if i == 0 else i - 1) curr = shape.border_line(i) # the corner is the exact intersection of these two lines assert curr.side_of(corner) == Side.COLLINEAR assert prev.side_of(corner) == Side.COLLINEAR # --- IntBox -> Simplex -> region round trip --------------------------------- def test_box_to_simplex_preserves_region(): box = IntBox(-3, 2, 7, 11) s = box.to_simplex() assert s.bounding_box() == box for p in _grid(-6, 14): assert box.contains(p) == s.contains(p) def test_box_to_simplex_is_a_box_and_simplifies_back(): box = IntBox(0, 0, 10, 10) s = box.to_simplex() assert s.is_int_box() assert s.simplify() == box # --- containment semantics -------------------------------------------------- def test_triangle_containment(): tri = right_triangle() assert tri.contains(IntPoint(0, 0)) # corner assert tri.contains(IntPoint(5, 5)) # on hypotenuse assert tri.contains_inside(IntPoint(2, 2)) assert not tri.contains_inside(IntPoint(5, 5)) # on border, not inside assert not tri.contains(IntPoint(6, 6)) # x+y=12 > 10 assert tri.contains_on_border(IntPoint(3, 0)) # on the bottom edge def test_triangle_area_and_bounds(): tri = right_triangle() assert tri.is_bounded() assert tri.dimension() == 2 assert tri.area() == 50.0 assert tri.bounding_box() == IntBox(0, 0, 10, 10) # --- intersection invariants ------------------------------------------------ def test_intersection_contained_in_both_and_iff(): a = IntBox(0, 0, 12, 8).to_simplex() b = right_triangle() # x,y >= 0, x+y <= 10 inter = a.intersection(b) assert not inter.is_empty() for p in _grid(-3, 15): in_both = a.contains(p) and b.contains(p) in_inter = inter.contains(p) assert in_inter == in_both # every corner of the intersection lies inside both operands for i in range(inter.border_line_count()): c = inter.corner(i) assert a.contains(c) assert b.contains(c) def test_intersection_of_two_boxes_matches_box_intersection(): a_box = IntBox(0, 0, 10, 10) b_box = IntBox(4, -2, 20, 6) inter = a_box.to_simplex().intersection(b_box.to_simplex()) expected = a_box.intersection(b_box) for p in _grid(-5, 22): assert inter.contains(p) == expected.contains(p) def test_disjoint_intersection_is_empty(): a = IntBox(0, 0, 3, 3).to_simplex() b = IntBox(10, 10, 13, 13).to_simplex() assert a.intersection(b).is_empty() assert not a.intersects(b) def test_intersection_is_idempotent_with_self(): tri = right_triangle() inter = tri.intersection(tri) for p in _grid(-3, 13): assert inter.contains(p) == tri.contains(p) # --- normalization (get_instance drops redundant lines) --------------------- def test_get_instance_removes_redundant_line(): # a unit box plus a far-away redundant half-plane that does not cut it. # A DOWN-directed vertical line has its interior to the east, so this is # x >= -100, which contains the whole box and is therefore redundant. box_lines = list(IntBox(0, 0, 10, 10).to_simplex()._arr) redundant = Line.from_coords(-100, 1, -100, 0) # x >= -100 s = Simplex.get_instance(box_lines + [redundant]) assert s.border_line_count() == 4 # redundant line dropped for p in _grid(-5, 15): assert s.contains(p) == IntBox(0, 0, 10, 10).contains(p) def test_get_instance_detects_empty_from_opposing_halfplanes(): # x >= 5 (DOWN line, interior east) and x <= 0 (UP line, interior west) # cannot both hold. ge5 = Line.from_coords(5, 1, 5, 0) # DOWN -> interior x >= 5 le0 = Line.from_coords(0, 0, 0, 1) # UP -> interior x <= 0 s = Simplex.get_instance([ge5, le0]) assert s.is_empty() def test_translate_preserves_shape(): tri = right_triangle() from freeroute.geometry import IntVector moved = tri.translate_by(IntVector(100, 50)) for p in _grid(-3, 13): assert tri.contains(p) == moved.contains(IntPoint(p.x + 100, p.y + 50)) def test_offset_outward_enlarges_region(): box = IntBox(0, 0, 10, 10).to_simplex() bigger = box.offset(2) # every point of the original box is still contained after outward offset for p in _grid(0, 10): assert bigger.contains(p) # a point 2 outside the original right edge is now contained assert bigger.contains(IntPoint(12, 5)) assert not box.contains(IntPoint(12, 5)) def test_empty_simplex_predicates(): e = Simplex.empty() assert e.is_empty() assert e.dimension() == -1 assert e.is_outside(IntPoint(0, 0)) assert not e.contains(IntPoint(0, 0))