demos

Exact Symbolic Poincaré Conformal Hyperbolic Disk Demonstrations (SymPy + geometor.model)

In strict accordance with GEOMETOR’s Content / Code Separation, executable Python constructions live outside the historical :layout: book pages of La Science et l’Hypothèse and operate in geometor.poincare.disk and demos/ with zero ``float`` or ``sp.Float`` coordinates.

Because Poincaré’s conformal dictionary represents the hyperbolic plane inside the Euclidean unit disk $\mathbb{D} = \{(x, y) : x^2 + y^2 < 1\}$ bounded by the ideal horizon $\Omega: x^2 + y^2 = 1$, every hyperbolic geodesic is either a Euclidean diameter through $O(0,0)$ or a Euclidean circle cutting $\Omega$ orthogonally ($|OC|^2 - r^2 = 1$). Straightedge-and-compass constructions in geometor.model therefore execute 100% exact hyperbolic geometry over $\mathbb{Q}$ and quadratic radical extensions.


1. Horizon Inversion, Orthogonal Geodesic Circles & Rational Quadrances

Constructs Euclidean inversion $P \mapsto P^* = P / \|P\|^2$ in $\Omega$, circumscribes the unique orthogonal geodesic circle through $(P, Q, P^*)$, and verifies the exact polynomial bridge between Poincaré’s 4-point cross-ratio $\chi(P, Q; U, V)$ and Norman Wildberger’s Rational Hyperbolic Quadrance $q_{\mathrm{UHG}}(P, Q) \in \mathbb{Q}$:

