feat(phase6): exact hyperbolic layout, Gauss–Bonnet, cut graph, normalisation — 121 tests

New files:
- gauss_bonnet.hpp: euler_characteristic, genus, Σ(2π-Θ_v) sum/rhs/deficit,
  check_gauss_bonnet (throws), enforce_gauss_bonnet (correct sign: Δ=(lhs-rhs)/V)
- cut_graph.hpp: CutGraph struct + compute_cut_graph (tree-cotree, Erickson–Whittlesey
  2005); boundary edges correctly excluded from cut set
- test_phase6.cpp: 26 new tests (GaussBonnet ×8, CutGraph ×6, HyperbolicTrilateration
  ×4, Normalisation ×4 — all pass)

layout.hpp (Phase 6 rewrite):
- detail::trilaterate_hyp: exact Möbius + hyperbolic law of cosines replacing old tanh(d/2)
- detail::center_poincare_disk: Möbius centering for hyperbolic normalisation
- normalise_euclidean: centroid → origin + PCA major-axis rotation
- normalise_hyperbolic: Möbius centering in the Poincaré disk
- normalise_spherical: Rodrigues rotation → north pole
- euclidean_layout / hyper_ideal_layout: optional CutGraph* + HolonomyData* + normalise

Bug fixes caught by new tests:
- gauss_bonnet.hpp: enforce_gauss_bonnet had wrong sign for delta
- cut_graph.hpp: boundary edges were incorrectly marked as cut edges

