modules

geometor.model

Provides the core symbolic data structures, Euclidean construction kernel, and rational-trigonometry proof verifier for 2D constructive geometry in GEOMETOR.

Everything centers around Model.

Submodules

Attributes

GeometryObject

Exceptions

ConstructionError

Raised when a compass-and-straightedge intersection violates its diorismos or selector.

ProofAssertionError

Raised when a deductive geometric assertion fails exact symbolic verification.

Classes

SubconstructionScope

Active scope state for Model.subconstruction().

Angle

First-class rectilinear angle < B A C ) with vertex A and rays AB, AC.

Model

The central class representing an exact symbolic geometric construction and proof.

Step

Chronological construction or demonstration step recorded by Model.

Functions

canonicalize_golden(expr) → sympy.Expr

Canonicalize an expression in the golden field \(\mathbb{Q}(\phi)\).

canonicalize_kepler(expr) → sympy.Expr

Canonicalize an expression in the degree-4 Kepler field \(\mathbb{Q}(\sqrt{\phi})\).

clean_expr(expr) → sympy.Expr

Canonicalize a symbolic geometric coordinate without invoking sp.simplify.

exact_sign(expr) → int

Return the exact mathematical sign (+1, -1, or 0) of a real expression.

get_algebraic_field(*generators) → sympy.AlgebraicField

Construct and cache an explicit algebraic number field \(\mathbb{Q}(\alpha_1, \dots)\).

get_golden_field() → sympy.AlgebraicField

Return the cached quadratic number field \(\mathbb{Q}(\phi) = \mathbb{Q}(\sqrt{5})\).

get_kepler_field() → sympy.AlgebraicField

Return the cached degree-4 Kepler number field \(\mathbb{Q}(\sqrt{\phi})\).

reduce_in_field(expr, field_or_gen) → sympy.Expr

Reduce an algebraic expression inside an explicit number field \(\mathbb{Q}(\alpha)\).

zero_test(expr) → bool

Determine with 100% mathematical exactness whether expr == 0.

intersect_2d(s1, s2) → list[sympy.geometry.Point]

Compute exact symbolic intersections of 2D lines, circles, and segments.

proposition(prop_id, *[, g_id, kind, title, ...]) → collections.abc.Callable[[collections.abc.Callable[..., Any]], collections.abc.Callable[..., Any]]

Decorator marking a Euclidean construction function as a composable proposition.

cross(l1, l2) → sympy.Expr

Compute the exact rational cross \(c(l_1, l_2) = \cos^2\theta = 1 - s(l_1, l_2)\).

cross_ratio(A, B, C, D) → sympy.Expr

Compute the exact projective cross-ratio \((A, B; C, D) = \frac{\overline{AC}\cdot\overline{BD}}{\overline{BC}\cdot\overline{AD}}\).

is_harmonic(A, B, C, D) → bool

Return True iff (A, B; C, D) == -1 forms an exact harmonic range.

line_spread(l1, l2) → sympy.Expr

Compute the exact rational spread \(s(l_1, l_2) = \sin^2\theta\) between two lines.

orientation(A, B, C) → int

Return +1 if (A, B, C) is counter-clockwise, -1 if clockwise, 0 if collinear.

quadrance(p1_or_seg[, p2]) → sympy.Expr

Compute the exact Euclidean quadrance \(Q = (x_2 - x_1)^2 + (y_2 - y_1)^2\).

vertex_spread(B, A, C) → sympy.Expr

Compute the exact spread at vertex A between rays AB and AC (< B A C )).

load_model(file_path[, logger]) → geometor.model.Model

Load a model.json file (v1 or v2) and reconstitute a Model.

Package Contents

geometor.model.canonicalize_golden(expr: object) → sympy.Expr

Canonicalize an expression in the golden field \(\mathbb{Q}(\phi)\).

geometor.model.canonicalize_kepler(expr: object) → sympy.Expr

Canonicalize an expression in the degree-4 Kepler field \(\mathbb{Q}(\sqrt{\phi})\).

geometor.model.clean_expr(expr: object) → sympy.Expr

Canonicalize a symbolic geometric coordinate without invoking sp.simplify.

Applies a fast tiered pipeline:

  1. Immediate O(1) return for Python int and SymPy Rational / Integer.

  2. Fraction combination (sympy.together()), bounded polynomial expansion, and denominator rationalization (sympy.radsimp() with symbolic=False).

  3. Conditional sympy.sqrtdenest() only when nested radicals are actually present.