\[(1 - \tau)^2 (\chi^2 + 1)^2 = 4 (1 + \tau)^2 \chi^2, \qquad q_{\mathrm{UHG}}(P, Q) = -\frac{4\tau}{(1 - \tau)^2} = -\sinh^2 d_{\mathbb{H}}(P, Q).\]
  1r"""Demo 1: Horizon Inversion, Orthogonal Geodesic Circles, and Rational Quadrances.
          2
          3Constructs Henri Poincaré's conformal disk dictionary (*La Science et l'Hypothèse*,
          4Part II, Ch. III, 1902) inside ``geometor.model`` with 100% exact SymPy algebra:
          5
          61. Horizon circle :math:`\Omega: x^2 + y^2 = 1` centered at :math:`O = (0, 0)`.
          72. Pure straightedge-and-compass Euclidean inversion :math:`P \mapsto P^* = P / \|P\|^2`
          8   satisfying :math:`Q(O, P) \cdot Q(O, P^*) = 1` and involution :math:`(P^*)^* = P`.
          93. Unique orthogonal hyperbolic geodesic circle through :math:`(P, Q, P^*)` cutting
         10   :math:`\Omega` orthogonally (:math:`|OC|^2 - r^2 = 1`) at ideal boundary points :math:`U, V`.
         114. Exact equivalence between Poincaré's 4-point cross-ratio distance
         12   :math:`d_{\mathbb{H}}(P, Q) = \ln \chi(P, Q; U, V)` and Norman Wildberger's
         13   transcendental-free Rational Hyperbolic Quadrance :math:`q_{\text{UHG}}(P, Q) \in \mathbb{Q}`.
         14"""
         15
         16from __future__ import annotations
         17
         18import argparse
         19import sys
         20from pathlib import Path
         21from typing import Sequence
         22
         23_REPO_ROOT = Path(__file__).resolve().parents[1]
         24for _candidate in (_REPO_ROOT / "src", _REPO_ROOT.parent / "model" / "src"):
         25    if str(_candidate) not in sys.path:
         26        sys.path.insert(0, str(_candidate))
         27
         28import sympy as sp  # noqa: E402
         29from geometor.model import Model, quadrance, zero_test  # noqa: E402
         30
         31from geometor.poincare import (  # noqa: E402
         32    construct_hyperbolic_geodesic,
         33    construct_inversion_in_horizon,
         34    construct_unit_horizon,
         35    hyperbolic_cross_ratio_and_quadrance,
         36    render_figure_svg,
         37)
         38
         39
         40def assert_zero_floats(model: Model) -> None:
         41    """Verify that every element in ``model`` is 100% free of ``sp.Float``."""
         42    for el in model:
         43        if el.has(sp.Float):
         44            raise AssertionError(f"Floating-point contamination detected in {el!r}")
         45
         46
         47def run_demo(svg_out: Path | None = None) -> dict[str, sp.Expr]:
         48    """Execute the exact horizon-inversion and geodesic construction."""
         49    model = Model("demo_inversion_and_geodesics", logger=False, auto_intersect=False)
         50    origin, _ex, _omega = construct_unit_horizon(model)
         51
         52    # Interior rational points P and Q
         53    p_pt = model.set_point(sp.Rational(1, 3), sp.Rational(1, 4), classes=["given"], label="P")
         54    q_pt = model.set_point(sp.Rational(-2, 5), sp.Rational(1, 5), classes=["given"], label="Q")
         55
         56    # 1. Exact compass-and-straightedge inversion in the unit horizon
         57    p_star = construct_inversion_in_horizon(model, p_pt, label="P*")
         58    p_roundtrip = construct_inversion_in_horizon(model, p_star, label="P**")
         59    assert zero_test(quadrance(origin, p_pt) * quadrance(origin, p_star) - 1)
         60    assert zero_test(p_roundtrip.x - p_pt.x) and zero_test(p_roundtrip.y - p_pt.y)
         61
         62    # 2. Orthogonal hyperbolic geodesic circle through P and Q
         63    geo = construct_hyperbolic_geodesic(model, p_pt, q_pt, label="g_PQ")
         64    carrier, u_ideal, v_ideal = geo
         65    assert not geo.is_diameter
         66    assert geo.center is not None and geo.radius_squared is not None
         67    assert zero_test(geo.orthogonality_residual)
         68    assert zero_test(quadrance(origin, u_ideal) - 1)
         69    assert zero_test(quadrance(origin, v_ideal) - 1)
         70
         71    # 3. Diameter case when collinear with origin O
         72    r_pt = model.set_point(sp.Rational(2, 3), sp.Rational(1, 2), classes=["given"], label="R")
         73    diam_geo = construct_hyperbolic_geodesic(model, p_pt, r_pt, label="diam_PR")
         74    assert diam_geo.is_diameter
         75
         76    # 4. Cross-ratio and Rational Hyperbolic Quadrance invariants
         77    metrics = hyperbolic_cross_ratio_and_quadrance(p_pt, q_pt, u_ideal, v_ideal)
         78    assert_zero_floats(model)
         79
         80    svg_markup = render_figure_svg("fig2_poincare_dictionary_inversion.svg")
         81    if svg_out is not None:
         82        svg_out.parent.mkdir(parents=True, exist_ok=True)
         83        svg_out.write_text(svg_markup, encoding="utf-8")
         84
         85    _ = carrier
         86    return {
         87        "P_star_x": p_star.x,
         88        "P_star_y": p_star.y,
         89        "center_x": geo.center.x,
         90        "center_y": geo.center.y,
         91        "radius_squared": geo.radius_squared,
         92        "conformal_quadrance": metrics.conformal_quadrance,
         93        "wildberger_uhg_quadrance": metrics.wildberger_uhg_quadrance,
         94        "cayley_klein_quadrance": metrics.cayley_klein_quadrance,
         95        "cross_ratio": metrics.cross_ratio,
         96        "poincare_distance": metrics.poincare_distance,
         97    }
         98
         99
        100def main(argv: Sequence[str] | None = None) -> int:
        101    """CLI entry point for Demo 1."""
        102    parser = argparse.ArgumentParser(description=__doc__)
        103    parser.add_argument("--svg", type=Path, default=None, help="Optional output SVG path.")
        104    args = parser.parse_args(argv)
        105
        106    summary = run_demo(svg_out=args.svg)
        107    print("=== Demo 1: Horizon Inversion & Hyperbolic Geodesic Dictionary ===")
        108    print(f"P         = (1/3, 1/4)  ->  P* = ({summary['P_star_x']}, {summary['P_star_y']})")
        109    print(
        110        f"Geodesic  : center C = ({summary['center_x']}, {summary['center_y']}), "
        111        f"r^2 = {summary['radius_squared']} (|OC|^2 - r^2 = 1)"
        112    )
        113    print(f"tau(P, Q) = {summary['conformal_quadrance']}")
        114    print(f"q_UHG     = {summary['wildberger_uhg_quadrance']}")
        115    print(f"q_CK      = {summary['cayley_klein_quadrance']}")
        116    print(f"chi       = {summary['cross_ratio']}")
        117    print(f"d_H(P, Q) = {summary['poincare_distance']}")
        118    return 0
        119
        120
        121if __name__ == "__main__":
        122    raise SystemExit(main())
        

