# 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:** currently a symmetric finite-difference approximation (see `hyper_ideal_hessian.hpp`).
The analytic Hessian through the `(bᵢ, aₑ) → lᵢⱼ → ζ → αᵢⱼ/βᵢ` chain is Phase 9b.

**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.

---

## Gauss–Bonnet constraint

Before calling any Newton solver, the target angles must satisfy the Gauss–Bonnet 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.
