diff --git a/doc/roadmap/phases.md b/doc/roadmap/phases.md index 10b87e7..a44883b 100644 --- a/doc/roadmap/phases.md +++ b/doc/roadmap/phases.md @@ -75,11 +75,79 @@ mesh type. Java features from `de.varylab.discreteconformal` not yet in C++: ``` -9a Inversive-distance functional (Luo 2004 / Bowers–Stephenson) +9a Circle-packing / inversive-distance functionals + ─────────────────────────────────────────────── + Status: discovered during 9a-prep audit (2026-05-19) that the + original roadmap mention "inversive_distance_functional.hpp" was + based on a misreading — the Java repo at de.varylab.discreteconformal + does NOT contain InversiveDistanceFunctional.java. It contains the + related CPEuclideanFunctional.java which is the face-dual variant + (Bobenko-Pinkall-Springborn 2010). These are two mathematically + distinct models that both belong in this phase. Plan split: + +9a.1 CPEuclideanFunctional (FACE-based circle packing) + → cp_euclidean_functional.hpp + Java reference: CPEuclideanFunctional.java (260 lines) + test + Mathematical reference: + Bobenko, Pinkall, Springborn (2010) — "Discrete conformal maps + and ideal hyperbolic polyhedra", Geom. Topol. 14, 379-426. + DOFs: ρ_f per FACE (log-radius of the face-circle) + Constants: θ_e per edge (intersection angle, π/2 = orthogonal) + φ_f per face (target face-angle sum, default 2π) + Energy: E(ρ) = -Σ_f φ_f·ρ_f + + Σ_e [½·p(θ*_e, Δρ_e)·Δρ_e + Λ(θ*_e + p)] + where p = 2·atan(tan(θ*/2)·tanh(Δρ/2)), + Λ = Clausen function, θ* = π − θ. + Hessian: analytic, 2×2 per interior edge: + h_jk = sin(θ) / (cosh(Δρ) − cos(θ)) + Gauge: first face is pinned (ρ_0 = 0) + + New CGAL entry: CGAL::discrete_circle_packing_euclidean(mesh, np) + Test suite: test_cp_euclidean_functional.cpp + (Dodecahedron-with-removed-face, θ=π/2, + FD gradient check, FD-vs-analytic Hessian check) + +9a.2 Inversive-distance functional (VERTEX-based) → inversive_distance_functional.hpp - Follows the exact same pattern as the three existing functionals. - → newton_inversive_distance() - → New test suite: test_inversive_distance.cpp + Java reference: NONE (does not exist upstream) + Mathematical reference: + Luo, F. (2004) "Combinatorial Yamabe Flow on Surfaces", + Commun. Contemp. Math. 6(5), 765-780. + Bowers, P. & Stephenson, K. (2004) "Uniformizing dessins + and Belyĭ maps via circle packing", Mem. AMS 170(805). + Glickenstein, D. (2011) "Discrete conformal variations and + scalar curvature on piecewise flat manifolds", + J. Diff. Geom. 87(2), 201-238 (analytic Hessian). + DOFs: u_i per VERTEX (u_i = log r_i) + Constants: I_ij per edge (inversive distance, computed + once from initial geometry: + I_ij = (ℓ_ij² − r_i² − r_j²) + / (2 r_i r_j)) + Θ_v per vertex (target cone angle, default 2π) + Edge length: ℓ_ij(u)² = e^{2u_i} + e^{2u_j} + 2 I_ij e^{u_i+u_j} + Angles: identical half-tangent atan2 form to Euclidean + Gradient: ∂E/∂u_v = Θ_v − Σ_{T∋v} α_v(T) + (Luo 2004 Lemma 3.1) + Energy: path integral E(u) = ∫₀¹⟨G(tu),u⟩dt + (Luo's 1-form is closed; no general closed form, + use 10-point Gauss-Legendre as in Euclidean) + Hessian: FD for MVP; analytic (Glickenstein 2011 eq. 4.6) + as future optimisation + Gauge: first vertex pinned (u_0 = 0) + + New CGAL entry: CGAL::discrete_inversive_distance_map(mesh, np) + Test suite: test_inversive_distance_functional.cpp + (small triangle + quad + tetra, FD gradient + check, convergence test, special case I=1 + coincides with tangential circle packing) + +Cross-validation between 9a.1 and 9a.2 (Glickenstein 2011 §5): + In the tangential limit (θ_e = 0 in 9a.1 ⇔ I_ij = 1 in 9a.2), + both functionals describe the same circle packing. Their + converged radii must satisfy + ρ_f(9a.1) vs ½·log(r_i·r_j) (9a.2) + under the appropriate vertex↔face dual correspondence. This + cross-check is part of the Phase 9a acceptance tests. 9b Analytic HyperIdeal Hessian → Replace FD Hessian in hyper_ideal_hessian.hpp diff --git a/doc/tutorials/add-inversive-distance.md b/doc/tutorials/add-inversive-distance.md index 96bc2b1..6680546 100644 --- a/doc/tutorials/add-inversive-distance.md +++ b/doc/tutorials/add-inversive-distance.md @@ -1,8 +1,16 @@ -# Tutorial: Porting the Inversive-Distance Functional (Phase 9a) +# Tutorial: Porting the Inversive-Distance Functional (Phase 9a.2) This is a complete, step-by-step example of how to add a new functional -to conformallab++. It ports `InversiveDistanceFunctional.java` from the -Java reference implementation (Luo 2004). +to conformallab++. + +> **Note:** This tutorial implements the **vertex-based** inversive-distance +> circle packing of Luo (2004) and Glickenstein (2011). Despite an earlier +> draft of this document, the Java reference repository `varylab/conformallab` +> does **not** contain an `InversiveDistanceFunctional.java`. This is a +> from-the-literature implementation, validated by cross-checking against +> the **face-based** circle-packing functional `CPEuclideanFunctional.java` +> (Bobenko-Pinkall-Springborn 2010, see Phase 9a.1) in the tangential +> limit `I_ij = 1` ⇔ `θ_e = 0`. **Prerequisite:** Read [doc/api/extending.md](../api/extending.md) first for the general pattern. This tutorial fills in every detail for one @@ -12,22 +20,50 @@ specific case. ## Mathematical background -Inversive distance (Luo 2004) uses a different edge-length update formula: +Inversive distance circle packing parametrises each vertex by a circle of +radius `r_i = exp(u_i)`. For each edge, the *inversive distance* `I_ij` +between the two adjacent circles is a fixed constant of the edge, computed +once from the initial geometry: ``` -Given inversive distances I_{ij} ∈ ℝ for each edge {i,j}: - - cosh(l̃_{ij}) = I_{ij} · cosh((u_i + u_j) / 2) - + (cosh²((u_i - u_j) / 2) - 1) · ... +I_ij = (ℓ_ij² − r_i² − r_j²) / (2 r_i r_j) (Bowers-Stephenson) ``` -For the Euclidean version the angle formula is the same law of cosines, -but the log-lengths Λ̃ are computed differently from the u-vector. +| Range of `I_ij` | Geometric meaning | +|---|---| +| `I_ij = 1` | Tangential circles (Koebe-style) | +| `I_ij = cos φ ∈ (0,1)` | Overlapping circles with intersection angle φ | +| `I_ij = 0` | Orthogonal circles | +| `I_ij ∈ (−1, 0)` | Disjoint circles (inversive-distance > 1) | -**Reference:** Luo, F. (2004). *Combinatorial Yamabe Flow on Surfaces*. -Communications in Contemporary Mathematics, 6(5), 765–780. +The edge length is then determined by the radii via: -**Java source:** `de.varylab.discreteconformal.functional.InversiveDistanceFunctional` +``` +ℓ_ij(u)² = exp(2 u_i) + exp(2 u_j) + 2 I_ij exp(u_i + u_j) (Luo 2004 §3) +``` + +The angle formula is the same law of cosines (numerically stable +half-tangent form) as the Euclidean functional — only the edge-length +mapping changes. The gradient + +``` +∂E/∂u_i = Θ_i − Σ_{T ∋ i} α_i(T) +``` + +is the standard Yamabe-flow gradient (Luo 2004 Lemma 3.1). + +**Primary references:** +- Luo, F. (2004). *Combinatorial Yamabe Flow on Surfaces*. + Communications in Contemporary Mathematics, 6(5), 765–780. +- Bowers, P. L. & Stephenson, K. (2004). *Uniformizing dessins and + Belyĭ maps via circle packing*. Memoirs of the AMS 170(805). +- Glickenstein, D. (2011). *Discrete conformal variations and scalar + curvature on piecewise flat manifolds*. J. Differential Geometry + 87(2), 201–238 (analytic Hessian, eq. 4.6). + +**Java source:** none — this functional is implemented from the +above papers. See [doc/roadmap/phases.md](../roadmap/phases.md) §9a for +the historical clarification. ---