Parameters:

expr – A numeric value or SymPy expression.

Returns:

A simplified exact sympy.Expr.

geometor.model.exact_sign(expr: object) → int

Return the exact mathematical sign (+1, -1, or 0) of a real expression.

Parameters:

expr – A real numeric value or SymPy expression.

Returns:

1 if expr > 0, -1 if expr < 0, or 0 if expr == 0.

Raises:

ValueError – If expr has free symbols whose sign cannot be determined or is non-real.

geometor.model.get_algebraic_field(*generators: sympy.Expr) → sympy.AlgebraicField

Construct and cache an explicit algebraic number field \(\mathbb{Q}(\alpha_1, \dots)\).

Parameters:

*generators – Algebraic SymPy expressions generating the extension over QQ.

Returns:

A cached sympy.polys.domains.AlgebraicField.

geometor.model.get_golden_field() → sympy.AlgebraicField

Return the cached quadratic number field \(\mathbb{Q}(\phi) = \mathbb{Q}(\sqrt{5})\).

geometor.model.get_kepler_field() → sympy.AlgebraicField

Return the cached degree-4 Kepler number field \(\mathbb{Q}(\sqrt{\phi})\).

geometor.model.reduce_in_field(expr: object, field_or_gen: sympy.AlgebraicField | sympy.Expr | tuple[sympy.Expr, ...]) → sympy.Expr

Reduce an algebraic expression inside an explicit number field \(\mathbb{Q}(\alpha)\).

Handles nested square roots whose radicands simplify to squares inside the target field (such as \(\sqrt{1 + \phi} = \phi\) and \(\sqrt{\phi - 1} = \phi^{-1/2}\) in the Kepler field), and falls back cleanly when outer extensions (e.g. \(\sqrt{3}\)) are present.

Parameters:
  • expr – Numeric value or SymPy expression to canonicalize.

  • field_or_gen – An sympy.AlgebraicField, a generator expression, or tuple of generators.

Returns:

Canonicalized sympy.Expr.

geometor.model.zero_test(expr: object) → bool

Determine with 100% mathematical exactness whether expr == 0.

Uses a fast 3-tier decision procedure:

  1. O(1) structural and rational check.

  2. High-precision adaptive evalf(20) rejection for non-zero constant expressions (rejects non-zeros in microseconds without symbolic expansion).

  3. Exact algebraic proof of zero via fraction numerator expansion, clean_expr(), GoldenRatio radical substitution, and fallback simplify / minimal_polynomial.

Parameters:

expr – A numeric value or SymPy expression.

Returns:

True if and only if expr is identically zero.

exception geometor.model.ConstructionError

Bases: ValueError

Raised when a compass-and-straightedge intersection violates its diorismos or selector.

class geometor.model.SubconstructionScope

Active scope state for Model.subconstruction().

name: str
g_id: str = ''
scope_intersections: bool = True
hide_guides: bool = True
initial_keys: set[Any]
scope_structs: list[Any] = []
created_elements: list[Any] = []
returned_elements: set[Any]
property scope_id: str

Canonical identifier recorded on nested steps.

include(*elements: Any) → None

Include existing outer lines/circles in this scope’s intersection pool.

keep(*elements: Any) → None

Preserve specific elements created inside this scope as primary (non-guide) outputs.

geometor.model.intersect_2d(s1: sympy.geometry.entity.GeometryEntity, s2: sympy.geometry.entity.GeometryEntity) → list[sympy.geometry.Point]

Compute exact symbolic intersections of 2D lines, circles, and segments.

Uses closed-form homogeneous cross-products (line-line), orthogonal projection discriminants (line-circle), and radical-axis projections (circle-circle) so nested radical expressions never fall into SymPy’s slow Ellipse quartic solver.

Parameters:
  • s1 – First 2D geometric entity (Line, Circle, or Segment).

  • s2 – Second 2D geometric entity (Line, Circle, or Segment).

Returns:

Deterministically sorted list of exact real sympy.geometry.Point intersections.

geometor.model.proposition(prop_id: str, *, g_id: str = '', kind: str = 'problem', title: str = '', hide_guides: bool = True, scope_intersections: bool = True) → collections.abc.Callable[[collections.abc.Callable[..., Any]], collections.abc.Callable[..., Any]]

Decorator marking a Euclidean construction function as a composable proposition.

