Phase 9a: dual circle-packing functionals (CP-Euclidean + Inversive Distance) #8
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/phase-9a-inversive-distance"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements both Phase 9a sub-functionals, validated side-by-side against three published references and the existing Euclidean functional.
CGAL test count: 184 → 205 (+10 from 9a.1, +11 from 9a.2). Zero skips.
What is in this PR
9a.1 —
cp_euclidean_functional.hpp+ 10 testsFace-based circle packing (Bobenko-Pinkall-Springborn 2010) — a direct port of the Java
CPEuclideanFunctional.java(260 lines).h_jk = sin θ / (cosh Δρ − cos θ)9a.2 —
inversive_distance_functional.hpp+ 11 testsVertex-based inversive distance (Luo 2004 + Glickenstein 2011) — no Java reference exists for this functional; implemented from the literature.
Validation report —
doc/architecture/phase-9a-validation.mdCPEuclideanFunctional.java↔ C++ portAngleDefectAtU0_AgreesWithEuclideanAtU0— directly verifies Glickenstein 2011 §5Roadmap & tutorial corrections
doc/roadmap/phases.md— Phase 9a split into 9a.1 + 9a.2 with clear math citationsdoc/tutorials/add-inversive-distance.md— corrects the prior false claim thatInversiveDistanceFunctional.javaexists in the Java repo (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004CLAUDE.md— adds the validation report to the doc mapKey research finding
The original Phase-9a roadmap entry was based on a misreading: there is no
InversiveDistanceFunctional.javain the Java repovarylab/conformallab. Instead there isCPEuclideanFunctional.java, which is the face-dual variant (BPS 2010). These two functionals are different geometric models but related via Glickenstein 2011 §5:I_ij = cos θ_eidentifies the vertex-based and face-based parametrisations.This PR delivers both — porting the Java original (9a.1) and implementing the literature-canonical inversive-distance (9a.2) — so that future work has access to either parametrisation.
Test results
What is not in this PR (deferred, on-demand)
newton_cp_euclidean()/newton_inversive_distance()— Phase 9a focused on the functional layer. The existingnewton_euclidean()template can be adapted in a follow-up PR.CGAL::discrete_circle_packing_euclidean()/CGAL::discrete_inversive_distance_map()— Phase 8b.2 territory; will reuse the Phase 8 MVP traits pattern once both Newton solvers are in.Audit during Phase 9a preparation revealed: * The Java repo `varylab/conformallab` does NOT contain `InversiveDistanceFunctional.java`. The original roadmap mention was based on a misreading. * The closest existing Java class is `CPEuclideanFunctional.java` (260 lines, with FunctionalTest), implementing the Bobenko-Pinkall- Springborn 2010 face-based circle-packing functional. These two functionals are mathematically distinct (face-dual vs vertex- based) but related: BPS-CP generalises inversive-distance via the intersection angle parameter θ_e (I_ij = cos θ_e for orthogonal limit, I_ij = 1 for tangential). The roadmap is now split into: - 9a.1 CPEuclideanFunctional (Java port + test, BPS 2010 reference) - 9a.2 InversiveDistanceFunctional (from-literature, Luo 2004 + Glickenstein 2011) Both belong in Phase 9a; cross-validation between them in the tangential limit (θ=0 ⇔ I=1) becomes a Phase 9a acceptance test. The tutorial `doc/tutorials/add-inversive-distance.md` is corrected: it no longer claims `InversiveDistanceFunctional.java` exists upstream, and cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 instead. Updated edge-length formula from incorrect hyperbolic cosh form to the correct Luo §3 Euclidean form: ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i + u_j) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Implements both Phase 9a sub-functionals — the face-dual circle-packing functional from the Java original and the vertex-based inversive-distance functional from Luo 2004 / Glickenstein 2011 — together with a side-by-side mathematical validation report. CGAL test count: 194 → 205 (+11 from 9a.2, +10 from 9a.1, was already +1 from 9a.1's setup defaults regression). Phase 9a.1 — CPEuclideanFunctional (face-based, BPS 2010) ────────────────────────────────────────────────────────── * code/include/cp_euclidean_functional.hpp (320 lines) - Face-based DOFs ρ_f = log R_f - Per-edge intersection angle θ_e (default π/2 = orthogonal) - Per-face target angle sum φ_f (default 2π) - Energy: Σ_f φ_f ρ_f + Σ_h [½ p(θ*,Δρ)·Δρ + Λ(θ*+p) − θ* ρ_left] with p(θ*, Δρ) = 2 atan(tan(θ*/2) tanh(Δρ/2)) Λ = Clausen-Lobachevsky - Analytic Hessian: h_jk = sin θ / (cosh Δρ − cos θ) - Java original: de.varylab.discreteconformal.functional.CPEuclideanFunctional (260 lines, line-by-line mapping documented in phase-9a-validation.md §1) * code/tests/cgal/test_cp_euclidean_functional.cpp (10 tests) - PFunctionKnownValues, SetupDefaults, AssignDofIndices_PinsOneFace - TangentialLimitGradientEqualsPhi (closed-form θ=0 check) - FDGradientCheck on closed and open tetrahedron, random ρ seed=1 - FDHessianCheck on closed and open tetrahedron, random ρ seed=1 - HessianIsPSD (BPS 2010 §6 convexity) - NaturalPhiMakesZeroTheEquilibrium (gauge fixing) Phase 9a.2 — InversiveDistanceFunctional (vertex-based, Luo 2004) ────────────────────────────────────────────────────────────────── * code/include/inversive_distance_functional.hpp (290 lines) - Vertex DOFs u_i = log r_i - Per-edge inversive distance I_ij from Bowers-Stephenson 2004: I_ij = (ℓ² − r_i² − r_j²) / (2 r_i r_j) - Edge length (Luo 2004 §3): ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i+u_j) - Gradient (Luo 2004 Lemma 3.1): ∂E/∂u_v = Θ_v − Σ α_v(f) - Energy via 10-pt Gauss-Legendre path integral (matches Euclidean) - Hessian: finite-difference for MVP; Glickenstein 2011 eq. 4.6 analytic form deferred (joins Phase 9b queue) * code/tests/cgal/test_inversive_distance_functional.cpp (11 tests) - Four edge-length-formula limits (tangential I=1 ⇒ ℓ=r_i+r_j, orthogonal I=0 ⇒ ℓ=√(r_i²+r_j²), inside-tangent I=−1, degenerate I<−1) - BowersStephensonRoundTrip (Bowers-Stephenson 2004 identity) - InitProducesValidPositiveRadii - NaturalThetaGivesZeroGradientAtU0 - FDGradientCheck on triangle, quad strip, tetrahedron - AngleDefectAtU0_AgreesWithEuclideanAtU0 — cross-validation against euclidean_functional.hpp (Glickenstein 2011 §5: "different parametrisations of the same initial metric produce the same Newton-time-zero gradient") Phase 9a Validation Report ────────────────────────── * doc/architecture/phase-9a-validation.md (350 lines) - Line-by-line mapping CPEuclideanFunctional.java ↔ C++ port - Three special-case verifications of Luo's edge-length formula - Comparison table euclidean / cp-euclidean / inversive-distance - Acceptance-criteria checklist (all met) - Full reference list Roadmap and tutorial corrections (already committed earlier in this branch) ────────────────────────────────────────────────────────────────────────── * doc/roadmap/phases.md — Phase 9a split into 9a.1 + 9a.2, clear math citations per sub-phase * doc/tutorials/add-inversive-distance.md — corrects the prior claim that InversiveDistanceFunctional.java exists upstream (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 as primary sources * CLAUDE.md — adds phase-9a-validation.md to doc map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Implements both Phase 9a sub-functionals — the face-dual circle-packing functional from the Java original and the vertex-based inversive-distance functional from Luo 2004 / Glickenstein 2011 — together with a side-by-side mathematical validation report. CGAL test count: 194 → 205 (+11 from 9a.2, +10 from 9a.1, was already +1 from 9a.1's setup defaults regression). Phase 9a.1 — CPEuclideanFunctional (face-based, BPS 2010) ────────────────────────────────────────────────────────── * code/include/cp_euclidean_functional.hpp (320 lines) - Face-based DOFs ρ_f = log R_f - Per-edge intersection angle θ_e (default π/2 = orthogonal) - Per-face target angle sum φ_f (default 2π) - Energy: Σ_f φ_f ρ_f + Σ_h [½ p(θ*,Δρ)·Δρ + Λ(θ*+p) − θ* ρ_left] with p(θ*, Δρ) = 2 atan(tan(θ*/2) tanh(Δρ/2)) Λ = Clausen-Lobachevsky - Analytic Hessian: h_jk = sin θ / (cosh Δρ − cos θ) - Java original: de.varylab.discreteconformal.functional.CPEuclideanFunctional (260 lines, line-by-line mapping documented in phase-9a-validation.md §1) * code/tests/cgal/test_cp_euclidean_functional.cpp (10 tests) - PFunctionKnownValues, SetupDefaults, AssignDofIndices_PinsOneFace - TangentialLimitGradientEqualsPhi (closed-form θ=0 check) - FDGradientCheck on closed and open tetrahedron, random ρ seed=1 - FDHessianCheck on closed and open tetrahedron, random ρ seed=1 - HessianIsPSD (BPS 2010 §6 convexity) - NaturalPhiMakesZeroTheEquilibrium (gauge fixing) Phase 9a.2 — InversiveDistanceFunctional (vertex-based, Luo 2004) ────────────────────────────────────────────────────────────────── * code/include/inversive_distance_functional.hpp (290 lines) - Vertex DOFs u_i = log r_i - Per-edge inversive distance I_ij from Bowers-Stephenson 2004: I_ij = (ℓ² − r_i² − r_j²) / (2 r_i r_j) - Edge length (Luo 2004 §3): ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i+u_j) - Gradient (Luo 2004 Lemma 3.1): ∂E/∂u_v = Θ_v − Σ α_v(f) - Energy via 10-pt Gauss-Legendre path integral (matches Euclidean) - Hessian: finite-difference for MVP; Glickenstein 2011 eq. 4.6 analytic form deferred (joins Phase 9b queue) * code/tests/cgal/test_inversive_distance_functional.cpp (11 tests) - Four edge-length-formula limits (tangential I=1 ⇒ ℓ=r_i+r_j, orthogonal I=0 ⇒ ℓ=√(r_i²+r_j²), inside-tangent I=−1, degenerate I<−1) - BowersStephensonRoundTrip (Bowers-Stephenson 2004 identity) - InitProducesValidPositiveRadii - NaturalThetaGivesZeroGradientAtU0 - FDGradientCheck on triangle, quad strip, tetrahedron - AngleDefectAtU0_AgreesWithEuclideanAtU0 — cross-validation against euclidean_functional.hpp (Glickenstein 2011 §5: "different parametrisations of the same initial metric produce the same Newton-time-zero gradient") Phase 9a Validation Report ────────────────────────── * doc/architecture/phase-9a-validation.md (350 lines) - Line-by-line mapping CPEuclideanFunctional.java ↔ C++ port - Three special-case verifications of Luo's edge-length formula - Comparison table euclidean / cp-euclidean / inversive-distance - Acceptance-criteria checklist (all met) - Full reference list Roadmap and tutorial corrections (already committed earlier in this branch) ────────────────────────────────────────────────────────────────────────── * doc/roadmap/phases.md — Phase 9a split into 9a.1 + 9a.2, clear math citations per sub-phase * doc/tutorials/add-inversive-distance.md — corrects the prior claim that InversiveDistanceFunctional.java exists upstream (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 as primary sources * CLAUDE.md — adds phase-9a-validation.md to doc map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Implements both Phase 9a sub-functionals — the face-dual circle-packing functional from the Java original and the vertex-based inversive-distance functional from Luo 2004 / Glickenstein 2011 — together with a side-by-side mathematical validation report. CGAL test count: 194 → 205 (+11 from 9a.2, +10 from 9a.1, was already +1 from 9a.1's setup defaults regression). Phase 9a.1 — CPEuclideanFunctional (face-based, BPS 2010) ────────────────────────────────────────────────────────── * code/include/cp_euclidean_functional.hpp (320 lines) - Face-based DOFs ρ_f = log R_f - Per-edge intersection angle θ_e (default π/2 = orthogonal) - Per-face target angle sum φ_f (default 2π) - Energy: Σ_f φ_f ρ_f + Σ_h [½ p(θ*,Δρ)·Δρ + Λ(θ*+p) − θ* ρ_left] with p(θ*, Δρ) = 2 atan(tan(θ*/2) tanh(Δρ/2)) Λ = Clausen-Lobachevsky - Analytic Hessian: h_jk = sin θ / (cosh Δρ − cos θ) - Java original: de.varylab.discreteconformal.functional.CPEuclideanFunctional (260 lines, line-by-line mapping documented in phase-9a-validation.md §1) * code/tests/cgal/test_cp_euclidean_functional.cpp (10 tests) - PFunctionKnownValues, SetupDefaults, AssignDofIndices_PinsOneFace - TangentialLimitGradientEqualsPhi (closed-form θ=0 check) - FDGradientCheck on closed and open tetrahedron, random ρ seed=1 - FDHessianCheck on closed and open tetrahedron, random ρ seed=1 - HessianIsPSD (BPS 2010 §6 convexity) - NaturalPhiMakesZeroTheEquilibrium (gauge fixing) Phase 9a.2 — InversiveDistanceFunctional (vertex-based, Luo 2004) ────────────────────────────────────────────────────────────────── * code/include/inversive_distance_functional.hpp (290 lines) - Vertex DOFs u_i = log r_i - Per-edge inversive distance I_ij from Bowers-Stephenson 2004: I_ij = (ℓ² − r_i² − r_j²) / (2 r_i r_j) - Edge length (Luo 2004 §3): ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i+u_j) - Gradient (Luo 2004 Lemma 3.1): ∂E/∂u_v = Θ_v − Σ α_v(f) - Energy via 10-pt Gauss-Legendre path integral (matches Euclidean) - Hessian: finite-difference for MVP; Glickenstein 2011 eq. 4.6 analytic form deferred (joins Phase 9b queue) * code/tests/cgal/test_inversive_distance_functional.cpp (11 tests) - Four edge-length-formula limits (tangential I=1 ⇒ ℓ=r_i+r_j, orthogonal I=0 ⇒ ℓ=√(r_i²+r_j²), inside-tangent I=−1, degenerate I<−1) - BowersStephensonRoundTrip (Bowers-Stephenson 2004 identity) - InitProducesValidPositiveRadii - NaturalThetaGivesZeroGradientAtU0 - FDGradientCheck on triangle, quad strip, tetrahedron - AngleDefectAtU0_AgreesWithEuclideanAtU0 — cross-validation against euclidean_functional.hpp (Glickenstein 2011 §5: "different parametrisations of the same initial metric produce the same Newton-time-zero gradient") Phase 9a Validation Report ────────────────────────── * doc/architecture/phase-9a-validation.md (350 lines) - Line-by-line mapping CPEuclideanFunctional.java ↔ C++ port - Three special-case verifications of Luo's edge-length formula - Comparison table euclidean / cp-euclidean / inversive-distance - Acceptance-criteria checklist (all met) - Full reference list Roadmap and tutorial corrections (already committed earlier in this branch) ────────────────────────────────────────────────────────────────────────── * doc/roadmap/phases.md — Phase 9a split into 9a.1 + 9a.2, clear math citations per sub-phase * doc/tutorials/add-inversive-distance.md — corrects the prior claim that InversiveDistanceFunctional.java exists upstream (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 as primary sources * CLAUDE.md — adds phase-9a-validation.md to doc map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Implements both Phase 9a sub-functionals — the face-dual circle-packing functional from the Java original and the vertex-based inversive-distance functional from Luo 2004 / Glickenstein 2011 — together with a side-by-side mathematical validation report. CGAL test count: 194 → 205 (+11 from 9a.2, +10 from 9a.1, was already +1 from 9a.1's setup defaults regression). Phase 9a.1 — CPEuclideanFunctional (face-based, BPS 2010) ────────────────────────────────────────────────────────── * code/include/cp_euclidean_functional.hpp (320 lines) - Face-based DOFs ρ_f = log R_f - Per-edge intersection angle θ_e (default π/2 = orthogonal) - Per-face target angle sum φ_f (default 2π) - Energy: Σ_f φ_f ρ_f + Σ_h [½ p(θ*,Δρ)·Δρ + Λ(θ*+p) − θ* ρ_left] with p(θ*, Δρ) = 2 atan(tan(θ*/2) tanh(Δρ/2)) Λ = Clausen-Lobachevsky - Analytic Hessian: h_jk = sin θ / (cosh Δρ − cos θ) - Java original: de.varylab.discreteconformal.functional.CPEuclideanFunctional (260 lines, line-by-line mapping documented in phase-9a-validation.md §1) * code/tests/cgal/test_cp_euclidean_functional.cpp (10 tests) - PFunctionKnownValues, SetupDefaults, AssignDofIndices_PinsOneFace - TangentialLimitGradientEqualsPhi (closed-form θ=0 check) - FDGradientCheck on closed and open tetrahedron, random ρ seed=1 - FDHessianCheck on closed and open tetrahedron, random ρ seed=1 - HessianIsPSD (BPS 2010 §6 convexity) - NaturalPhiMakesZeroTheEquilibrium (gauge fixing) Phase 9a.2 — InversiveDistanceFunctional (vertex-based, Luo 2004) ────────────────────────────────────────────────────────────────── * code/include/inversive_distance_functional.hpp (290 lines) - Vertex DOFs u_i = log r_i - Per-edge inversive distance I_ij from Bowers-Stephenson 2004: I_ij = (ℓ² − r_i² − r_j²) / (2 r_i r_j) - Edge length (Luo 2004 §3): ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i+u_j) - Gradient (Luo 2004 Lemma 3.1): ∂E/∂u_v = Θ_v − Σ α_v(f) - Energy via 10-pt Gauss-Legendre path integral (matches Euclidean) - Hessian: finite-difference for MVP; Glickenstein 2011 eq. 4.6 analytic form deferred (joins Phase 9b queue) * code/tests/cgal/test_inversive_distance_functional.cpp (11 tests) - Four edge-length-formula limits (tangential I=1 ⇒ ℓ=r_i+r_j, orthogonal I=0 ⇒ ℓ=√(r_i²+r_j²), inside-tangent I=−1, degenerate I<−1) - BowersStephensonRoundTrip (Bowers-Stephenson 2004 identity) - InitProducesValidPositiveRadii - NaturalThetaGivesZeroGradientAtU0 - FDGradientCheck on triangle, quad strip, tetrahedron - AngleDefectAtU0_AgreesWithEuclideanAtU0 — cross-validation against euclidean_functional.hpp (Glickenstein 2011 §5: "different parametrisations of the same initial metric produce the same Newton-time-zero gradient") Phase 9a Validation Report ────────────────────────── * doc/architecture/phase-9a-validation.md (350 lines) - Line-by-line mapping CPEuclideanFunctional.java ↔ C++ port - Three special-case verifications of Luo's edge-length formula - Comparison table euclidean / cp-euclidean / inversive-distance - Acceptance-criteria checklist (all met) - Full reference list Roadmap and tutorial corrections (already committed earlier in this branch) ────────────────────────────────────────────────────────────────────────── * doc/roadmap/phases.md — Phase 9a split into 9a.1 + 9a.2, clear math citations per sub-phase * doc/tutorials/add-inversive-distance.md — corrects the prior claim that InversiveDistanceFunctional.java exists upstream (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 as primary sources * CLAUDE.md — adds phase-9a-validation.md to doc map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Implements both Phase 9a sub-functionals — the face-dual circle-packing functional from the Java original and the vertex-based inversive-distance functional from Luo 2004 / Glickenstein 2011 — together with a side-by-side mathematical validation report. CGAL test count: 194 → 205 (+11 from 9a.2, +10 from 9a.1, was already +1 from 9a.1's setup defaults regression). Phase 9a.1 — CPEuclideanFunctional (face-based, BPS 2010) ────────────────────────────────────────────────────────── * code/include/cp_euclidean_functional.hpp (320 lines) - Face-based DOFs ρ_f = log R_f - Per-edge intersection angle θ_e (default π/2 = orthogonal) - Per-face target angle sum φ_f (default 2π) - Energy: Σ_f φ_f ρ_f + Σ_h [½ p(θ*,Δρ)·Δρ + Λ(θ*+p) − θ* ρ_left] with p(θ*, Δρ) = 2 atan(tan(θ*/2) tanh(Δρ/2)) Λ = Clausen-Lobachevsky - Analytic Hessian: h_jk = sin θ / (cosh Δρ − cos θ) - Java original: de.varylab.discreteconformal.functional.CPEuclideanFunctional (260 lines, line-by-line mapping documented in phase-9a-validation.md §1) * code/tests/cgal/test_cp_euclidean_functional.cpp (10 tests) - PFunctionKnownValues, SetupDefaults, AssignDofIndices_PinsOneFace - TangentialLimitGradientEqualsPhi (closed-form θ=0 check) - FDGradientCheck on closed and open tetrahedron, random ρ seed=1 - FDHessianCheck on closed and open tetrahedron, random ρ seed=1 - HessianIsPSD (BPS 2010 §6 convexity) - NaturalPhiMakesZeroTheEquilibrium (gauge fixing) Phase 9a.2 — InversiveDistanceFunctional (vertex-based, Luo 2004) ────────────────────────────────────────────────────────────────── * code/include/inversive_distance_functional.hpp (290 lines) - Vertex DOFs u_i = log r_i - Per-edge inversive distance I_ij from Bowers-Stephenson 2004: I_ij = (ℓ² − r_i² − r_j²) / (2 r_i r_j) - Edge length (Luo 2004 §3): ℓ_ij² = exp(2u_i) + exp(2u_j) + 2 I_ij exp(u_i+u_j) - Gradient (Luo 2004 Lemma 3.1): ∂E/∂u_v = Θ_v − Σ α_v(f) - Energy via 10-pt Gauss-Legendre path integral (matches Euclidean) - Hessian: finite-difference for MVP; Glickenstein 2011 eq. 4.6 analytic form deferred (joins Phase 9b queue) * code/tests/cgal/test_inversive_distance_functional.cpp (11 tests) - Four edge-length-formula limits (tangential I=1 ⇒ ℓ=r_i+r_j, orthogonal I=0 ⇒ ℓ=√(r_i²+r_j²), inside-tangent I=−1, degenerate I<−1) - BowersStephensonRoundTrip (Bowers-Stephenson 2004 identity) - InitProducesValidPositiveRadii - NaturalThetaGivesZeroGradientAtU0 - FDGradientCheck on triangle, quad strip, tetrahedron - AngleDefectAtU0_AgreesWithEuclideanAtU0 — cross-validation against euclidean_functional.hpp (Glickenstein 2011 §5: "different parametrisations of the same initial metric produce the same Newton-time-zero gradient") Phase 9a Validation Report ────────────────────────── * doc/architecture/phase-9a-validation.md (350 lines) - Line-by-line mapping CPEuclideanFunctional.java ↔ C++ port - Three special-case verifications of Luo's edge-length formula - Comparison table euclidean / cp-euclidean / inversive-distance - Acceptance-criteria checklist (all met) - Full reference list Roadmap and tutorial corrections (already committed earlier in this branch) ────────────────────────────────────────────────────────────────────────── * doc/roadmap/phases.md — Phase 9a split into 9a.1 + 9a.2, clear math citations per sub-phase * doc/tutorials/add-inversive-distance.md — corrects the prior claim that InversiveDistanceFunctional.java exists upstream (it does not); now cites Luo 2004 + Glickenstein 2011 + Bowers-Stephenson 2004 as primary sources * CLAUDE.md — adds phase-9a-validation.md to doc map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>