| |
| |
| |
|
|
| from dataclasses import dataclass, field |
| from sympy import Rational, simplify, sqrt |
| import logging |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| @dataclass |
| class Point: |
| name: str |
| x: object |
| 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 |
|
|
| 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() |
| } |
|
|