2. Limiting Horoparallels, Ultraparallels & Independence of Postulate V

Constructs two asymptotic horoparallels $\ell_1, \ell_2$ and two ultraparallels $m_1, m_2$ through $P(\frac{1}{6}, \frac{2}{5})$ relative to the rational geodesic $L_{\mathrm{hyp}}$ joining $(\frac{4}{5}, -\frac{3}{5})$ and $(-\frac{4}{5}, -\frac{3}{5})$, proving algebraically via intersect_2d that Euclid’s Fifth Postulate fails inside $\mathbb{D}$ while Postulates I–IV hold identically.

  1r"""Demo 2: Limiting Horoparallels, Ultraparallels, and Independence of Postulate V.
          2
          3Constructs Henri Poincaré's relative consistency proof for Lobachevskian geometry
          4(*La Science et l'Hypothèse*, Part II, Ch. III, 1902):
          5
          6Given a hyperbolic geodesic :math:`L_{\text{hyp}}` with rational ideal endpoints
          7:math:`U_L = (\frac{4}{5}, -\frac{3}{5})` and :math:`V_L = (-\frac{4}{5}, -\frac{3}{5})`
          8and an exterior point :math:`P = (\frac{1}{6}, \frac{2}{5}) \in \mathbb{D} \setminus L_{\text{hyp}}`:
          9
         10- Constructs the two limiting horoparallels :math:`\ell_1 = PU_L` and :math:`\ell_2 = PV_L`
         11  sharing ideal boundary vertices with :math:`L_{\text{hyp}}` on :math:`\partial\Omega`
         12  while remaining disjoint inside the open disk :math:`\mathbb{D}`.
         13- Constructs two distinct ultraparallel geodesics :math:`m_1, m_2` through :math:`P`
         14  whose closures in :math:`\overline{\mathbb{D}}` are 100% disjoint from :math:`L_{\text{hyp}}`.
         15- Demonstrates that any contradiction in Lobachevsky's geometry would translate via
         16  Poincaré's Dictionary into a contradiction in Euclidean circle geometry.
         17"""
         18
         19from __future__ import annotations
         20
         21import argparse
         22import sys
         23from pathlib import Path
         24from typing import Sequence
         25
         26_REPO_ROOT = Path(__file__).resolve().parents[1]
         27for _candidate in (_REPO_ROOT / "src", _REPO_ROOT.parent / "model" / "src"):
         28    if str(_candidate) not in sys.path:
         29        sys.path.insert(0, str(_candidate))
         30
         31import sympy as sp  # noqa: E402
         32import sympy.geometry as spg  # noqa: E402
         33from geometor.model import exact_sign, intersect_2d, zero_test  # noqa: E402
         34
         35from geometor.poincare import (  # noqa: E402
         36    UltraparallelPencil,
         37    construct_ultraparallels_through_point,
         38    render_figure_svg,
         39)
         40
         41
         42def run_demo(svg_out: Path | None = None) -> UltraparallelPencil:
         43    """Construct and verify the Lobachevskian parallel pencil through P."""
         44    pencil = construct_ultraparallels_through_point()
         45    for el in pencil.model:
         46        if el.has(sp.Float):
         47            raise AssertionError(f"Floating-point atom found in {el!r}")
         48
         49    base_carrier = pencil.base_geodesic.carrier
         50    # Verify limiting horoparallels meet base_geodesic ONLY on the ideal horizon x^2 + y^2 == 1
         51    for lim in pencil.limiting_parallels:
         52        hits = intersect_2d(base_carrier, lim.carrier)
         53        interior_hits = [
         54            pt
         55            for pt in hits
         56            if isinstance(pt, spg.Point2D) and exact_sign(pt.x**2 + pt.y**2 - 1) < 0
         57        ]
         58        bdry_hits = [
         59            pt
         60            for pt in hits
         61            if isinstance(pt, spg.Point2D) and zero_test(pt.x**2 + pt.y**2 - 1)
         62        ]
         63        assert len(interior_hits) == 0
         64        assert len(bdry_hits) == 1
         65
         66    # Verify ultraparallels have ZERO intersections in the closed unit disk x^2 + y^2 <= 1
         67    for ultra in pencil.ultraparallels:
         68        hits = intersect_2d(base_carrier, ultra.carrier)
         69        closed_disk_hits = [
         70            pt
         71            for pt in hits
         72            if isinstance(pt, spg.Point2D) and exact_sign(pt.x**2 + pt.y**2 - 1) <= 0
         73        ]
         74        assert len(closed_disk_hits) == 0
         75
         76    svg_markup = render_figure_svg("fig3_lobachevsky_parallels.svg")
         77    if svg_out is not None:
         78        svg_out.parent.mkdir(parents=True, exist_ok=True)
         79        svg_out.write_text(svg_markup, encoding="utf-8")
         80
         81    return pencil
         82
         83
         84def main(argv: Sequence[str] | None = None) -> int:
         85    """CLI entry point for Demo 2."""
         86    parser = argparse.ArgumentParser(description=__doc__)
         87    parser.add_argument("--svg", type=Path, default=None, help="Optional output SVG path.")
         88    args = parser.parse_args(argv)
         89
         90    pencil = run_demo(svg_out=args.svg)
         91    print("=== Demo 2: Lobachevskian Horoparallels & Ultraparallels (Postulate V) ===")
         92    print(f"Base geodesic L_hyp ideal endpoints : {pencil.base_geodesic.ideal_points}")
         93    print(f"Exterior point P                    : ({pencil.point.x}, {pencil.point.y})")
         94    for idx, lim in enumerate(pencil.limiting_parallels, start=1):
         95        print(f"Horoparallel l_{idx} center & r^2       : {lim.center}, r^2 = {lim.radius_squared}")
         96    for idx, ultra in enumerate(pencil.ultraparallels, start=1):
         97        print(
         98            f"Ultraparallel m_{idx} center & r^2      : {ultra.center}, r^2 = {ultra.radius_squared} "
         99            "(0 intersections in closed disk)"
        100        )
        101    return 0
        102
        103
        104if __name__ == "__main__":
        105    raise SystemExit(main())
        

