Files
ConformalLabpp/doc/math/geometry-modes.md
Tarik Moussa 202b9a108d feat(num): N3 selectable scale-floor clamp mode (HardJava | SmoothBarrier)
The hyper-ideal vertex scale b is floored to keep the geometry valid.  The
original clamp `b<0 → 0.01` mirrors the Java oracle but is only C⁰ (in fact
value-discontinuous at b=0): a Newton step crossing the feasibility boundary
hits a kink that can stall convergence (numerical-stability audit N3).

Rather than replace the Java-faithful behaviour (which would break the golden
parity tests), make the floor a selectable mode so BOTH the Java standpoint
and the clean mathematics are available:

- HyperIdealScaleClamp::HardJava (DEFAULT) — the original snap, bit-for-bit
  faithful to HyperIdealFunctional.java → all parity tests unchanged.
- HyperIdealScaleClamp::SmoothBarrier — C¹ softplus floor
  b ↦ floor + softplus_β(b−floor), β = HYPER_IDEAL_SCALE_SHARPNESS (=100);
  ≈ identity away from the floor, smooth across b=0.  Opt-in.

clamp_hyper_ideal_scale centralises the logic (also folds in the N4 nachzügler:
compute_face_angles used a bare 0.01).  The mode threads with a defaulted
trailing parameter through compute_face_angles, face_angles_from_local_dofs,
evaluate_hyper_ideal, the four hyper_ideal_hessian* variants and
newton_hyper_ideal — so every existing call site keeps HardJava behaviour.

Tests (+4): clamp-function C¹/floor/identity contract, mode-equivalence away
from the boundary, and end-to-end SmoothBarrier convergence to the same Java
golden vector (LawsonHyperIdeal).  296/296 CGAL tests pass.
Documented in doc/math/geometry-modes.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:44:50 +02:00

6.6 KiB
Raw Permalink Blame History

The Three Geometry Modes

conformallab++ implements discrete conformal geometry in three model spaces. All three share the same algorithmic structure — only the angle formula, the trilateration geometry, and the Hessian sign differ.


Comparison

                  Euclidean            Spherical            Hyper-ideal
                  ─────────────────────────────────────────────────────
Space             ℝ²                   S²                   H² (Poincaré disk)
Curvature         K = 0                K = +1               K = 1
Typical genus     any (cone metrics)   0                    ≥ 1
Angle sum         Σαᵥ = Θᵥ             Σαᵥ = Θᵥ             Σβᵥ = Θᵥ
Gradient sign     G_v = actual  Θᵥ    G_v = Θᵥ  actual    G_v = actual  Θᵥ
Hessian           PSD (cotangent-Lap.) NSD (sign-flip)       PSD (FD, strict conv.)
Newton solve      SimplicialLDLT(H)    SimplicialLDLT(H)    SimplicialLDLT(H)
Holonomy          translations ωᵢ      rotations (2-D)       Möbius maps Tᵢ ∈ SU(1,1)
Period            τ = ω₂/ω₁ ∈         —                     axis of Tᵢ
Normalise         PCA centring         Rodrigues to N pole   weighted Möbius centring
Layout            Layout2D (ℝ²)        Layout3D (unit sphere) Layout2D (Poincaré disk)

Euclidean (ℝ²)

Energy: the cotangent-weighted angle defect energy (Pinkall & Polthier 1993).

Effective log-length:

Λ̃ᵢⱼ = λ°ᵢⱼ + uᵢ + uⱼ

Gradient: G_v = Σ_{faces adj. v} α_v(face) Θᵥ — the actual angle sum minus the target.

Hessian: cotangent Laplacian, assembled in euclidean_hessian.hpp. PSD with one zero eigenvalue (constant function) for closed meshes — handled by pinning one vertex or SparseQR.

Normalisation: centroid → origin, major axis → x-axis via PCA (normalise_euclidean).

Holonomy: lattice translations ω₁, ω₂ ∈ for closed surfaces. Period ratio τ = ω₂/ω₁ ∈ is the conformal modulus of the torus.


Spherical (S²)

Typical use: genus-0 (sphere-like) surfaces.

Gradient: G_v = Θᵥ Σ α_v(face) — note the sign reversal vs. Euclidean.

Hessian: NSD (the spherical energy is concave). Newton solves (H)·Δx = G, i.e. SimplicialLDLT is called on H. This is handled transparently in newton_spherical().