Executes the wrapped function inside Model.subconstruction(), scoping automatic intersections to the proposition and automatically marking un-returned auxiliary lines, circles, and guide poles with guide=True.

Parameters:
  • prop_id – Proposition or center identifier (e.g., "I.1", "X(13)").

  • g_id – Optional G-Index node identifier (e.g., "G.17").

  • kind – "problem" (Q.E.F.) or "theorem" (Q.E.D.).

  • title – Optional human-readable proposition title.

  • hide_guides – Default hide_guides setting when invoked.

  • scope_intersections – Default scope_intersections setting when invoked.

class geometor.model.Angle(p1_or_points: sympy.geometry.Point | list[sympy.geometry.Point] | tuple[sympy.geometry.Point, ...], vertex: sympy.geometry.Point | None = None, p2: sympy.geometry.Point | None = None)

First-class rectilinear angle < B A C ) with vertex A and rays AB, AC.

Stores exact algebraic spread, cross, and acute/right/obtuse dot-product sign without requiring transcendental acos or atan2 evaluation during equality proofs.

p1: sympy.geometry.Point
vertex: sympy.geometry.Point
p2: sympy.geometry.Point
points: list[sympy.geometry.Point]
__eq__(other: object) → bool
__hash__() → int
__repr__() → str
property ray1: sympy.geometry.Ray

First ray from vertex through p1.

property ray2: sympy.geometry.Ray

Second ray from vertex through p2.

property spread: sympy.Expr

Exact rational trigonometry spread \(s(\angle BAC) = \sin^2\theta\).

property cross: sympy.Expr

Exact rational trigonometry cross \(c(\angle BAC) = \cos^2\theta\).

property dot_sign: int

+1 acute, 0 right, -1 obtuse.

Type:

Exact dot-product sign

property is_right: bool

Return True iff the angle is an exact right angle.

property radians: sympy.Expr

Interior angle measure in radians in \([0, \pi]\).

property degrees: sympy.Expr

Interior angle measure in degrees.

geometor.model.cross(l1: sympy.geometry.Line | sympy.geometry.Segment | tuple[sympy.geometry.Point, sympy.geometry.Point], l2: sympy.geometry.Line | sympy.geometry.Segment | tuple[sympy.geometry.Point, sympy.geometry.Point]) → sympy.Expr

Compute the exact rational cross \(c(l_1, l_2) = \cos^2\theta = 1 - s(l_1, l_2)\).

geometor.model.cross_ratio(A: sympy.geometry.Point, B: sympy.geometry.Point, C: sympy.geometry.Point, D: sympy.geometry.Point) → sympy.Expr

Compute the exact projective cross-ratio \((A, B; C, D) = \frac{\overline{AC}\cdot\overline{BD}}{\overline{BC}\cdot\overline{AD}}\).

Projects collinear displacements onto a shared line direction vector so the cross-ratio is computed purely rationally without introducing square roots.

Parameters:
  • A – First base point.

  • B – Second base point.

  • C – First division point.

  • D – Second division point.

Returns:

Exact simplified sympy.Expr.

Raises:

ValueError – If the four points are not collinear or the denominator vanishes.

geometor.model.is_harmonic(A: sympy.geometry.Point, B: sympy.geometry.Point, C: sympy.geometry.Point, D: sympy.geometry.Point) → bool

Return True iff (A, B; C, D) == -1 forms an exact harmonic range.

geometor.model.line_spread(l1: sympy.geometry.Line | sympy.geometry.Segment | tuple[sympy.geometry.Point, sympy.geometry.Point], l2: sympy.geometry.Line | sympy.geometry.Segment | tuple[sympy.geometry.Point, sympy.geometry.Point]) → sympy.Expr

Compute the exact rational spread \(s(l_1, l_2) = \sin^2\theta\) between two lines.

Parameters:
  • l1 – First line, segment, or (P, Q) point pair.

  • l2 – Second line, segment, or (P, Q) point pair.

Returns:

Exact simplified spread in \([0, 1]\).

geometor.model.orientation(A: sympy.geometry.Point, B: sympy.geometry.Point, C: sympy.geometry.Point) → int

Return +1 if (A, B, C) is counter-clockwise, -1 if clockwise, 0 if collinear.

geometor.model.quadrance(p1_or_seg: sympy.geometry.Point | sympy.geometry.Segment, p2: sympy.geometry.Point | None = None) → sympy.Expr