3. Pythagorean Ideal Triangle & Saccheri Quadrilateral Angular Defect

Constructs the ideal triangle $\triangle U_1 U_2 U_3$ on $U_1(1,0), U_2(-\frac{3}{5},\frac{4}{5}), U_3(-\frac{3}{5},-\frac{4}{5})$ with rational side centers $(1,2), (1,-2), (-\frac{5}{3},0)$, rational radii $2, 2, \frac{4}{3}$, and interior spreads $(0, 0, 0)$, alongside the rational Saccheri quadrilateral $A_1 A_2 B_2 B_1$ with equal leg UHG quadrances $-\frac{225}{64}$ and strictly acute summit spreads $\frac{64}{145} < 1$.

  1r"""Demo 3: Ideal Hyperbolic Triangle & Saccheri Quadrilateral Angular Defect.
          2
          3Proves two celebrated theorems of Bolyai–Lobachevsky–Saccheri geometry using 100%
          4rational points, rational circle centers, and Rational Trigonometry spreads inside
          5``geometor.model``:
          6
          71. **Pythagorean Ideal Triangle** :math:`\triangle U_1 U_2 U_3` on
          8   :math:`U_1 = (1, 0),\; U_2 = (-\frac{3}{5}, \frac{4}{5}),\; U_3 = (-\frac{3}{5}, -\frac{4}{5})`:
          9   - Orthogonal side circles centered at :math:`(1, 2),\; (1, -2),\; (-\frac{5}{3}, 0)`
         10     with exact rational radii :math:`2,\; 2,\; \frac{4}{3}`.
         11   - Interior tangent spreads :math:`(0, 0, 0)` and maximal Gauss–Bonnet defect :math:`\pi`.
         122. **Hyperbolic Right Triangle** :math:`\triangle OAB` & **Saccheri Quadrilateral**
         13   :math:`A_1 A_2 B_2 B_1`:
         14   - Right triangle on :math:`O(0,0),\; A(\frac{1}{2},0),\; B(0,\frac{1}{2})` has base spread
         15     :math:`1` and acute vertex spreads :math:`\frac{9}{34} < \frac{1}{2}`.
         16   - Saccheri quadrilateral with base :math:`A_1(-\frac{1}{3}, 0),\; A_2(\frac{1}{3}, 0)` and
         17     summit :math:`B_1(-\frac{17}{39}, \frac{20}{39}),\; B_2(\frac{17}{39}, \frac{20}{39})` has
         18     equal leg UHG quadrances :math:`-\frac{225}{64}`, right base spreads :math:`(1, 1)`,
         19     and strictly acute summit spreads :math:`\frac{64}{145} < 1` (refuting Saccheri's
         20     Euclidean right-angle hypothesis).
         21"""
         22
         23from __future__ import annotations
         24
         25import argparse
         26import sys
         27from pathlib import Path
         28from typing import Sequence
         29
         30_REPO_ROOT = Path(__file__).resolve().parents[1]
         31for _candidate in (_REPO_ROOT / "src", _REPO_ROOT.parent / "model" / "src"):
         32    if str(_candidate) not in sys.path:
         33        sys.path.insert(0, str(_candidate))
         34
         35import sympy as sp  # noqa: E402
         36from geometor.model import zero_test  # noqa: E402
         37
         38from geometor.poincare import (  # noqa: E402
         39    IdealTriangleResult,
         40    SaccheriDefectResult,
         41    construct_ideal_hyperbolic_triangle,
         42    construct_saccheri_triangleDefect,
         43    render_figure_svg,
         44)
         45
         46
         47def run_demo(svg_dir: Path | None = None) -> tuple[IdealTriangleResult, SaccheriDefectResult]:
         48    """Construct and verify the ideal hyperbolic triangle and Saccheri quadrilateral."""
         49    ideal = construct_ideal_hyperbolic_triangle()
         50    sac = construct_saccheri_triangleDefect()
         51
         52    for m in (ideal.model, sac.model):
         53        for el in m:
         54            if el.has(sp.Float):
         55                raise AssertionError(f"Floating-point atom detected in {el!r}")
         56
         57    assert ideal.radii == (sp.Integer(2), sp.Integer(2), sp.Rational(4, 3))
         58    assert all(zero_test(s) for s in ideal.vertex_spreads)
         59
         60    assert sac.triangle_spreads == (sp.Integer(1), sp.Rational(9, 34), sp.Rational(9, 34))
         61    assert sac.saccheri_base_spreads == (sp.Integer(1), sp.Integer(1))
         62    assert sac.saccheri_summit_spreads == (sp.Rational(64, 145), sp.Rational(64, 145))
         63    assert sac.saccheri_leg_uhg_quadrances == (
         64        sp.Rational(-225, 64),
         65        sp.Rational(-225, 64),
         66    )
         67
         68    if svg_dir is not None:
         69        svg_dir.mkdir(parents=True, exist_ok=True)
         70        (svg_dir / "fig4_ideal_hyperbolic_triangle.svg").write_text(
         71            render_figure_svg("fig4_ideal_hyperbolic_triangle.svg"), encoding="utf-8"
         72        )
         73        (svg_dir / "fig5_saccheri_angular_defect.svg").write_text(
         74            render_figure_svg("fig5_saccheri_angular_defect.svg"), encoding="utf-8"
         75        )
         76
         77    return ideal, sac
         78
         79
         80def main(argv: Sequence[str] | None = None) -> int:
         81    """CLI entry point for Demo 3."""
         82    parser = argparse.ArgumentParser(description=__doc__)
         83    parser.add_argument(
         84        "--svg-dir",
         85        type=Path,
         86        default=None,
         87        help="Optional directory to write fig4 and fig5 SVGs.",
         88    )
         89    args = parser.parse_args(argv)
         90
         91    ideal, sac = run_demo(svg_dir=args.svg_dir)
         92    print("=== Demo 3: Ideal Hyperbolic Triangle & Saccheri Angular Defect ===")
         93    print(f"Ideal vertices       : {ideal.vertices}")
         94    print(f"Ideal circle centers : {ideal.centers}")
         95    print(f"Ideal circle radii   : {ideal.radii}")
         96    print(f"Ideal vertex spreads : {ideal.vertex_spreads}  ->  defect = {ideal.angular_defect}")
         97    print("-" * 68)
         98    print(
         99        f"Right triangle OAB   : spreads = {sac.triangle_spreads} (< (1, 1/2, 1/2)), "
        100        f"defect = {sac.triangle_defect}"
        101    )
        102    print(f"Saccheri leg q_UHG   : {sac.saccheri_leg_uhg_quadrances}")
        103    print(f"Saccheri base spreads: {sac.saccheri_base_spreads}")
        104    print(
        105        f"Saccheri summit      : spreads = {sac.saccheri_summit_spreads} (< 1), "
        106        f"defect = {sac.saccheri_defect}"
        107    )
        108    return 0
        109
        110
        111if __name__ == "__main__":
        112    raise SystemExit(main())
        