121 tests pass, 2 skipped (Hessian stubs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-13 01:15:41 +02:00
parent 24f7607b2d
commit 4fc48b39f0
6 changed files with 1100 additions and 146 deletions

View File

@@ -4,7 +4,7 @@ conformallab++ is a modern C++ reimplementation of the [ConformalLab](https://gi
The long-term goal is a **CGAL package** that brings discrete conformal maps (hyper-ideal, spherical, Euclidean) to the CGAL ecosystem using `CGAL::Surface_mesh` as the underlying half-edge data structure.
> **Status:** Phase 5 vollständig abgeschlossen. Alle drei Geometrien lösbar via Newton-Solver (SimplicialLDLT + SparseQR-Fallback). BFS-Layout in ℝ²/S²/Poincaré-Disk, JSON/XML-Serialisierung, vollständige CLI-App. **95 Tests, 2 skipped**.
> **Status:** Phase 6 vollständig abgeschlossen. Alle drei Geometrien lösbar via Newton-Solver (SimplicialLDLT + SparseQR-Fallback). BFS-Layout in ℝ²/S²/Poincaré-Disk mit exakter hyperbolischer Trilateration (Möbius + Kosinussatz), GaussBonnet-Konsistenzprüfung, Tree-Cotree-Schnittgraph, Normalisierung (PCA/Möbius-Zentrierung). JSON/XML-Serialisierung, vollständige CLI-App. **121 Tests, 2 skipped**.
---
@@ -29,6 +29,10 @@ The long-term goal is a **CGAL package** that brings discrete conformal maps (hy
| **BFS Layout** (ℝ², S², Poincaré disk) | ✅ Phase 5 |
| **CLI app** (`conformallab_core`) | ✅ Phase 5 |
| **JSON + XML serialisation** | ✅ Phase 5 |
| **GaussBonnet check + enforce** | ✅ Phase 6 |
| **Tree-cotree cut graph** (2g seam edges) | ✅ Phase 6 |
| **Exact hyperbolic trilateration** (Möbius + law of cosines) | ✅ Phase 6 |
| **Layout normalisation** (PCA centring / Möbius centering) | ✅ Phase 6 |
---
@@ -218,7 +222,7 @@ ctest --test-dir build -R "^cgal\." --output-on-failure
./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json
```
Expected: **95 tests pass, 2 skipped** (the two `@Ignore` Hessian stubs).
Expected: **121 tests pass, 2 skipped** (the two `@Ignore` Hessian stubs).
### Interactive viewer
@@ -250,8 +254,10 @@ cmake -S code -B build -DWITH_CGAL=ON && cmake --build build -t example_viewer -
| `mesh_io.hpp` | `read_mesh` / `write_mesh` / `load_mesh` / `save_mesh` |
| `conformal_mesh.hpp` | `ConformalMesh` = `CGAL::Surface_mesh<Point3>` + property-map helpers |
| `mesh_builder.hpp` | `make_triangle` / `make_tetrahedron` / `make_quad_strip` / `make_fan` / `make_spherical_tetrahedron` |
| `layout.hpp` | `euclidean_layout` / `spherical_layout` / `hyper_ideal_layout``Layout2D/3D`; BFS unfolding |
| `layout.hpp` | `euclidean_layout` / `spherical_layout` / `hyper_ideal_layout``Layout2D/3D`; exact hyperbolic trilateration; normalise_{euclidean,hyperbolic,spherical}; `CutGraph*` + `HolonomyData*` |
| `serialization.hpp` | `save/load_result_json` + `save/load_result_xml` |
| `gauss_bonnet.hpp` | `euler_characteristic`, `genus`, `gauss_bonnet_sum/rhs/deficit`, `check_gauss_bonnet`, `enforce_gauss_bonnet` |
| `cut_graph.hpp` | `CutGraph` struct + `compute_cut_graph` (tree-cotree, EricksonWhittlesey 2005) |
| `mesh_utils.hpp` | CGAL → Eigen conversion (`cgal_to_eigen`) |
| `constants.hpp` | `conformallab::PI`, `TWO_PI` |
@@ -267,8 +273,10 @@ code/
│ ├── mesh_io.hpp
│ ├── mesh_utils.hpp
│ ├── newton_solver.hpp # ← public solve_linear_system + 3 Newton solvers
│ ├── layout.hpp # ← BFS layout (euclidean/spherical/hyper_ideal)
│ ├── layout.hpp # ← BFS layout + exact hyp. trilateration + normalise
│ ├── serialization.hpp # ← JSON + XML save/load
│ ├── gauss_bonnet.hpp # ← GaussBonnet check + enforce (Phase 6)
│ ├── cut_graph.hpp # ← Tree-cotree cut-graph algorithm (Phase 6)
│ ├── hyper_ideal_{functional,hessian,geometry,utility,visualization_utility}.hpp
│ ├── spherical_{functional,hessian,geometry}.hpp
│ ├── euclidean_{functional,hessian,geometry}.hpp
@@ -297,7 +305,8 @@ code/
│ ├── test_newton_solver.cpp # 14 tests (incl. 3 SparseQR tests)
│ ├── test_mesh_io.cpp # 6 tests
│ ├── test_pipeline.cpp # 5 tests
── test_layout.cpp # 8 tests (layout + JSON/XML)
── test_layout.cpp # 8 tests (layout + JSON/XML)
│ └── test_phase6.cpp # 26 tests (GB, cut graph, trilateration, normalisation)
└── deps/
├── eigen-3.4.0/ # always extracted
├── CGAL-6.1.1/ # extracted with WITH_CGAL
@@ -334,7 +343,11 @@ Pure-math tests requiring only Eigen: Clausen / Lobachevsky / ImLi₂, hyper-ide
| `Pipeline` | 5 | End-to-end: build → setup → solve → export → reload, all three geometries |
| `Layout` | 6 | Edge-length preservation (Euclidean/Spherical), arc-lengths on S², Poincaré disk |
| `Serialization` | 2 | JSON and XML round-trips (DOF + layout) |
| **Total** | **95** | 2 skipped (Hessian stubs) |
| `GaussBonnet` | 8 | χ, genus, sum/rhs, deficit, check, enforce (sign-correct) |
| `CutGraph` | 6 | Tree-cotree algorithm, open/closed meshes, flagindex consistency |
| `HyperbolicTrilateration` | 4 | Möbius + law of cosines: exact distances, inside-disk, off-origin |
| `Normalisation` | 4 | Euclidean centroid, length-ratio invariance, Möbius centering (hyp.) |
| **Total** | **121** | 2 skipped (Hessian stubs) |
---
@@ -498,11 +511,13 @@ GaussBonnet pre-check ✅ ❌
### What "cone metrics" still requires
**Cone metrics** — the property map `theta_v` is already subtracted in the gradient (`G_v = Σα_v Θ_v`), so prescribing a cone angle is a one-liner: `maps.theta_v[v] = desired_angle`. What is missing is the *application layer*: checking GaussBonnet consistency (Σ (2π Θ_v) = 2π·χ), distributing angle defects sensibly, and special handling at boundary vertices.
**Cone metrics** — the property map `theta_v` is already subtracted in the gradient (`G_v = Σα_v Θ_v`), so prescribing a cone angle is a one-liner: `maps.theta_v[v] = desired_angle`. Use `check_gauss_bonnet(mesh, maps)` to verify Σ(2πΘ_v) = 2π·χ before solving, and `enforce_gauss_bonnet` to fix floating-point drift.
**Layout (Phase 5 ✅)**`layout.hpp` implements BFS unfolding for all three geometries via `euclidean_layout`, `spherical_layout`, and `hyper_ideal_layout`. For open meshes the embedding is globally consistent; for closed meshes the first BFS visit wins and `has_seam = true` is set. To get a proper parameterisation of a closed mesh, cut it to a disk first (not yet implemented).
**Layout (Phase 6 ✅)**`layout.hpp` implements BFS unfolding for all three geometries. The hyperbolic layout now uses **exact trilateration** via Möbius maps and the hyperbolic law of cosines (replacing the old `tanh(d/2)` approximation). Pass a `CutGraph*` to track seam edges on closed surfaces, and a `HolonomyData*` to capture the holonomy group elements. Set `normalise=true` for PCA centring (Euclidean), Möbius centering (hyperbolic), or Rodrigues rotation to the north pole (spherical).
**Next steps** — global uniformization (cutting closed meshes, period matrices, holonomy), GaussBonnet checking, analytical HyperIdeal Hessian.
**Cut graph (Phase 6 ✅)**`compute_cut_graph(mesh)` implements the tree-cotree algorithm (EricksonWhittlesey 2005). For a closed genus-g surface it returns exactly 2g seam edges whose removal turns the surface into a topological disk.
**Next steps** — holonomy matrices / period matrix (genus-1 torus: τ = ω₂/ω₁ ∈ ), analytical HyperIdeal Hessian, full global uniformization pipeline.
---
@@ -928,10 +943,21 @@ Phase 5 Layout + CLI + Serialisierung ✅ abgeschlossen
→ test_layout.cpp: 8 Tests (Eucl./Sphär./HyperIdeal + JSON/XML)
→ 95 Tests gesamt (2 skipped)
Phase 6 (geplant)
Phase 6 Layout-Erweiterung (Java-Parität) ✅ abgeschlossen
→ gauss_bonnet.hpp: euler_characteristic, genus, Σ(2π-Θ_v) check + enforce
→ cut_graph.hpp: Tree-Cotree-Algorithmus (Erickson-Whittlesey); 2g Schnittkan.
→ layout.hpp: exakte hyperbolische Trilateration (Möbius + hyperb. Kosinussatz)
→ layout.hpp: normalise_euclidean (Schwerpunkt→0, PCA-Rotation)
→ layout.hpp: normalise_hyperbolic (Möbius-Zentrierung im Poincaré-Disk)
→ layout.hpp: normalise_spherical (Rodrigues-Rotation zum Nordpol)
→ layout.hpp: CutGraph* + HolonomyData* Parameter für alle Layout-Funktionen
→ test_phase6.cpp: 26 Tests (GB, CutGraph, Trilateration, Normalisierung)
→ 121 Tests gesamt (2 skipped)
Phase 7 (geplant)
→ Analytischer HyperIdeal-Hessian (direkte Ableitung durch ζ-Kette)
GaussBonnet Konsistenzprüfung für Kegelmetriken
Mesh-Cut für geschlossene Flächen → globale Parameterisierung
Holonomie-Matrizen für Tori (Periodenmatrix τ = ω₂/ω₁ ∈ )
Vollständige globale Parameterisierung geschlossener Flächen
→ Inversive-Distance-Funktional (Luo 2004)
```