File size: 2,330 Bytes
eab734a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""Solver unit tests."""

from __future__ import annotations

import math

import pytest

from helpers import load_fixture

from statement_to_tikz.ir import SolveMode
from statement_to_tikz.pipeline import solve_with_fallback
from statement_to_tikz.solve import evaluate_residuals, solve_geometry


@pytest.mark.parametrize(
    "fixture",
    [
        "equilateral.json",
        "isosceles_median.json",
        "right_altitude.json",
        "parallelogram.json",
        "circumcenter.json",
        "angle_bisector.json",
    ],
)
def test_exact_fixtures(fixture: str) -> None:
    ir, expected = load_fixture(fixture)
    assert expected == "exact"
    scene = solve_geometry(ir)
    assert scene.mode == SolveMode.exact, scene.message
    assert scene.max_residual <= 1e-4


def test_equilateral_side_lengths() -> None:
    ir, _ = load_fixture("equilateral.json")
    scene = solve_geometry(ir)
    a = scene.coordinates["A"]
    b = scene.coordinates["B"]
    c = scene.coordinates["C"]

    def dist(p, q):
        return math.hypot(p[0] - q[0], p[1] - q[1])

    assert abs(dist(a, b) - 1.0) < 1e-3
    assert abs(dist(b, c) - 1.0) < 1e-3
    assert abs(dist(c, a) - 1.0) < 1e-3


def test_midpoint_property() -> None:
    ir, _ = load_fixture("isosceles_median.json")
    scene = solve_geometry(ir)
    b, c, d = (scene.coordinates[k] for k in ("B", "C", "D"))
    assert abs(d[0] - 0.5 * (b[0] + c[0])) < 1e-3
    assert abs(d[1] - 0.5 * (b[1] + c[1])) < 1e-3
    a = scene.coordinates["A"]
    # non-degenerate: A not on BC
    area = abs((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]))
    assert area > 0.5


def test_underconstrained_becomes_schematic() -> None:
    ir, expected = load_fixture("underconstrained_triangle.json")
    assert expected == "schematic"
    scene = solve_with_fallback(ir)
    assert scene.mode == SolveMode.schematic
    assert set(scene.coordinates) >= {"A", "B", "C"}


def test_conflicting_falls_back_schematic() -> None:
    ir, expected = load_fixture("conflicting_lengths.json")
    exact = solve_geometry(ir)
    assert exact.mode != SolveMode.exact
    scene = solve_with_fallback(ir)
    assert scene.mode == SolveMode.schematic
    # residuals remain large for conflicting lengths
    res = evaluate_residuals(ir, scene.coordinates)
    assert max(res) > 0.1