Gauge fix: for a closed spherical surface, one additional DOF must be pinned to remove the rotational gauge mode. SphericalMaps has a dedicated gauge_vertex field.

Normalisation: normalise_spherical() rotates the layout so the area centroid maps to the north pole (Rodrigues rotation formula).


Hyper-ideal (H²)

Typical use: genus-g surfaces (g ≥ 1), hyperbolic cone metrics.

DOFs: both vertex variables bᵢ (hyper-ideal radius) and edge variables aₑ (intersection angle). assign_all_dof_indices(mesh, maps) assigns both automatically. No vertex needs to be pinned — the functional is strictly convex.

Geometry functions (in hyper_ideal_geometry.hpp):

ζ₁₃(b, a)  ζ₁₄(b, a)  ζ₁₅(b, a)   — the three fundamental Springborn functions
lᵢⱼ(ζ)                             — edge length from ζ value
αᵢⱼ(l₁, l₂, l₃)                   — interior angle
βᵢ(l₁, l₂, l₃)                    — vertex angle sum contribution

Hessian: a symmetric finite-difference approximation (see hyper_ideal_hessian.hpp). The solver uses the per-face block-FD variant by default (O(F) face evaluations, ~331166× faster than full-FD); the full-FD baseline is retained for cross-validation. The analytic Hessian through the (bᵢ, aₑ) → lᵢⱼ → ζ → αᵢⱼ/βᵢ chain is Phase 9b-analytic.

Scale-floor clamp mode (HyperIdealScaleClamp). The vertex scale bᵢ must stay positive for the geometry to be valid. When a Newton iterate drives bᵢ toward or below zero, one of two floors is applied — selectable per solver call (last argument of newton_hyper_ideal, default HardJava):

Mode Behaviour When to use
HardJava (default) bᵢ < 0 → HYPER_IDEAL_SCALE_FLOOR (= 0.01). Bit-for-bit faithful to HyperIdealFunctional.java, so the Java golden-oracle parity tests hold exactly. It is only C⁰ (in fact value-discontinuous at bᵢ = 0), so a step crossing the feasibility boundary sees a kink that can stall Newton. Default — preserves parity; correct whenever scales stay positive (the normal case).
SmoothBarrier bᵢ ↦ floor + softplusβ(bᵢ floor) with β = HYPER_IDEAL_SCALE_SHARPNESS (= 100). C¹ everywhere, ≈ bᵢ to machine precision once β·(bᵢfloor) ≳ 35, and decays smoothly to floor as bᵢ → −∞. Removes the N3 kink, but perturbs values near the boundary and therefore deviates from the Java oracle there. Opt in when a solve is expected to cross the feasibility boundary and convergence robustness matters more than strict Java parity.

Both modes share HYPER_IDEAL_SCALE_FLOOR; the edge clamp (aₑ < 0 → 0) is unchanged. Away from the floor (the normal bᵢ ≫ 0.01 regime) the two modes are numerically identical — the LawsonHyperIdeal.SmoothBarrierConvergesToGoldenVector test confirms the smooth mode reaches the same golden solution as HardJava. Implemented in clamp_hyper_ideal_scale (hyper_ideal_functional.hpp); origin: numerical-stability audit finding N3.

Holonomy: Möbius isometries Tᵢ ∈ SU(1,1) (orientation-preserving isometries of the Poincaré disk). MobiusMap in layout.hpp: T(z) = (az+b)/(cz+d).

Normalisation: normalise_hyperbolic() performs iterative face-area-weighted Möbius centring (Fréchet mean, 30 iterations) to map the weighted centroid to the disk origin.


GaussBonnet constraint

Before calling any Newton solver, the target angles must satisfy the GaussBonnet equation:

Σᵥ (2π  Θᵥ)  =  2π · χ(M)    (Euclidean / flat)
Σᵥ (2π  Θᵥ)  >  0             (spherical, χ > 0)
Σᵥ (2π  Θᵥ)  <  0             (hyperbolic, χ < 0)

If this is violated, no conformal factor can realise the target angles and Newton will fail to converge without a visible error.

check_gauss_bonnet(mesh, maps);      // throws if violated
enforce_gauss_bonnet(mesh, maps);    // redistributes residual uniformly across all vertices

This is the most common source of silent non-convergence. Always call one of these before solving.