4. Heated-Disk Optical Rays, Beltrami–Klein Bridge & Stellar Parallax

Realizes Poincaré’s physical thermal parable ($T \propto 1 - r^2$, $n \propto (1 - r^2)^{-1}$), proves by pure straightedge radial projection $K(P) = \frac{2P}{1 + \|P\|^2}$ that Poincaré conformal arcs map onto Beltrami–Klein straight chords with identical Cayley–Klein quadrance $q_{\mathrm{CK}}$, and models Poincaré’s astronomical parallax argument for geometric conventionalism.

  1r"""Demo 4: Poincaré's Heated Disk, Beltrami–Klein Projection, & Stellar Parallax.
          2
          3Synthesizes the physical and projective models of *La Science et l'Hypothèse*
          4(Part II, Chs. IV–V, 1902):
          5
          61. **Poincaré's Heated-Disk Parable (Ch. IV)**:
          7   - Temperature :math:`T(r) \propto R^2 - r^2` and refractive index :math:`n(r) \propto (R^2 - r^2)^{-1}`
          8     bend optical Fermat geodesics and thermally contracted rulers along orthogonal circles,
          9     making the finite disk physically indistinguishable from an infinite Lobachevskian plane.
         102. **Beltrami–Klein Projective Chord Bridge**:
         11   - Radial straightedge projection from :math:`O` onto the straight chord :math:`UV`
         12     realizes the exact Weierstrass map :math:`K(P) = \frac{2P}{1 + \|P\|^2}`, turning
         13     Poincaré's conformal arcs into Beltrami–Klein projective segments while preserving
         14     :math:`q_{\text{CK}}(P_1, P_2) = \tanh^2 d_{\mathbb{H}}(P_1, P_2)`.
         153. **Stellar Parallax & Geometric Conventionalism (Ch. V § 3)**:
         16   - Any astronomical triangle :math:`\triangle S_1 S_2 S_3` with angular sum differing
         17     from :math:`\pi` admits two empirically equivalent descriptions: Lobachevskian space
         18     with straight rays vs. Euclidean space with refracted rays.
         19"""
         20
         21from __future__ import annotations
         22
         23import argparse
         24import sys
         25from pathlib import Path
         26from typing import Sequence
         27
         28_REPO_ROOT = Path(__file__).resolve().parents[1]
         29for _candidate in (_REPO_ROOT / "src", _REPO_ROOT.parent / "model" / "src"):
         30    if str(_candidate) not in sys.path:
         31        sys.path.insert(0, str(_candidate))
         32
         33import sympy as sp  # noqa: E402
         34from geometor.model import zero_test  # noqa: E402
         35
         36from geometor.poincare import (  # noqa: E402
         37    BeltramiKleinBridgeResult,
         38    HeatedDiskResult,
         39    StellarParallaxResult,
         40    construct_beltrami_klein_bridge,
         41    construct_heated_disk_optical_rays,
         42    construct_stellar_parallax_conventionalism,
         43    hyperbolic_cross_ratio_and_quadrance,
         44    render_figure_svg,
         45)
         46
         47
         48def _klein_hilbert_quadrance(
         49    k1: sp.geometry.Point2D,
         50    k2: sp.geometry.Point2D,
         51) -> sp.Expr:
         52    r"""Compute the Beltrami–Klein metric invariant directly from Klein coordinates.
         53
         54    For interior Klein points :math:`K_1, K_2`, the Cayley–Klein quadrance satisfies
         55    :math:`1 - q_{\text{CK}}(K_1, K_2) = \frac{(1 - \|K_1\|^2)(1 - \|K_2\|^2)}{(1 - K_1 \cdot K_2)^2}`.
         56    """
         57    n1 = k1.x**2 + k1.y**2
         58    n2 = k2.x**2 + k2.y**2
         59    dot = k1.x * k2.x + k1.y * k2.y
         60    return sp.simplify(1 - ((1 - n1) * (1 - n2)) / (1 - dot) ** 2)
         61
         62
         63def run_demo(
         64    svg_dir: Path | None = None,
         65) -> tuple[HeatedDiskResult, BeltramiKleinBridgeResult, StellarParallaxResult]:
         66    """Construct and verify the Heated Disk, Beltrami–Klein bridge, and Parallax models."""
         67    heated = construct_heated_disk_optical_rays()
         68    bridge = construct_beltrami_klein_bridge()
         69    parallax = construct_stellar_parallax_conventionalism()
         70
         71    for m in (heated.model, bridge.model, parallax.model):
         72        for el in m:
         73            if el.has(sp.Float):
         74                raise AssertionError(f"Floating-point atom detected in {el!r}")
         75
         76    p1, p2 = bridge.poincare_points
         77    k1, k2 = bridge.klein_points
         78    poincare_metrics = hyperbolic_cross_ratio_and_quadrance(
         79        p1, p2, bridge.poincare_geodesic.U, bridge.poincare_geodesic.V
         80    )
         81    q_ck_from_klein = _klein_hilbert_quadrance(k1, k2)
         82    assert zero_test(poincare_metrics.cayley_klein_quadrance - q_ck_from_klein)
         83
         84    if svg_dir is not None:
         85        svg_dir.mkdir(parents=True, exist_ok=True)
         86        for fname in (
         87            "fig6_heated_disk_refraction.svg",
         88            "fig7_cayley_klein_beltrami_bridge.svg",
         89            "fig8_stellar_parallax_conventionalism.svg",
         90        ):
         91            (svg_dir / fname).write_text(render_figure_svg(fname), encoding="utf-8")
         92
         93    return heated, bridge, parallax
         94
         95
         96def main(argv: Sequence[str] | None = None) -> int:
         97    """CLI entry point for Demo 4."""
         98    parser = argparse.ArgumentParser(description=__doc__)
         99    parser.add_argument(
        100        "--svg-dir",
        101        type=Path,
        102        default=None,
        103        help="Optional directory to write fig6, fig7, and fig8 SVGs.",
        104    )
        105    args = parser.parse_args(argv)
        106
        107    heated, bridge, parallax = run_demo(svg_dir=args.svg_dir)
        108    p1, p2 = bridge.poincare_points
        109    k1, k2 = bridge.klein_points
        110    metrics = hyperbolic_cross_ratio_and_quadrance(
        111        p1, p2, bridge.poincare_geodesic.U, bridge.poincare_geodesic.V
        112    )
        113
        114    print("=== Demo 4: Heated Disk, Beltrami–Klein Bridge, & Stellar Parallax ===")
        115    print(
        116        f"Heated disk source S = ({heated.source.x}, {heated.source.y}) with "
        117        f"{len(heated.isothermals)} isothermals and {len(heated.light_rays)} Fermat rays"
        118    )
        119    print(f"Poincaré points      : P1 = ({p1.x}, {p1.y}), P2 = ({p2.x}, {p2.y})")
        120    print(f"Klein chord images   : K1 = ({k1.x}, {k1.y}), K2 = ({k2.x}, {k2.y})")
        121    print(
        122        f"Invariant match      : q_CK(Poincaré) = {metrics.cayley_klein_quadrance} == "
        123        f"q_CK(Beltrami–Klein) = {_klein_hilbert_quadrance(k1, k2)}"
        124    )
        125    print(
        126        f"Parallax triangle    : {len(parallax.stars)} stars, "
        127        f"{len(parallax.lobachevsky_rays)} hyperbolic arcs vs. "
        128        f"{len(parallax.euclidean_chords)} Euclidean refracted chords"
        129    )
        130    return 0
        131
        132
        133if __name__ == "__main__":
        134    raise SystemExit(main())