File size: 3,698 Bytes
4de02b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# geometry_types.py - V1.0 (Geometric Sanity Engine — Type Contracts)
# CTO NOTE: All coordinate arithmetic uses SymPy Rational to guarantee
# exact arithmetic — no float drift, no rounding surprises.

from dataclasses import dataclass, field
from sympy import Rational, simplify, sqrt
import logging

logger = logging.getLogger(__name__)


@dataclass
class Point:
    name: str
    x: object  # SymPy expression or numeric
    y: object

    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return (simplify(self.x - other.x) == 0 and
                simplify(self.y - other.y) == 0)

    def __repr__(self):
        return f"{self.name}({self.x}, {self.y})"

    def distance_to(self, other: "Point"):
        """Exact SymPy distance between two points."""
        return sqrt((self.x - other.x)**2 + (self.y - other.y)**2)


@dataclass
class Line:
    """Represents the line ax + by + c = 0."""
    a: object
    b: object
    c: object

    def y_at(self, x_val):
        """Compute y given x (assuming b ≠ 0)."""
        if simplify(self.b) == 0:
            raise ValueError("[Line.y_at] Vertical line — b=0, y is undefined for a given x.")
        return Rational(-self.a * x_val - self.c, self.b)

    def contains(self, p: "Point") -> bool:
        """Returns True if point p satisfies ax + by + c = 0."""
        val = simplify(self.a * p.x + self.b * p.y + self.c)
        return val == 0

    def __repr__(self):
        return f"Line({self.a}x + {self.b}y + {self.c} = 0)"


@dataclass
class Segment:
    p1: Point
    p2: Point

    def midpoint(self) -> Point:
        """Returns the midpoint with exact rational arithmetic."""
        from sympy import Rational as R
        mid_x = (self.p1.x + self.p2.x) / 2
        mid_y = (self.p1.y + self.p2.y) / 2
        return Point(
            name=f"mid_{self.p1.name}{self.p2.name}",
            x=simplify(mid_x),
            y=simplify(mid_y)
        )

    def length(self):
        """Exact SymPy length of the segment."""
        return self.p1.distance_to(self.p2)

    def __repr__(self):
        return f"Segment({self.p1.name}{self.p2.name})"


@dataclass
class Circle:
    center: Point
    radius: object  # SymPy expression (can be symbolic)

    def contains(self, p: Point) -> bool:
        """Returns True if p lies exactly on the circle boundary."""
        lhs = (p.x - self.center.x)**2 + (p.y - self.center.y)**2
        return simplify(lhs - self.radius**2) == 0

    def __repr__(self):
        return f"Circle(center={self.center}, r={self.radius})"


@dataclass
class GeometryAnchor:
    """
    V1.0: The single source of truth for verified geometric facts.
    Populated by `constraint_checks.py` and consumed by `geometric_sanity.py`.
    """
    points: list = field(default_factory=list)
    lines: list = field(default_factory=list)
    segments: list = field(default_factory=list)
    circles: list = field(default_factory=list)
    verified_facts: list = field(default_factory=list)
    warnings: list = field(default_factory=list)

    def add_fact(self, fact: str):
        logger.info(f"[GEO-ANCHOR] ✓ FACT: {fact}")
        self.verified_facts.append(fact)

    def add_warning(self, warning: str):
        logger.warning(f"[GEO-ANCHOR] ✗ WARNING: {warning}")
        self.warnings.append(warning)

    def is_clean(self) -> bool:
        """True if no geometric contradictions were detected."""
        return len(self.warnings) == 0

    def summary(self) -> dict:
        return {
            "verified_facts": self.verified_facts,
            "warnings": self.warnings,
            "has_contradictions": not self.is_clean()
        }