Compute the exact Euclidean quadrance \(Q = (x_2 - x_1)^2 + (y_2 - y_1)^2\).

Accepts either two sympy.geometry.Point instances quadrance(P, Q) or a single sympy.geometry.Segment quadrance(seg).

Parameters:
  • p1_or_seg – First point P or a segment / P Q /.

  • p2 – Second point Q when p1_or_seg is a point.

Returns:

Exact simplified sympy.Expr with zero square roots introduced.

geometor.model.vertex_spread(B: sympy.geometry.Point, A: sympy.geometry.Point, C: sympy.geometry.Point) → sympy.Expr

Compute the exact spread at vertex A between rays AB and AC (< B A C )).

Parameters:
  • B – Point on the first ray.

  • A – The vertex of the angle.

  • C – Point on the second ray.

Returns:

Exact simplified spread \(s(\angle BAC)\).

geometor.model.GeometryObject
class geometor.model.Model(name: str = '', logger: logging.Logger | bool | None = None, use_point_subscript: bool = False, *, auto_intersect: bool = True, use_multiprocessing: bool = False, enable_logging: bool | None = None)

Bases: dict, geometor.model.points.PointsMixin, geometor.model.lines.LinesMixin, geometor.model.circles.CirclesMixin, geometor.model.intersections.IntersectionsMixin, geometor.model.polygons.PolygonsMixin, geometor.model.segments.SegmentsMixin, geometor.model.invariants.AnglesMixin, geometor.model.proof.ProofMixin, geometor.model.polynomials.PolynomialsMixin, geometor.model.serialize.SerializeMixin, geometor.model.reports.ReportMixin, geometor.model.delete.DeleteMixin, geometor.model.sections.SectionsMixin, geometor.model.wedges.WedgesMixin, geometor.model.ancestors.AncestorsMixin

The central class representing an exact symbolic geometric construction and proof.

use_point_subscript = False
auto_intersect = True
use_multiprocessing = False
ID_gen
last_point_id = ''
steps: list[geometor.model.proof.Step] = []
property is_logging_enabled: bool

Return True only when INFO messages have at least one active non-null handler.

log(message: object) → None

Log a message to the model’s logger or print if it’s a Rich renderable.

set_analysis_hook(hook_function: collections.abc.Callable | None) → None

Set a hook function to be called during analysis/point addition.

set_simplifier(simplifier_function: collections.abc.Callable[[sympy.Expr], sympy.Expr] | None) → None

Set an optional custom symbolic simplifier for point coordinates.

property new_points: list[sympy.geometry.Point]

The points added since the most recent clear_new_points().

clear_new_points() → None

Clear the list of newly added points.

property name: str

The name of the model.

__setitem__(key: GeometryObject, value: geometor.model.element.Element) → None

Set an item in the model, enforcing type checks.

remove_by_ID(ID: str) → None

Remove a geometric element from the model by its ID.

property points: list[sympy.geometry.Point]

Return point elements from the model as a list.

property structs: list[geometor.model.element.Struct]

Return non-guide structural elements (lines or circles) from the model.

property lines: list[sympy.geometry.Line]

Return line elements from the model as a list.

property circles: list[sympy.geometry.Circle]

Return circle elements from the model as a list.

property segments: list[sympy.geometry.Segment]

Return segment elements from the model as a list.

property polygons: list[sympy.geometry.Polygon]

Return polygon elements from the model as a list.

limits() → list[list[float]]

Find [[min_x, max_x], [min_y, max_y]] limits from points and circles.

get_element_by_ID
parse_command(command: str) → None

Parse and execute a single command string.

exception geometor.model.ProofAssertionError

Bases: AssertionError

Raised when a deductive geometric assertion fails exact symbolic verification.

class geometor.model.Step

Chronological construction or demonstration step recorded by Model.

index: int
kind: str
action: str
elements: list[str] = []
by: str = ''
label: str = ''
focus: list[str] = []
parent_scope: str = ''
primitive: dict[str, Any]
to_dict() → dict[str, Any]

Serialize this Step to a JSON-compatible dictionary.

classmethod from_dict(data: dict[str, Any]) → Step

Reconstruct a Step from its dictionary representation.

geometor.model.load_model(file_path: str | pathlib.Path, logger: logging.Logger | bool | None = None) → geometor.model.Model

Load a model.json file (v1 or v2) and reconstitute a Model.