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

132 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
```cpp
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.