diff --git a/README.md b/README.md index 5cbb0c2..b5b56d4 100644 --- a/README.md +++ b/README.md @@ -4,161 +4,161 @@ 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 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), Gauss–Bonnet-Konsistenzprüfung, Tree-Cotree-Schnittgraph, Normalisierung (PCA/Möbius-Zentrierung). JSON/XML-Serialisierung, vollständige CLI-App. **121 Tests, 2 skipped**. +> **Status:** Phase 7 vollständig abgeschlossen. Alle drei Geometrien lösbar via Newton-Solver (SimplicialLDLT + SparseQR-Fallback). Priority-BFS-Layout in ℝ²/S²/Poincaré-Disk mit exakter hyperbolischer Trilateration, Gauss–Bonnet, Tree-Cotree-Schnittgraph, Möbius-Holonomie, Periodenmatrix (Genus 1), Fundamentalbereich-Polygon, halfedge_uv-Texturatlas. JSON/XML-Serialisierung, vollständige CLI-App. **158 Tests, 2 skipped**. --- ## Features -| Area | Status | -|------|--------| -| Clausen / Lobachevsky / ImLi₂ functions | ✅ Phase 1 | -| Hyper-ideal geometry (ζ, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ) | ✅ Phase 2 | -| CGAL `Surface_mesh` infrastructure + mesh builders | ✅ Phase 3a | -| Hyper-ideal functional (energy + gradient) | ✅ Phase 3b | -| Spherical functional (energy + gradient + gauge-fix) | ✅ Phase 3c/3e | -| Euclidean functional (energy + gradient) | ✅ Phase 3d | -| Euclidean Hessian (cotangent-Laplace, Pinkall–Polthier) | ✅ Phase 3f | -| Spherical Hessian (∂α/∂u from law of cosines) | ✅ Phase 3f | -| Hyper-ideal Hessian (numerical FD, symmetrised) | ✅ Phase 4a | -| Newton solver — all three geometries | ✅ Phase 4a | -| **SparseQR fallback** for rank-deficient H (gauge modes) | ✅ Phase 4a | +| Bereich | Status | +|---------|--------| +| Clausen / Lobachevsky / ImLi₂ Funktionen | ✅ Phase 1 | +| Hyper-ideal Geometrie (ζ, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ) | ✅ Phase 2 | +| CGAL `Surface_mesh` Infrastruktur + Mesh-Builder | ✅ Phase 3a | +| Hyper-ideal Funktional (Energie + Gradient) | ✅ Phase 3b | +| Sphärisches Funktional (Energie + Gradient + Gauge-Fix) | ✅ Phase 3c/3e | +| Euklidisches Funktional (Energie + Gradient) | ✅ Phase 3d | +| Euklidischer Hessian (Kotangenten-Laplace, Pinkall–Polthier) | ✅ Phase 3f | +| Sphärischer Hessian (∂α/∂u aus Kosinussatz) | ✅ Phase 3f | +| Hyper-ideal Hessian (numerisch FD, symmetrisiert) | ✅ Phase 4a | +| Newton-Solver — alle drei Geometrien | ✅ Phase 4a | +| **SparseQR-Fallback** für rangdefiziente H (Gauge-Moden) | ✅ Phase 4a | | Mesh I/O (CGAL::IO — OFF / OBJ / PLY) | ✅ Phase 4b | -| End-to-end pipeline tests | ✅ Phase 4c | -| **Example programs** (headless + interactive viewer) | ✅ Phase 4d | -| **BFS Layout** (ℝ², S², Poincaré disk) | ✅ Phase 5 | -| **CLI app** (`conformallab_core`) | ✅ Phase 5 | -| **JSON + XML serialisation** | ✅ Phase 5 | -| **Gauss–Bonnet 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 | +| End-to-End-Pipeline-Tests | ✅ Phase 4c | +| **Beispiel-Programme** (headless + interaktiver Viewer) | ✅ Phase 4d | +| **BFS-Layout** (ℝ², S², Poincaré-Disk) | ✅ Phase 5 | +| **CLI-App** (`conformallab_core`) | ✅ Phase 5 | +| **JSON + XML Serialisierung** | ✅ Phase 5 | +| **Gauss–Bonnet Check + Enforce** | ✅ Phase 6 | +| **Tree-Cotree-Schnittgraph** (2g Naht-Kanten) | ✅ Phase 6 | +| **Exakte hyperbolische Trilateration** (Möbius + Kosinussatz) | ✅ Phase 6 | +| **Layout-Normalisierung** (PCA-Zentrierung / Möbius-Zentrierung) | ✅ Phase 6 | +| **Priority-BFS** (Min-Heap über BFS-Tiefe, minimiert Fehlerakkumulation) | ✅ Phase 7 | +| **MobiusMap** — T(z)=(az+b)/(cz+d), from_three, compose, inverse | ✅ Phase 7 | +| **halfedge_uv** — Naht-bewusstes UV pro Halfedge (GPU-Texturatlas) | ✅ Phase 7 | +| **Möbius-Holonomie** — SU(1,1)-Isometrie pro Schnitt-Kante (hyperbolisch) | ✅ Phase 7 | +| **Periodenmatrix** — τ = ω₂/ω₁ ∈ ℍ, SL(2,ℤ)-Reduktion (Genus 1) | ✅ Phase 7 | +| **Fundamentalbereich** — CCW-Parallelogramm, Kachelkopien | ✅ Phase 7 | +| Analytischer HyperIdeal-Hessian (ζ-Kette) | ❌ Phase 8 geplant | +| Inversive-Distance-Funktional (Luo 2004) | ❌ nicht portiert | +| Vollständige globale Uniformisierung Genus g ≥ 2 | ❌ Phase 8 geplant | +| Periodenmatrix Siegel Ω (g×g, g ≥ 2) | ❌ Phase 8 geplant | --- -## Quick start — CLI app +## Quick Start — CLI-App ```bash cmake -S code -B build -DWITH_CGAL=ON cmake --build build -j4 -# Run the conformal map CLI on any OFF/OBJ/PLY mesh +# Konformes Layout für beliebige OFF/OBJ/PLY-Netze ./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json -x result.xml -# Available geometries: euclidean | spherical | hyper_ideal +# Geometrien: euclidean | spherical | hyper_ideal ./bin/conformallab_core -i input.off -g spherical -o sphere.off -# Show input mesh in interactive viewer (built-in) +# Interaktiver Viewer ./bin/conformallab_core -i input.off -s ``` -### Example programs +### Beispiel-Programme ```bash -# Layout + JSON/XML round-trip demo -./build/examples/example_layout [input.off] [layout.off] [result.json] [result.xml] - -# Headless pipelines -./build/examples/example_euclidean [input.off] [output.off] -./build/examples/example_hyper_ideal [input.off] [output.off] - -# Interactive viewer (requires -DWITH_CGAL=ON, viewer is built automatically) -./build/examples/example_viewer [input.off] +./build/examples/example_layout [input.off] [layout.off] [result.json] [result.xml] +./build/examples/example_euclidean [input.off] [output.off] +./build/examples/example_hyper_ideal [input.off] [output.off] +./build/examples/example_viewer [input.off] # interaktiv (WITH_VIEWER) ``` -If no input file is given each example uses a built-in quad-strip mesh. - --- -## Library usage — minimal Euclidean pipeline +## Bibliotheks-Nutzung + +### Minimale euklidische Pipeline ```cpp #include "conformal_mesh.hpp" -#include "mesh_builder.hpp" #include "mesh_io.hpp" #include "euclidean_functional.hpp" #include "newton_solver.hpp" +#include "layout.hpp" using namespace conformallab; int main() { - // 1. Load mesh ConformalMesh mesh = load_mesh("input.off"); - // 2. Set up functional maps auto maps = setup_euclidean_maps(mesh); compute_euclidean_lambda0_from_mesh(mesh, maps); - // 3. Assign DOFs — pin first vertex (gauge fix) + // DOFs zuweisen — ersten Vertex pinnen (Gauge-Fix) auto vit = mesh.vertices().begin(); - maps.v_idx[*vit++] = -1; // pinned: u[v0] = 0 + maps.v_idx[*vit++] = -1; int idx = 0; for (; vit != mesh.vertices().end(); ++vit) maps.v_idx[*vit] = idx++; - const int n = idx; - // 4. Set target angles (natural equilibrium: x* = 0) - std::vector x0(n, 0.0); + // Zielwinkel: natürliches Gleichgewicht (x* = 0) + std::vector x0(idx, 0.0); auto G0 = euclidean_gradient(mesh, x0, maps); for (auto v : mesh.vertices()) { int iv = maps.v_idx[v]; if (iv >= 0) maps.theta_v[v] -= G0[iv]; } - // 5. Solve - auto result = newton_euclidean(mesh, std::vector(n, -0.1), maps); - - // 6. Save result - if (result.converged) + auto res = newton_euclidean(mesh, x0, maps); + if (res.converged) save_mesh("output.off", mesh); - - return result.converged ? 0 : 1; + return res.converged ? 0 : 1; } ``` -For **HyperIdeal** geometry: +### Layout + Holonomie (geschlossene Flächen) + +```cpp +#include "layout.hpp" +#include "cut_graph.hpp" +#include "period_matrix.hpp" +#include "fundamental_domain.hpp" + +// Schnittgraph berechnen (Tree-Cotree, 2g Kanten) +CutGraph cg = compute_cut_graph(mesh); + +// Layout mit Holonomie-Tracking +HolonomyData hol; +Layout2D lay = euclidean_layout(mesh, res.x, maps, &cg, &hol, /*normalise=*/true); + +// Periodenmatrix (Genus 1) +PeriodData pd = compute_period_matrix(hol); // τ = ω₂/ω₁ ∈ ℍ, SL(2,ℤ)-reduziert +std::cout << "τ = " << pd.tau << "\n"; + +// Fundamentalbereich-Parallelogramm +FundamentalDomain fd = compute_fundamental_domain(hol); +// fd.vertices — 4 Ecken (CCW) +// fd.generators — ω₁, ω₂ + +// Kachelkopie für Universalüberlagerung +Layout2D tile = tiling_copy(lay, fd.generators[0], fd.generators[1], 1, -1); + +// halfedge_uv — Naht-bewusstes UV pro Halfedge (GPU-Texturatlas) +// lay.halfedge_uv[h.idx()] = UV von source(h) aus Sicht von face(h) +``` + +### HyperIdeal-Geometrie ```cpp auto maps = setup_hyper_ideal_maps(mesh); int n = assign_all_dof_indices(mesh, maps); -// … set theta_v / theta_e targets … auto result = newton_hyper_ideal(mesh, x0, maps); + +// Möbius-Holonomie (SU(1,1)-Isometrien pro Schnitt-Kante) +HolonomyData hol; +Layout2D lay = hyper_ideal_layout(mesh, result.x, maps, &cg, &hol); +// hol.mobius_maps[i] = T_i (Möbius-Abbildung für i-te Schnitt-Kante) ``` -### Layout (embedding into target space) - -After solving, convert scale factors into actual vertex coordinates: - -```cpp -#include "layout.hpp" -#include "serialization.hpp" - -// Euclidean: flat 2-D positions in ℝ² -Layout2D layout = euclidean_layout(mesh, result.x, maps); -// layout.uv[v.idx()] = Eigen::Vector2d - -// Spherical: unit vectors on S² ⊂ ℝ³ -Layout3D slayout = spherical_layout(mesh, result.x, smaps); -// slayout.pos[v.idx()] = Eigen::Vector3d - -// HyperIdeal: Poincaré disk coordinates in ℝ² -Layout2D hlayout = hyper_ideal_layout(mesh, result.x, hmaps); - -// Save layout as OFF -save_layout_off("layout.off", mesh, layout); - -// Serialise to JSON / XML -save_result_json("result.json", result, "euclidean", - mesh.number_of_vertices(), mesh.number_of_faces(), &layout); -save_result_xml("result.xml", result, "euclidean", - mesh.number_of_vertices(), mesh.number_of_faces(), &layout); - -// Load back -NewtonResult res2; std::string geom; Layout2D uv2; -auto x = load_result_json("result.json", &res2, &geom, &uv2); -``` - -Using **`solve_linear_system`** directly (with fallback detection): +### SparseQR-Fallback direkt nutzen ```cpp #include "newton_solver.hpp" @@ -166,43 +166,41 @@ Using **`solve_linear_system`** directly (with fallback detection): bool used_fallback = false; auto dx = conformallab::solve_linear_system(H, rhs, &used_fallback); if (used_fallback) - std::cout << "SparseQR was used (H is rank-deficient)\n"; + std::cout << "SparseQR verwendet (H ist rangdefizient)\n"; ``` --- -## Build modes +## Build-Modi -| Mode | CMake flags | What gets built | CI | -|------|------------|-----------------|-----| -| **Tests only** (default) | *(none)* | `conformallab_tests` — Eigen + GTest only | ✅ automatic | -| **CGAL tests + examples** | `-DWITH_CGAL=ON` | `conformallab_cgal_tests`, `example_euclidean`, `example_hyper_ideal`, `example_layout`, `conformallab_core` (CLI) | local only | -| **Interactive viewer** | `-DWITH_CGAL=ON` *(implied)* | above + `example_viewer` + viewer linked into CLI | local only | +| Modus | CMake-Flags | Was wird gebaut | +|-------|------------|-----------------| +| **Nur Tests** (Standard) | *(keine)* | `conformallab_tests` — Eigen + GTest | +| **CGAL-Tests + Beispiele** | `-DWITH_CGAL=ON` | `conformallab_cgal_tests`, Beispiel-Programme, CLI | +| **Interaktiver Viewer** | `-DWITH_CGAL=ON` | + `example_viewer`, Viewer in CLI integriert | -External dependencies are bundled as tarballs in `code/deps/tarballs/` and extracted lazily at CMake configure time (GTest is fetched via `FetchContent`). - -**Boost** is required only with `-DWITH_CGAL=ON` (header-only use by CGAL 6.x). +Externe Abhängigkeiten sind als Tarballs in `code/deps/tarballs/` enthalten und werden beim CMake-Configure-Schritt extrahiert (GTest via `FetchContent`). **Boost** wird nur mit `-DWITH_CGAL=ON` benötigt (Header-Only durch CGAL 6.x). --- -## Prerequisites +## Voraussetzungen | Tool | Minimum | |------|---------| -| C++ compiler (GCC or Clang) | C++17 | +| C++ Compiler (GCC oder Clang) | C++17 | | CMake | 3.20 | -| Boost headers | 1.70 *(only with `-DWITH_CGAL=ON`)* | +| Boost Headers | 1.70 *(nur mit `-DWITH_CGAL=ON`)* | --- -## Getting started +## Einstieg ```bash git clone https://codeberg.org/TMoussa/ConformalLabpp cd ConformalLabpp ``` -### Tests only (CI default — no system deps) +### Nur Tests (CI-Standard — keine System-Abhängigkeiten) ```bash cmake -S code -B build @@ -210,686 +208,313 @@ cmake --build build --target conformallab_tests -j$(nproc) ctest --test-dir build --output-on-failure ``` -### CGAL tests + headless examples (needs system Boost) +### CGAL-Tests + Beispiele (benötigt System-Boost) ```bash cmake -S code -B build -DWITH_CGAL=ON cmake --build build -j$(nproc) ctest --test-dir build -R "^cgal\." --output-on-failure -./build/examples/example_euclidean -./build/examples/example_hyper_ideal ./build/examples/example_layout -./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json +./bin/conformallab_core -i input.off -g euclidean -o layout.off ``` -Expected: **121 tests pass, 2 skipped** (the two `@Ignore` Hessian stubs). +Erwartet: **158 Tests bestanden, 2 skipped** (die zwei `@Ignore`-Hessian-Stubs). -### Interactive viewer +### Interaktiver Viewer ```bash -# WITH_CGAL=ON already implies viewer; example_viewer is built automatically -cmake -S code -B build -DWITH_CGAL=ON && cmake --build build -t example_viewer -j$(nproc) +cmake -S code -B build -DWITH_CGAL=ON +cmake --build build -t example_viewer -j$(nproc) ./build/examples/example_viewer data/off/example.off ``` --- -## Public headers (`code/include/`) +## Öffentliche Header (`code/include/`) -| Header | Description | +| Header | Beschreibung | |--------|-------------| | `clausen.hpp` | Clausen Cl₂, Lobachevsky Л, ImLi₂ | -| `hyper_ideal_geometry.hpp` | ζ functions, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ | -| `hyper_ideal_utility.hpp` | Tetrahedron volume (Meyerhoff / Kolpakov–Mednykh) | -| `hyper_ideal_functional.hpp` | HyperIdeal energy + gradient on `ConformalMesh` | -| `hyper_ideal_hessian.hpp` | HyperIdeal Hessian (numerical FD, symmetrised) | -| `hyper_ideal_visualization_utility.hpp` | Poincaré disk projection, circumcircle helpers | -| `spherical_geometry.hpp` | Spherical arc length, half-angle formula | -| `spherical_functional.hpp` | Spherical energy + gradient + gauge-fix | -| `spherical_hessian.hpp` | Spherical Hessian (∂α/∂u, law of cosines) | -| `euclidean_geometry.hpp` | Euclidean corner-angle (t-value / atan2) | -| `euclidean_functional.hpp` | Euclidean energy + gradient | -| `euclidean_hessian.hpp` | Cotangent-Laplace Hessian (Pinkall–Polthier) | -| `newton_solver.hpp` | `newton_euclidean` / `newton_spherical` / `newton_hyper_ideal` + public **`solve_linear_system`** | +| `hyper_ideal_geometry.hpp` | ζ-Funktionen, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ | +| `hyper_ideal_utility.hpp` | Tetraeder-Volumen (Meyerhoff / Kolpakov–Mednykh) | +| `hyper_ideal_functional.hpp` | HyperIdeal Energie + Gradient auf `ConformalMesh` | +| `hyper_ideal_hessian.hpp` | HyperIdeal Hessian (numerisch FD, symmetrisiert) | +| `hyper_ideal_visualization_utility.hpp` | Poincaré-Disk-Projektion, Umkreis-Helfer | +| `spherical_geometry.hpp` | Sphärische Bogenlänge, Halbwinkelformel | +| `spherical_functional.hpp` | Sphärisch: Energie + Gradient + Gauge-Fix | +| `spherical_hessian.hpp` | Sphärischer Hessian (∂α/∂u, Kosinussatz) | +| `euclidean_geometry.hpp` | Euklidischer Eckenwinkel (t-Wert / atan2) | +| `euclidean_functional.hpp` | Euklidisch: Energie + Gradient | +| `euclidean_hessian.hpp` | Kotangenten-Laplace Hessian (Pinkall–Polthier) | +| `newton_solver.hpp` | `newton_{euclidean,spherical,hyper_ideal}` + öffentliches `solve_linear_system` | +| `conformal_mesh.hpp` | `ConformalMesh` = `CGAL::Surface_mesh` + Property-Map-Helfer | +| `mesh_builder.hpp` | `make_triangle` / `make_tetrahedron` / `make_quad_strip` / `make_fan` / … | | `mesh_io.hpp` | `read_mesh` / `write_mesh` / `load_mesh` / `save_mesh` | -| `conformal_mesh.hpp` | `ConformalMesh` = `CGAL::Surface_mesh` + 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`; exact hyperbolic trilateration; normalise_{euclidean,hyperbolic,spherical}; `CutGraph*` + `HolonomyData*` | +| `mesh_utils.hpp` | CGAL → Eigen Konvertierung (`cgal_to_eigen`) | | `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, Erickson–Whittlesey 2005) | -| `mesh_utils.hpp` | CGAL → Eigen conversion (`cgal_to_eigen`) | +| `cut_graph.hpp` | `CutGraph` + `compute_cut_graph` (Tree-Cotree, Erickson–Whittlesey 2005) | +| `layout.hpp` | `euclidean/spherical/hyper_ideal_layout` → `Layout2D/3D`; `MobiusMap`; `halfedge_uv`; Priority-BFS; `HolonomyData`; `normalise_*` | +| `period_matrix.hpp` | `PeriodData`, `compute_period_matrix`, `reduce_to_fundamental_domain`, `is_in_fundamental_domain` | +| `fundamental_domain.hpp` | `FundamentalDomain`, `compute_fundamental_domain_{genus1,}`, `tiling_copy`, `tiling_neighbourhood` | | `constants.hpp` | `conformallab::PI`, `TWO_PI` | --- -## Project structure +## Projektstruktur ``` code/ -├── include/ # All public headers (header-only library) +├── include/ # Alle öffentlichen Header (Header-Only-Bibliothek) │ ├── conformal_mesh.hpp │ ├── mesh_builder.hpp -│ ├── mesh_io.hpp -│ ├── mesh_utils.hpp -│ ├── newton_solver.hpp # ← public solve_linear_system + 3 Newton solvers -│ ├── layout.hpp # ← BFS layout + exact hyp. trilateration + normalise -│ ├── serialization.hpp # ← JSON + XML save/load -│ ├── gauss_bonnet.hpp # ← Gauss–Bonnet check + enforce (Phase 6) -│ ├── cut_graph.hpp # ← Tree-cotree cut-graph algorithm (Phase 6) +│ ├── mesh_io.hpp / mesh_utils.hpp +│ ├── newton_solver.hpp # 3 Newton-Solver + solve_linear_system +│ ├── layout.hpp # Priority-BFS, MobiusMap, halfedge_uv, Holonomie +│ ├── serialization.hpp +│ ├── gauss_bonnet.hpp +│ ├── cut_graph.hpp # Tree-Cotree +│ ├── period_matrix.hpp # τ ∈ ℍ, SL(2,ℤ)-Reduktion (Genus 1) +│ ├── fundamental_domain.hpp # Parallelogramm, Kacheln; 4g-Polygon TODO Phase 8 │ ├── hyper_ideal_{functional,hessian,geometry,utility,visualization_utility}.hpp │ ├── spherical_{functional,hessian,geometry}.hpp │ ├── euclidean_{functional,hessian,geometry}.hpp │ ├── clausen.hpp │ └── constants.hpp -├── examples/ # Standalone example programs -│ ├── CMakeLists.txt -│ ├── example_euclidean.cpp # Headless Euclidean pipeline -│ ├── example_hyper_ideal.cpp # Headless HyperIdeal pipeline -│ ├── example_layout.cpp # Solve → layout → OFF/JSON/XML round-trip -│ └── example_viewer.cpp # Interactive libigl viewer (WITH_VIEWER) +├── examples/ +│ ├── example_euclidean.cpp +│ ├── example_hyper_ideal.cpp +│ ├── example_layout.cpp # Solve → Layout → OFF/JSON/XML + Round-Trip +│ └── example_viewer.cpp # Interaktiver libigl-Viewer ├── src/ -│ ├── apps/v0/conformallab_cli.cpp # Full CLI app (Phase 5) +│ ├── apps/v0/conformallab_cli.cpp # CLI-App (Phase 5) │ └── viewer/simple_viewer.cpp ├── tests/ -│ ├── CMakeLists.txt -│ ├── *.cpp # conformallab_tests (no CGAL) +│ ├── *.cpp # conformallab_tests (kein CGAL) │ └── cgal/ -│ ├── CMakeLists.txt -│ ├── test_conformal_mesh.cpp # 14 tests -│ ├── test_hyper_ideal_functional.cpp # 7 tests (1 skipped) -│ ├── test_spherical_functional.cpp # 11 tests (1 skipped) -│ ├── test_euclidean_functional.cpp # 11 tests -│ ├── test_euclidean_hessian.cpp # 8 tests -│ ├── test_spherical_hessian.cpp # 8 tests -│ ├── 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_phase6.cpp # 26 tests (GB, cut graph, trilateration, normalisation) +│ ├── test_conformal_mesh.cpp # 14 Tests +│ ├── test_hyper_ideal_functional.cpp # 7 Tests +│ ├── test_spherical_functional.cpp # 12 Tests (1 skipped) +│ ├── test_euclidean_functional.cpp # 11 Tests (1 skipped) +│ ├── test_euclidean_hessian.cpp # 9 Tests +│ ├── test_spherical_hessian.cpp # 8 Tests +│ ├── test_newton_solver.cpp # 14 Tests +│ ├── test_mesh_io.cpp # 9 Tests +│ ├── test_pipeline.cpp # 5 Tests +│ ├── test_layout.cpp # 8 Tests (Layout + JSON/XML) +│ ├── test_phase6.cpp # 26 Tests (GB, CutGraph, Trilateration) +│ └── test_phase7.cpp # 37 Tests (MobiusMap, Priority-BFS, +│ # halfedge_uv, Periodenmatrix, FD) └── deps/ - ├── eigen-3.4.0/ # always extracted - ├── CGAL-6.1.1/ # extracted with WITH_CGAL - ├── libigl-2.6.0/ # extracted with WITH_VIEWER - ├── glfw-3.4/ # extracted with WITH_VIEWER - ├── libigl-glad/ - └── single_includes/ # CLI11, json.hpp + ├── eigen-3.4.0/ + ├── CGAL-6.1.1/ + ├── libigl-2.6.0/ + ├── glfw-3.4/ + └── single_includes/ # CLI11, json.hpp ``` --- -## Test suites +## Test-Suiten -### `conformallab_tests` (CI — always built) +### `conformallab_tests` (CI — immer gebaut) -Pure-math tests requiring only Eigen: Clausen / Lobachevsky / ImLi₂, hyper-ideal geometry, tetrahedron volumes. +Reine Mathe-Tests, nur Eigen: Clausen / Lobachevsky / ImLi₂, Hyper-ideal Geometrie, Tetraeder-Volumina. -### `conformallab_cgal_tests` (local — `-DWITH_CGAL=ON`) +### `conformallab_cgal_tests` (lokal — `-DWITH_CGAL=ON`) -| Suite | Tests | What it checks | -|-------|------:|----------------| -| `ConformalMeshTopology` | 4 | Euler characteristic, vertex/edge/face counts | -| `ConformalMeshTraversal` | 4 | Halfedge iteration, valence, opposite | -| `ConformalMeshProperties` | 5 | Property maps (λ, θ, idx, α, geometry type) | -| `ConformalMeshValidity` | 1 | CGAL validity for all factory meshes | -| `HyperIdealFunctional` | 7 | FD gradient checks + Hessian symmetry | -| `SphericalFunctional` | 11 | Angle formula + gradient + gauge-fix | -| `EuclideanFunctional` | 11 | Angle formula + gradient | -| `EuclideanHessian` | 8 | Cotangent-Laplace structure, FD agreement, PSD, null space | -| `SphericalHessian` | 8 | Derivative correctness, NSD at equilibrium | -| `NewtonSolver` | 11 | Convergence (Euclidean ×3, Spherical ×4, HyperIdeal ×4) | -| `SparseQRFallback` | 3 | Full-rank LDLT path · singular matrix triggers QR · closed-mesh gauge-mode | -| `MeshIO` | 6 | OFF/OBJ round-trips, error handling | -| `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) | -| `GaussBonnet` | 8 | χ, genus, sum/rhs, deficit, check, enforce (sign-correct) | -| `CutGraph` | 6 | Tree-cotree algorithm, open/closed meshes, flag–index 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) | +| Suite | Tests | Was geprüft wird | +|-------|------:|-----------------| +| `ConformalMeshTopology` | 4 | Euler-Charakteristik, Vertex/Edge/Face-Anzahl | +| `ConformalMeshTraversal` | 4 | Halfedge-Iteration, Valenz, Opposite | +| `ConformalMeshProperties` | 5 | Property-Maps (λ, θ, idx, α, Geometrietyp) | +| `ConformalMeshValidity` | 1 | CGAL-Validität für alle Factory-Meshes | +| `HyperIdealFunctional` | 7 | FD-Gradient-Checks + Hessian-Symmetrie | +| `SphericalFunctional` | 12 | Winkelformel + Gradient + Gauge-Fix (1 skipped) | +| `EuclideanFunctional` | 11 | Winkelformel + Gradient (1 skipped) | +| `EuclideanHessian` | 9 | Kotangenten-Laplace-Struktur, FD-Übereinstimmung, PSD, Nullraum | +| `SphericalHessian` | 8 | Ableitungskorrektheit, NSD am Gleichgewicht | +| `NewtonSolver` | 11 | Konvergenz (Eucl. ×3, Sphär. ×4, HyperIdeal ×4) | +| `SparseQRFallback` | 3 | Full-Rank-LDLT · singuläre Matrix → QR · geschlossenes Mesh | +| `MeshIO` | 9 | OFF/OBJ Round-Trips, Fehlerbehandlung | +| `Pipeline` | 5 | End-to-End: Build → Setup → Solve → Export → Reload | +| `Layout` | 8 | Kantenlängen-Erhaltung (Eucl./Sphär.), Poincaré-Disk | +| `Serialization` | 2 | JSON- und XML-Round-Trips (DOF + Layout) | +| `GaussBonnet` | 8 | χ, Genus, Summe/RHS, Defizit, Check, Enforce | +| `CutGraph` | 6 | Tree-Cotree, offene/geschlossene Meshes, Flag–Index-Konsistenz | +| `HyperbolicTrilateration` | 4 | Möbius + Kosinussatz: exakte Abstände, Disk-Inneres, off-origin | +| `Normalisation` | 4 | Eukl. Schwerpunkt, Längenverhältnisse, Möbius-Zentrierung | +| `MobiusMap` | 8 | Identity, Inverse, Compose, from_three, apply(Vector2d) | +| `BestRootFace` | 2 | Gültige Wurzel-Fläche, Interior-Bonus | +| `HalfedgeUV` | 4 | Größe = #Halfedges, Naht-Konsistenz, Randhalfedges = 0 | +| `PriorityBFS` | 3 | Erfolg, kein Seam bei offenen Meshes, alle Vertices platziert | +| `NormaliseEuclidean` | 2 | UV-Schwerpunkt = 0, halfedge_uv-Schwerpunkt = 0 | +| `PeriodMatrix` | 7 | τ ∈ ℍ, SL(2,ℤ)-Reduktion, Ausnahme außerhalb ℍ | +| `FundamentalDomain` | 7 | Genus-1-Parallelogramm CCW, Generatoren, g>1 leer | +| `TilingCopy/Neighbourhood` | 4 | Verschiebung korrekt, Anzahl Kacheln | +| **Gesamt** | **158** | **2 skipped** (Hessian-Stubs, identisch mit Java `@Ignore`) | --- -## Newton solver & SparseQR fallback +## Newton-Solver & SparseQR-Fallback -`newton_solver.hpp` exposes three solvers with a unified interface: +`newton_solver.hpp` stellt drei Solver mit einheitlicher Schnittstelle bereit: ``` -NewtonResult newton_euclidean (mesh, x0, maps [, tol, max_iter]) -NewtonResult newton_spherical (mesh, x0, maps [, tol, max_iter]) +NewtonResult newton_euclidean (mesh, x0, maps [, tol, max_iter]) +NewtonResult newton_spherical (mesh, x0, maps [, tol, max_iter]) NewtonResult newton_hyper_ideal(mesh, x0, maps [, tol, max_iter, hess_eps]) ``` -Each iteration: -1. Evaluate gradient **G** -2. Evaluate Hessian **H** (analytical for Euclidean / Spherical; numerical FD for HyperIdeal) -3. Solve **H·Δx = −G** — try `Eigen::SimplicialLDLT`, fall back to `Eigen::SparseQR` on failure -4. Backtracking line search (up to 20 halvings) +Jede Iteration: Gradient **G** → Hessian **H** → löse **H·Δx = −G** (SimplicialLDLT, Fallback SparseQR) → Backtracking-Liniensuche. -**Gradient sign conventions:** +| Geometrie | **G** | **H**-Vorzeichen | +|-----------|-------|-----------------| +| Euklidisch | Θ_v − Σα_v | PSD → LDLT auf H | +| Sphärisch | Θ_v − Σα_v | NSD → LDLT auf **−H** | +| HyperIdeal | Σβ_v − Θ_v | PSD → LDLT auf H | -| Geometry | **G** | **H** sign | -|----------|-------|-----------| -| Euclidean | Θ_v − Σα_v | PSD → LDLT on H | -| Spherical | Θ_v − Σα_v | NSD → LDLT on **−H** | -| HyperIdeal | Σβ_v − Θ_v | PSD → LDLT on H | - -**SparseQR fallback** (`solve_linear_system`): -When `SimplicialLDLT` fails (singular/rank-deficient **H**, e.g. gauge modes on closed meshes without a pinned vertex), `SparseQR` finds the minimum-norm Newton step orthogonal to the null space. Because the gradient always lies in the row space of **H**, the solver converges correctly without requiring the caller to pin a vertex. - -The fallback is a **public API**: - -```cpp -bool used_fallback = false; -auto dx = conformallab::solve_linear_system(H, rhs, &used_fallback); -``` +**SparseQR-Fallback** (`solve_linear_system`): Bei rangdefizientem **H** (Gauge-Moden bei geschlossenen Meshes ohne gepinnten Vertex) findet SparseQR den minimalen Newton-Schritt orthogonal zum Nullraum. --- -## Mathematical scope — C++ vs. Java original +## Mathematischer Umfang — C++ vs. Java-Original -The three core functionals are fully equivalent to the Java original at the level of energy, gradient, and Hessian formulas. The table below shows where parity holds, where there is a numerical difference, and what is not yet ported. - -| Mathematical layer | Java ConformalLab | conformallab++ | +| Mathematische Schicht | Java ConformalLab | conformallab++ | |---|---|---| -| **Euclidean functional** — energy, gradient | ✅ | ✅ | -| **Spherical functional** — energy, gradient, gauge-fix | ✅ | ✅ | -| **HyperIdeal functional** — energy, gradient | ✅ | ✅ | -| **Inversive-distance functional** (Luo 2004, Bowers–Stephenson) | ✅ | ❌ not ported | -| **Euclidean Hessian** — cotangent-Laplace (Pinkall–Polthier 1993) | ✅ analytical | ✅ analytical | -| **Spherical Hessian** — ∂α/∂u from law of cosines | ✅ analytical | ✅ analytical | -| **HyperIdeal Hessian** — through ζ → lᵢⱼ → β/α chain | ✅ analytical | ⚠️ symmetric FD | -| **Newton solver** | ✅ | ✅ | -| SparseQR fallback for gauge-mode null spaces | ? | ✅ | -| **Cone metrics** — prescribed Θ_v ≠ 2π | ✅ full pipeline | ⚠️ data structure only | -| **Boundary conditions** — Dirichlet u=f, Neumann, free boundary | ✅ | ⚠️ pin-only | -| **Layout / embedding** — DOF vector → vertex coordinates in ℝ² / H² / S² | ✅ | ✅ BFS unfolding (all three geometries) | -| **Gauss–Bonnet consistency check** on target angles | ✅ | ❌ | -| **Global uniformization** for genus g ≥ 1 | ✅ | ❌ | -| **Period matrices** — Teichmüller parameters for g ≥ 2 | ✅ | ❌ | -| **Holonomy / monodromy** | ✅ | ❌ | -| **HyperIdeal generator** — constructing geometrically valid meshes | ✅ | ❌ only test meshes | -| Clausen / Lobachevsky / ImLi₂ special functions | ✅ | ✅ | -| Discrete elliptic utility (modular normalisation of τ) | ✅ | ✅ (not yet wired up) | -| Poincaré disk / Lorentz boost visualisation helpers | ✅ | ✅ | -| Mesh I/O + serialisation | ✅ XML/CoHDS | ✅ OFF/OBJ/PLY + JSON/XML | -| Interactive viewer | ✅ jReality | ✅ libigl/GLFW | +| Euklidisches Funktional — Energie, Gradient | ✅ | ✅ | +| Sphärisches Funktional — Energie, Gradient, Gauge-Fix | ✅ | ✅ | +| HyperIdeal Funktional — Energie, Gradient | ✅ | ✅ | +| Inversive-Distance-Funktional (Luo 2004) | ✅ | ❌ nicht portiert | +| Euklidischer Hessian — Kotangenten-Laplace | ✅ analytisch | ✅ analytisch | +| Sphärischer Hessian — ∂α/∂u aus Kosinussatz | ✅ analytisch | ✅ analytisch | +| HyperIdeal Hessian — ζ → lᵢⱼ → β/α Kette | ✅ analytisch | ⚠️ symmetrisches FD | +| Newton-Solver | ✅ | ✅ | +| SparseQR-Fallback für Gauge-Moden | ? | ✅ | +| Kegelmetriken — vorgeschriebenes Θ_v ≠ 2π | ✅ vollständig | ⚠️ nur Datenstruktur | +| Layout / Einbettung — ℝ² / H² / S² | ✅ | ✅ Priority-BFS, alle drei | +| Exakte hyperbolische Trilateration | ✅ Möbius | ✅ Möbius + Kosinussatz | +| halfedge_uv — Naht-bewusstes UV (Texturatlas) | ✅ | ✅ | +| Gauss–Bonnet Konsistenzprüfung | ✅ | ✅ | +| Tree-Cotree-Schnittgraph (2g Kanten) | ✅ | ✅ Erickson–Whittlesey | +| Holonomie / Monodromie — Euklidisch (Translationen) | ✅ | ✅ | +| Holonomie — Hyperbolisch (SU(1,1) Möbius-Mappe) | ✅ | ✅ | +| Periodenmatrix τ — Genus 1 (SL(2,ℤ)-reduziert) | ✅ | ✅ | +| Fundamentalbereich-Polygon — Genus 1 | ✅ | ✅ CCW-Parallelogramm | +| 4g-Polygon-Randlauf — Genus g > 1 | ✅ | ❌ TODO Phase 8 | +| Periodenmatrix Siegel Ω — Genus g ≥ 2 | ✅ | ❌ TODO Phase 8 | +| Globale Uniformisierung — Genus g ≥ 2 | ✅ | ❌ Phase 8 geplant | +| Clausen / Lobachevsky / ImLi₂ | ✅ | ✅ | +| Poincaré-Disk / Lorentz-Boost Visualisierung | ✅ | ✅ | +| Mesh I/O + Serialisierung | ✅ XML/CoHDS | ✅ OFF/OBJ/PLY + JSON/XML | +| Interaktiver Viewer | ✅ jReality | ✅ libigl/GLFW | -### Key numerical difference — HyperIdeal Hessian +### HyperIdeal Hessian — numerisch vs. analytisch -The analytical Hessian of the HyperIdeal functional requires differentiating through the chain - -``` -(b_i, a_e) → l_ij → ζ₁₃/ζ₁₄/ζ₁₅ → α_ij / β_i -``` - -which is feasible but involves many nested cases (four vertex-type combinations per edge). Until Phase 6 delivers the analytical version, conformallab++ uses a symmetric finite-difference Hessian: +Der analytische Hessian des HyperIdeal-Funktionals erfordert Differentiation durch die Kette `(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/₁₄/₁₅ → αᵢⱼ / βᵢ` (vier Vertex-Typ-Kombinationen pro Kante). conformallab++ verwendet stattdessen einen symmetrisierten Finite-Differenzen-Hessian: ``` H[i,j] = ( G(x + ε·eⱼ)[i] − G(x − ε·eⱼ)[i] ) / (2ε) ``` -This is O(ε²) accurate (≈ 10⁻¹⁰ relative error at ε = 10⁻⁵), positive semi-definite by strict convexity of the HyperIdeal energy (Springborn 2020), and costs n extra gradient evaluations per Newton step instead of O(n). For meshes with fewer than ~500 DOFs the difference in wall time is negligible. - -### Layout — detailed comparison with the Java original - -The BFS-trilateration algorithm itself is the same in both implementations. The differences are in the surrounding infrastructure: - -#### 1. Hyperbolic trilateration — approximation vs. exact - -In the Euclidean and spherical cases the C++ trilateration is **mathematically identical** to the Java original. -For the HyperIdeal / Poincaré disk case the two diverge: - -| | Java ConformalLab | conformallab++ | -|---|---|---| -| Poincaré disk trilateration | **Exact** via Möbius transformations | **Approximation**: `r = tanh(d/2)`, then Euclidean trilaterate | - -Java computes the new point by: (i) Möbius-map `p_a` to the origin, (ii) rotate so `p_b` lies on the positive real axis, (iii) apply the standard on-axis hyperbolic trilateration formula, (iv) invert the Möbius map. The result is exact in the hyperbolic metric. - -The C++ approximation replaces the hyperbolic distance `d` by the Poincaré-disk Euclidean radius `tanh(d/2)` and then calls `trilaterate_2d`. This is first-order accurate near the origin (small triangles) but degrades towards the boundary of the unit disk (large `d`, highly curved triangles). For the Newton solver this does not matter — the solver works entirely in log-scale `x`-space and never calls the layout — but the embedded coordinates will deviate from the true hyperbolic positions for deep hyperbolic triangulations. - -**To implement the exact version** replace `trilaterate_hyp` in `layout.hpp` with: - -```cpp -// Möbius map: send point p to the origin (unit disk) -auto mobius_to_origin = [](Eigen::Vector2d p, Eigen::Vector2d center) { - // T(z) = (z - center) / (1 - conj(center)*z) [complex arithmetic in R^2] - Eigen::Vector2d num = p - center; - double den_re = 1.0 - (center.x()*p.x() + center.y()*p.y()); - double den_im = (center.x()*p.y() - center.y()*p.x()); - double den2 = den_re*den_re + den_im*den_im; - return Eigen::Vector2d( - (num.x()*den_re + num.y()*den_im) / den2, - (num.y()*den_re - num.x()*den_im) / den2); -}; -// After mapping p_a → 0, p_b lies on the real axis. -// The on-axis formula for left-side trilateration then applies. -``` - -#### 2. Closed meshes — seam flag vs. cut + period matrices - -| | Java | conformallab++ | -|---|---|---| -| Open mesh (with boundary) | BFS unfolding | BFS unfolding ✅ identical | -| Closed mesh, genus 0 (sphere) | BFS + Möbius normalisation | BFS, `has_seam = true` | -| Closed mesh, genus ≥ 1 | Full cut + holonomy + period matrix | ❌ not implemented | - -For a closed mesh the BFS in both implementations eventually tries to place a vertex that was already placed from the other side of the seam. Java records the **holonomy element** — the Möbius transformation (Euclidean: rigid motion; hyperbolic: Möbius; spherical: rotation) that maps the "first visit" position to the "second visit" position. These holonomy elements around the two generators of the fundamental group $\pi_1(\Sigma_g)$ are the **monodromy data** that determine the conformal structure of the surface. - -For genus $g = 0$ (topological sphere) the holonomy is trivial after a Möbius normalization and the layout closes up. For genus $g \geq 1$ the holonomy data feeds into the **period matrix** computation; the lattice $\Lambda \subset \mathbb{C}$ for a torus is read off from the two holonomy elements of the single handle. - -#### 3. Layout normalisation - -Java applies a post-processing Möbius transformation (Euclidean: rigid motion + scaling; hyperbolic: Möbius centring) to bring the layout into a canonical form. C++ returns the raw BFS output: vertex 0 at the origin, vertex 1 on the positive x-axis. - -#### 4. Gauss–Bonnet consistency check - -Before solving, Java verifies that the prescribed target angles satisfy - -$$\sum_{v} (2\pi - \Theta_v) = 2\pi \cdot \chi(M)$$ - -(Gauss–Bonnet for the target metric). If this fails, no conformal factor exists that realises those angles and Java aborts with a meaningful error. C++ has no such check; Newton will fail to converge silently. - -#### Summary - -``` - Java C++ -───────────────────────────────────────────────────── -BFS-trilateration (open) ✅ ✅ identical -Euclidean trilateration ✅ ✅ identical -Spherical trilateration ✅ ✅ identical -Hyperbolic trilateration ✅ exact ⚠️ tanh(d/2) approx -Closed mesh genus 0 ✅ ⚠️ has_seam flag only -Closed mesh genus ≥ 1 ✅ ❌ -Holonomy / monodromy ✅ ❌ -Period matrices ✅ ❌ -Layout normalisation ✅ Möbius ❌ raw output -Gauss–Bonnet 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`. Use `check_gauss_bonnet(mesh, maps)` to verify Σ(2π−Θ_v) = 2π·χ before solving, and `enforce_gauss_bonnet` to fix floating-point drift. - -**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). - -**Cut graph (Phase 6 ✅)** — `compute_cut_graph(mesh)` implements the tree-cotree algorithm (Erickson–Whittlesey 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. +O(ε²)-genau (≈ 10⁻¹⁰ relativer Fehler bei ε = 10⁻⁵), PSD durch strikte Konvexität (Springborn 2020), kostet n zusätzliche Gradient-Auswertungen pro Newton-Schritt. Für Meshes mit < 500 DOFs ist der Unterschied in der Wandzeit vernachlässigbar. Der analytische Hessian ist für Phase 8 geplant. --- -## For mathematicians — extending the library +## Für Mathematiker — Bibliothek erweitern -This section explains how to add new functionals, test conjectures numerically, and hook into the existing solver infrastructure, with no assumed prior knowledge of the codebase. - -### Mental model - -The library is built around one central idea: a **discrete conformal functional** E(x) whose critical points are the conformally equivalent metrics. Everything else is infrastructure for evaluating E, its gradient G = ∂E/∂x, and its Hessian H = ∂²E/∂x². +### Mentales Modell ``` -ConformalMesh — half-edge mesh (CGAL::Surface_mesh) - + property maps — per-vertex / per-edge data (λ, θ, α, DOF index, …) +ConformalMesh — Halfedge-Mesh (CGAL::Surface_mesh) + + Property-Maps — per-Vertex/Edge Daten (λ, θ, α, DOF-Index, …) -Maps struct — collects all property maps for one functional - theta_v[v] — target angle at vertex v (your input) - v_idx[v] — DOF index, or −1 if pinned - e_idx[e] — DOF index for edge DOFs (HyperIdeal only) +Maps-Struct — sammelt alle Property-Maps für ein Funktional + theta_v[v] — Zielwinkel bei Vertex v (Eingabe) + v_idx[v] — DOF-Index, oder −1 wenn gepinnt + e_idx[e] — DOF-Index für Kanten-DOFs (nur HyperIdeal) -x ∈ ℝⁿ — the DOF vector the solver optimises +x ∈ ℝⁿ — DOF-Vektor, den der Solver optimiert -evaluate_*(mesh, x, maps) → { energy, gradient, … } -newton_*(mesh, x0, maps) → { x*, iterations, converged, … } +evaluate_*(mesh, x, maps) → { Energie, Gradient, … } +newton_*(mesh, x0, maps) → { x*, Iterationen, converged, … } ``` -The mesh geometry (vertex positions) is only used to initialise the log edge-lengths λ°. From then on the solver works entirely in the `x`-space. +Die Mesh-Geometrie (Vertex-Positionen) dient nur zur Initialisierung der Log-Kantenlängen λ°. Danach arbeitet der Solver ausschließlich im x-Raum. -### Adding a new functional — step-by-step +### Neues Funktional hinzufügen -Copy `euclidean_functional.hpp` as a template (it is the simplest of the three). You need to provide: +1. **Maps-Struct**: `setup_my_maps(mesh)` mit Property-Maps für λ, θ_v, v_idx +2. **Energie + Gradient**: Schleife über `mesh.faces()`, akkumuliere in `grad[v_idx[v]]` +3. **Gradient-Check**: Finite-Differenzen-Verifikation (Vorlage in jedem `test_*_functional.cpp`) +4. **Newton-Solver**: `solve_linear_system(H, -G, &used_fallback)` direkt nutzen -**1. A `Maps` struct** that holds the property maps your functional needs: - -```cpp -// my_functional.hpp -#pragma once -#include "conformal_mesh.hpp" - -namespace conformallab { - -struct MyMaps { - // property maps attached to the mesh - ConformalMesh::Property_map lambda; // log edge-lengths - ConformalMesh::Property_map theta_v; // target angles - ConformalMesh::Property_map v_idx; // DOF indices - - // any extra parameters your functional needs - double my_parameter = 1.0; -}; - -inline MyMaps setup_my_maps(ConformalMesh& mesh) { … } -``` - -**2. An energy + gradient function:** - -```cpp -struct MyResult { - double energy; - std::vector gradient; -}; - -inline MyResult evaluate_my_functional( - ConformalMesh& mesh, - const std::vector& x, - const MyMaps& m, - bool compute_energy = true) -{ - MyResult res; - res.gradient.assign(x.size(), 0.0); - - for (auto f : mesh.faces()) { - // iterate halfedges around face - // compute your per-face contribution to E and G - // accumulate: res.gradient[m.v_idx[v]] += … - } - - // subtract target-angle term - for (auto v : mesh.vertices()) { - int iv = m.v_idx[v]; - if (iv < 0) continue; - res.gradient[iv] -= m.theta_v[v]; // G_v = actual - target - } - - return res; -} -``` - -**3. A gradient check** — before trusting your formula, verify it numerically. There is a ready-made helper in `hyper_ideal_functional.hpp` you can call directly, or write your own: - -```cpp -// Finite-difference gradient check for any functional -bool my_gradient_check(ConformalMesh& mesh, - const std::vector& x, - const MyMaps& m, - double eps = 1e-6, double tol = 1e-5) -{ - auto r0 = evaluate_my_functional(mesh, x, m, false); - const int n = static_cast(x.size()); - for (int i = 0; i < n; ++i) { - auto xp = x; xp[i] += eps; - auto xm = x; xm[i] -= eps; - double fd = (evaluate_my_functional(mesh, xp, m).energy - - evaluate_my_functional(mesh, xm, m).energy) / (2*eps); - if (std::abs(fd - r0.gradient[i]) > tol * (1 + std::abs(fd))) - return false; - } - return true; -} -``` - -Add a `TEST(MyFunctional, GradientCheck_Triangle)` in `tests/cgal/` and it will be picked up automatically by CTest. - -**4. Hook into the Newton solver.** Once your gradient and Hessian are correct, plug in `solve_linear_system` or write a thin wrapper in the style of `newton_euclidean`: - -```cpp -// Use a numerical Hessian first (safe starting point) -#include "newton_solver.hpp" -#include - -// Build H by FD of your gradient, then: -bool ok = false; -auto dx = detail::solve_with_fallback(H, -G, ok); -``` - -Or supply an analytical Hessian as a sparse matrix and pass it directly. - -### Where the key mathematical objects live - -| Object | File | What to look for | -|--------|------|-----------------| -| Corner angle formula (Euclidean) | `euclidean_geometry.hpp` | `euclidean_corner_angle()` — inputs are log half-edge lengths | -| Spherical angle formula | `spherical_geometry.hpp` | `spherical_corner_angle()` — uses spherical law of cosines | -| HyperIdeal angle (ζ₁₃/ζ₁₄/ζ₁₅) | `hyper_ideal_geometry.hpp` | `zeta13/14/15()`, `alpha_ij()` — the four vertex-type cases | -| Per-face energy term | `*_functional.hpp` | the inner loop over `mesh.faces()` | -| Gradient accumulation | `*_functional.hpp` | `grad[v_idx[v]] += …` after the face loop | -| Cotangent-Laplace structure | `euclidean_hessian.hpp` | `euclidean_hessian()` — shows the sparse-triplet pattern | -| Spherical Hessian derivation | `spherical_hessian.hpp` | comments give the ∂α/∂u formula step by step | -| Special functions | `clausen.hpp` | `Cl2()`, `lobachevsky()`, `imLi2()` — all take a `double` angle | - -### How to navigate the half-edge mesh +### Halfedge-Mesh navigieren ```cpp for (auto f : mesh.faces()) { - // The three halfedges of face f: auto h0 = mesh.halfedge(f); auto h1 = mesh.next(h0); auto h2 = mesh.next(h1); - // Vertices opposite to each halfedge (the vertex NOT on h): - Vertex_index v0 = mesh.target(h2); // opposite to edge h0-h1 - Vertex_index v1 = mesh.target(h0); // opposite to edge h1-h2 - Vertex_index v2 = mesh.target(h1); // opposite to edge h0-h2 (= h2 target) + Vertex_index v_opp = mesh.target(h2); // dem Halfedge h0 gegenüberliegend + int dof = maps.v_idx[v_opp]; // −1 = gepinnt - // Access DOF index (−1 = pinned): - int i0 = maps.v_idx[v0]; - - // The opposite halfedge (for the adjacent face, if not on boundary): - auto h_opp = mesh.opposite(h0); - bool is_boundary = mesh.is_border(h_opp); + bool is_boundary = mesh.is_border(mesh.opposite(h0)); } ``` -### Attaching new data to a mesh +### Neue Daten am Mesh befestigen ```cpp -// Add a per-vertex curvature field (survives mesh copy): auto [curv, created] = mesh.add_property_map("v:my_curv", 0.0); - -// Write and read: curv[v] = 1.234; -double k = curv[v]; - -// Pass it through your Maps struct so functions can access it. ``` -Property maps are reference-counted and cheap to copy. Give them unique string names to avoid collision. +### Schnell-Start-Checkliste -### Quick-start experiment checklist +1. `examples/example_layout.cpp` lesen — zeigt die vollständige Pipeline in ~120 Zeilen +2. `cmake -S code -B build -DWITH_CGAL=ON && cmake --build build --target example_layout` +3. Gradient-Check-Test in `tests/cgal/` hinzufügen (beliebigen `GradientCheck_*`-Block kopieren) +4. Verschiedene Zielwinkel ausprobieren: `maps.theta_v[v] = M_PI / 3` für alle Innen-Vertices. Die Gauss–Bonnet-Bedingung Σ(2π − Θ_v) = 2π·χ(M) muss erfüllt sein. +5. Konvergenz beobachten: `NewtonResult` enthält `iterations` und `grad_inf_norm` -1. **Read** `examples/example_layout.cpp` — it shows the full pipeline (load → setup → solve → layout → JSON/XML save → reload) in ~120 lines with comments at every step. -2. **Build** with `cmake -S code -B build -DWITH_CGAL=ON && cmake --build build --target example_layout` and run `./build/examples/example_layout`. -3. **Add a gradient check test** in `tests/cgal/` — copy any `TEST(…, GradientCheck_…)` block and swap out the functional. Run with `ctest -R your_test_name`. -4. **Try different target angles** — set `maps.theta_v[v] = M_PI / 3` for all interior vertices and see how the solver responds. The constraint `Σ(2π − Θ_v) = 2π·χ(M)` (Gauss–Bonnet) must hold for a solution to exist. -5. **Inspect convergence** — `NewtonResult` carries `iterations`, `grad_inf_norm`, and the full `x` at termination. Plot `||G(xₖ)||` per iteration to verify quadratic convergence near the solution. +### Weiterführende Literatur -### Porting the Java uniformization pipeline — a roadmap for contributors - -The Java ConformalLab has a complete global uniformization pipeline that conformallab++ does not yet have. This section explains the mathematics behind it and the precise C++ steps needed to port each piece. - -#### What "global uniformization" means mathematically - -Given a triangulated surface $M$ of genus $g$ and a convergerd conformal scale factor $u_v$, global uniformization produces: - -- $g = 0$: an embedding into the sphere $S^2$ or the Riemann sphere $\hat{\mathbb{C}}$, well-defined up to Möbius transformation. -- $g = 1$: a flat torus $\mathbb{C} / \Lambda$; the lattice $\Lambda = \mathbb{Z} + \tau\mathbb{Z}$ is the **period** (one complex number $\tau$ in the upper half-plane). -- $g \geq 2$: a hyperbolic surface $\mathbb{H} / \Gamma$; the **period matrix** $\Omega \in \mathbb{H}_g$ (a $g\times g$ symmetric complex matrix with positive-definite imaginary part) parametrizes the conformal class. - -The BFS unfolding in `layout.hpp` already computes *local* coordinates correctly. The missing piece is tracking what happens when the BFS crosses a **handle** — a non-contractible loop in $M$. - -#### Step 1 — Gauss–Bonnet consistency check - -Before anything else, add a pre-solve validation function. This is a single loop: - -```cpp -// gauss_bonnet.hpp (new file, ~30 lines) -#pragma once -#include "conformal_mesh.hpp" -#include "euclidean_functional.hpp" // or whichever Maps type -#include "constants.hpp" -#include -#include - -namespace conformallab { - -// Throws if Σ(2π − θ_v) ≠ 2π·χ(M) to within tol. -// Call before newton_euclidean / newton_spherical. -inline void check_gauss_bonnet( - const ConformalMesh& mesh, - const EuclideanMaps& maps, // or SphericalMaps / HyperIdealMaps - double tol = 1e-8) -{ - // Euler characteristic χ = V − E + F - int chi = static_cast(mesh.number_of_vertices()) - - static_cast(mesh.number_of_edges()) - + static_cast(mesh.number_of_faces()); - - double lhs = 0.0; - for (auto v : mesh.vertices()) - lhs += TWO_PI - maps.theta_v[v]; - - double rhs = TWO_PI * chi; - if (std::abs(lhs - rhs) > tol) - throw std::runtime_error( - "Gauss–Bonnet violated: Σ(2π−θ_v) = " + std::to_string(lhs) - + ", expected 2π·χ = " + std::to_string(rhs)); -} - -} // namespace conformallab -``` - -This is the easiest piece and the highest-value first step: it will immediately catch target-angle mistakes that currently cause silent Newton non-convergence. - -#### Step 2 — homological basis / fundamental polygon cut - -To turn a genus-$g$ surface into a disk, cut along a **standard homological basis**: $g$ pairs of loops $(a_1, b_1), \ldots, (a_g, b_g)$ with $a_i \cdot b_j = \delta_{ij}$. - -In Java this is computed from the dual graph via a **spanning tree + co-tree** decomposition: - -1. Compute a spanning tree $T$ of the primal graph (edges used in BFS). -2. The non-tree edges form the co-tree; they correspond to independent homology classes. -3. For genus $g$, pick $2g$ non-tree edges that form a standard symplectic basis (i.e. they pair up with intersection number 1). -4. Cut the mesh along these $2g$ loops to obtain a $4g$-gon. - -In C++ the data structure to add is a **cut graph** stored as a set of `Edge_index` values that are marked as "boundary" for the BFS: - -```cpp -// cut_graph.hpp (new file) -#pragma once -#include "conformal_mesh.hpp" -#include -#include - -namespace conformallab { - -struct CutGraph { - std::unordered_set cut_edges; // edges treated as boundary - int genus; -}; - -// Compute a cut graph for mesh using spanning tree + co-tree. -// Result: a set of 2g edges whose removal makes M simply connected. -CutGraph compute_cut_graph(ConformalMesh& mesh); - -} // namespace conformallab -``` - -The `euclidean_layout` BFS then needs one extra line: - -```cpp -// Inside the BFS enqueue lambda — treat cut edges as boundary: -if (!mesh.is_border(h_opp) && !cut.cut_edges.count(mesh.edge(h).idx())) - q.push(h_opp); -``` - -#### Step 3 — holonomy tracking - -Once the BFS has a cut, every time it would cross a cut edge it instead records the **holonomy element** — the transformation that maps the coordinate on one side of the cut to the coordinate on the other side. - -For the **Euclidean case** the holonomy group is a subgroup of $\text{Isom}(\mathbb{R}^2)$ (rigid motions). Each holonomy element is a $2\times 2$ rotation + translation: - -```cpp -struct EuclideanHolonomy { - Eigen::Matrix2d R; // rotation - Eigen::Vector2d t; // translation - // apply: p ↦ R·p + t -}; -``` - -For the **hyperbolic case** the holonomy group is a subgroup of $\text{PSL}(2,\mathbb{R})$ acting on the upper half-plane (or equivalently $\text{PSU}(1,1)$ acting on the Poincaré disk). Each element is a $2\times 2$ real matrix with determinant 1: - -```cpp -struct MobiusElement { - Eigen::Matrix2d M; // [[a, b], [c, d]], det = 1 - // apply to z = (x,y): z ↦ (M[0,0]·z + M[0,1]) / (M[1,0]·z + M[1,1]) - // (complex arithmetic) -}; -``` - -The BFS accumulates one holonomy element per cut edge pair $(a_i, b_i)$. After BFS completes, the holonomy data is a $2g$-tuple of group elements. - -#### Step 4 — period matrix extraction - -From the holonomy elements, the period data is extracted as follows: - -**Genus 1 (torus):** The two holonomy elements $h_{a_1}, h_{b_1}$ are translations: $h_{a_1}(z) = z + \omega_1$, $h_{b_1}(z) = z + \omega_2$ with $\omega_1, \omega_2 \in \mathbb{C}$. The period ratio is $\tau = \omega_2 / \omega_1 \in \mathbb{H}$ (upper half-plane). Reduce $\tau$ to the fundamental domain of $\text{SL}(2,\mathbb{Z})$ with the standard Euclidean algorithm on $\text{SL}(2,\mathbb{Z})$. - -**Genus $g \geq 2$:** The $2g$ holonomy elements are generators of a Fuchsian group $\Gamma \subset \text{PSL}(2,\mathbb{R})$. The period matrix $\Omega_{ij} = \int_{b_j} \omega_i$ is computed by integrating the $g$ holomorphic differentials $\omega_1, \ldots, \omega_g$ around the $b$-cycles. On a discrete surface this reduces to a linear system involving the holonomy matrices. See Bobenko–Springborn (2004) §6 for the discrete version. - -The `discrete_elliptic_utility.hpp` in this codebase already contains a modular normalisation helper for $\tau$ — it is not yet wired up to any layout pipeline, but is precisely what Step 4 needs for the genus-1 case. - -#### Step 5 — layout normalisation - -After BFS + holonomy extraction, apply a canonical Möbius transformation to bring the layout into a standard position: - -- **Euclidean genus 0:** scale + rotate so that $\omega_1 = 1$ (standard horizontal period). -- **Hyperbolic:** apply the unique $\text{PSU}(1,1)$ element that maps the centroid of all vertices to the origin of the Poincaré disk. -- **Spherical genus 0:** apply the unique Möbius transformation that maps the three face-centres of the root face to the standard position on $S^2$. - -#### Recommended order of implementation - -| Priority | Piece | Estimated effort | C++ hook | -|----------|-------|-----------------|----------| -| 1 | Gauss–Bonnet check | ~30 lines, 1 day | new `gauss_bonnet.hpp` | -| 2 | Exact hyperbolic trilateration | ~50 lines, 1 day | replace `trilaterate_hyp` in `layout.hpp` | -| 3 | Cut-graph computation | ~200 lines, 3–5 days | new `cut_graph.hpp` | -| 4 | Holonomy tracking in BFS | ~80 lines, 2 days | extend `euclidean_layout` / `hyper_ideal_layout` | -| 5 | Period matrix (genus 1) | ~100 lines, 3 days | wire up `discrete_elliptic_utility.hpp` | -| 6 | Period matrix (genus ≥ 2) | research-level, weeks | new `period_matrix.hpp` | - -Steps 1–4 together constitute a **practically complete** uniformization for genus-0 and genus-1 surfaces, which covers the vast majority of mesh-processing use cases. - -### Recommended reading -| Springborn — *Ideal Hyperbolic Polyhedra and Discrete Uniformization* (2020) | HyperIdeal functional; the ζ₁₃/ζ₁₄/ζ₁₅ functions in `hyper_ideal_geometry.hpp` | -| Pinkall, Polthier — *Computing Discrete Minimal Surfaces* (1993) | Cotangent-Laplace Hessian in `euclidean_hessian.hpp` | -| Luo — *Combinatorial Yamabe Flow on Surfaces* (2004) | Inversive-distance functional (not yet ported — good first contribution) | -| Bobenko, Springborn — *Variational Principles for Circle Patterns* (2004) | Background for the angle-sum variational framework used throughout | +| Quelle | Bezug | +|--------|-------| +| Springborn — *Ideal Hyperbolic Polyhedra and Discrete Uniformization* (2020) | HyperIdeal-Funktional; ζ₁₃/₁₄/₁₅ in `hyper_ideal_geometry.hpp` | +| Pinkall, Polthier — *Computing Discrete Minimal Surfaces* (1993) | Kotangenten-Laplace in `euclidean_hessian.hpp` | +| Luo — *Combinatorial Yamabe Flow on Surfaces* (2004) | Inversive-Distance-Funktional (noch nicht portiert) | +| Bobenko, Springborn — *Variational Principles for Circle Patterns* (2004) | Hintergrund für das Winkelsum-Variationsprinzip | +| Erickson, Whittlesey — *Greedy Optimal Homotopy and Homology Generators* (SODA 2005) | Tree-Cotree-Algorithmus in `cut_graph.hpp` | --- -## Key design decisions +## Schlüssel-Designentscheidungen -**CGAL as CoHDS replacement.** `CGAL::Surface_mesh` replaces the Java `CoHDS` half-edge data structure. Vertex/edge/face/halfedge descriptors are typed integers — no raw handles, no RTTI. +**CGAL als CoHDS-Ersatz.** `CGAL::Surface_mesh` ersetzt die Java-`CoHDS`-Halfedge-Datenstruktur. Vertex/Edge/Face/Halfedge-Deskriptoren sind typisierte Integer. -**Property maps.** `mesh.add_property_map("v:lambda", 0.0)` replaces the Java adapter/decorator pattern. Multiple maps attach to one mesh without subclassing. +**Property-Maps.** `mesh.add_property_map("v:lambda", 0.0)` ersetzt das Java-Adapter/Decorator-Pattern. -**DOF vector convention.** All functionals use `x` indexed by `v_idx[v]` / `e_idx[e]` (−1 = pinned). This matches the Java `FunctionalTest` gradient-check convention and is uniform across all three geometries. +**DOF-Vektor-Konvention.** Alle Funktionale verwenden `x` indiziert durch `v_idx[v]` / `e_idx[e]` (−1 = gepinnt). Einheitlich über alle drei Geometrien. -**HyperIdeal Hessian via FD.** The analytical Hessian through `ζ13/14/15 → lij → β/α` is deferred to Phase 6. A symmetric FD Hessian `H[i,j] = (G(x+ε·eⱼ)[i] − G(x−ε·eⱼ)[i]) / (2ε)` is O(ε²) accurate, PSD by strict convexity, and sufficient for Newton on < 500 DOFs. +**Priority-BFS.** Faces werden in aufsteigender BFS-Tiefe verarbeitet (Min-Heap über `depth = max(depth[v_src], depth[v_tgt]) + 1`). Dies minimiert die Akkumulation von Trilaterations-Fehlern — je weiter eine Fläche vom Ursprung entfernt ist, desto später wird sie platziert. -**Spherical Hessian sign.** The spherical energy is **concave** (not convex) — the Hessian **H** is NSD at equilibrium. Newton solves `(−H)·Δx = G`, so the sign flip is handled transparently inside `newton_spherical`. +**halfedge_uv-Semantik.** `halfedge_uv[h.idx()]` ist das UV von `source(h)` aus Sicht von `face(h)`. An Naht-Halfedges tragen die beiden gegenüberliegenden Halfedges unterschiedliche UV-Werte — so erhält jede Fläche ihre eigene Kopie eines Naht-Vertex für GPU-Texturatlas ohne Vertex-Duplizierung. -**Natural theta trick.** Tests set `theta_v = Σα_v(x=x_base)` to make `x_base` the known equilibrium, avoiding any need to manufacture reference solutions. For HyperIdeal `x_base = (b=1.0, a=0.5)` is used (x=0 is degenerate in log-space). +**HyperIdeal Hessian via FD.** Der analytische Hessian durch `ζ13/14/15 → lij → β/α` ist auf Phase 8 verschoben. Ein symmetrischer FD-Hessian ist O(ε²)-genau, PSD durch strikte Konvexität und für < 500 DOFs ausreichend. + +**Sphärischer Hessian Vorzeichen.** Die sphärische Energie ist **konkav** (Hessian NSD). Newton löst `(−H)·Δx = G`, das Vorzeichen wird transparent in `newton_spherical` behandelt. --- ## CI -Tests run automatically on push to `main`, `dev`, and `claude/**` branches via a self-hosted Gitea Actions runner (`eulernest`, ARM64). The CI image contains cmake, g++, git, and Node.js. **Only `conformallab_tests` runs in CI** (no Boost/CGAL dependency there). +Tests laufen automatisch bei Push auf `main`, `dev` und `claude/**`-Branches via selbst-gehostetem Gitea-Actions-Runner (`eulernest`, ARM64). **Nur `conformallab_tests` läuft in CI** (kein Boost/CGAL dort). ```bash -# Rebuild and push the CI image when the Dockerfile changes +# CI-Image neu bauen und pushen docker buildx build \ --platform linux/arm64 \ -f .gitea/docker/Dockerfile.ci-cpp \ @@ -903,66 +528,45 @@ docker buildx build \ ## Roadmap ``` -Phase 1 Clausen / Lobachevsky / ImLi₂ ✅ abgeschlossen +Phase 1 Clausen / Lobachevsky / ImLi₂ ✅ abgeschlossen +Phase 2 Hyper-ideal Geometrie (ζ, lᵢⱼ, αᵢⱼ, σᵢ) ✅ abgeschlossen +Phase 3 CGAL Infrastruktur + alle drei Funktionale + + analytische Hessians (Eucl. + Sphär.) ✅ abgeschlossen +Phase 4 Newton-Solver + SparseQR-Fallback + Mesh-I/O + + Beispiel-Programme ✅ abgeschlossen +Phase 5 BFS-Layout + CLI + JSON/XML-Serialisierung ✅ abgeschlossen + → 95 Tests -Phase 2 Hyper-ideal Geometrie (ζ, lᵢⱼ, αᵢⱼ, σᵢ) ✅ abgeschlossen +Phase 6 Layout-Erweiterung (Java-Parität I) ✅ abgeschlossen + → gauss_bonnet.hpp: χ, Genus, Σ(2π-Θ_v) Check + Enforce + → cut_graph.hpp: Tree-Cotree (Erickson–Whittlesey 2005), 2g Schnitt-Kanten + → Exakte hyperbolische Trilateration (Möbius + Kosinussatz) + → normalise_{euclidean,hyperbolic,spherical} + → CutGraph* + HolonomyData* in allen Layout-Funktionen + → 121 Tests (+26 Phase-6-Tests) -Phase 3a CGAL Surface_mesh Infrastruktur ✅ abgeschlossen -Phase 3b HyperIdealFunctional ✅ abgeschlossen -Phase 3c SphericalFunctional ✅ abgeschlossen -Phase 3d EuclideanCyclicFunctional ✅ abgeschlossen -Phase 3e Gauge-Fix für SphericalFunctional ✅ abgeschlossen -Phase 3f Analytische Hessians (Eucl. + Sphär.) ✅ abgeschlossen -Phase 3g PI-Konstante konsolidieren ✅ abgeschlossen +Phase 7 Layout-Erweiterung (Java-Parität II) ✅ abgeschlossen + → layout.hpp: Priority-BFS (Min-Heap, BFS-Tiefe) + → layout.hpp: MobiusMap — T(z)=(az+b)/(cz+d), from_three, compose + → layout.hpp: halfedge_uv — Naht-bewusstes UV pro Halfedge + → layout.hpp: Möbius-Holonomie (SU(1,1) pro Schnitt-Kante, hyperbolisch) + → layout.hpp: best_root_face (größte 3D-Fläche, 1.5× Interior-Bonus) + → layout.hpp: Flächen-gewichtete iterative Möbius-Zentrierung (Fréchet-Mittel) + → period_matrix.hpp: τ = ω₂/ω₁ ∈ ℍ, SL(2,ℤ)-Reduktion + → fundamental_domain.hpp: CCW-Parallelogramm, Kanten-Identifikationen, + Kachelkopien; 4g-Polygon-Randlauf → TODO Phase 8 + → 158 Tests (+37 Phase-7-Tests) -Phase 4a Newton-Solver (alle drei Geometrien) ✅ abgeschlossen - → newton_euclidean / newton_spherical / newton_hyper_ideal - → detail::solve_with_fallback → public solve_linear_system - → Backtracking-Line-Search - → hyper_ideal_hessian.hpp (numerischer FD-Hessian) - -Phase 4b CGAL::IO Mesh-Import/Export ✅ abgeschlossen - → mesh_io.hpp: read/write/load/save - → Format-Erkennung aus Dateiendung (OFF, OBJ, PLY) - -Phase 4c End-to-End-Pipeline Tests ✅ abgeschlossen - → test_pipeline.cpp: 5 Tests (alle 3 Geometrien, I/O, full loop) - -Phase 4d SparseQR-Fallback + Beispiel-Programme ✅ abgeschlossen - → solve_linear_system als öffentliche API mit fallback_used-Flag - → 3 dedizierte SparseQR-Tests (full-rank, singular, closed mesh) - → examples/example_euclidean.cpp (headless) - → examples/example_hyper_ideal.cpp (headless) - → examples/example_viewer.cpp (interaktiver Viewer, WITH_VIEWER) - -Phase 5 Layout + CLI + Serialisierung ✅ abgeschlossen - → layout.hpp: BFS-Einbettung in ℝ² (Euclidean/HyperIdeal) und S² (Spherical) - → serialization.hpp: JSON (nlohmann/json) + XML (hand-written) save/load - → conformallab_core CLI: -i/-o/-g/-j/-x/-s/-v Flags, alle drei Geometrien - → example_layout.cpp: Solve → Layout → OFF/JSON/XML + Round-Trip-Check - → test_layout.cpp: 8 Tests (Eucl./Sphär./HyperIdeal + JSON/XML) - → 95 Tests gesamt (2 skipped) - -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) +Phase 8 (geplant) → Analytischer HyperIdeal-Hessian (direkte Ableitung durch ζ-Kette) - → Holonomie-Matrizen für Tori (Periodenmatrix τ = ω₂/ω₁ ∈ ℍ) - → Vollständige globale Parameterisierung geschlossener Flächen + → 4g-Polygon-Randlauf für Genus g > 1 (Grenzwert-Walk auf geschnittenem Mesh) + → Siegel-Periodenmatrix Ω (g×g, g ≥ 2) via Integration holomorpher Differentiale + → Vollständige globale Uniformisierung geschlossener Flächen beliebigen Genus → Inversive-Distance-Funktional (Luo 2004) ``` --- -## License +## Lizenz -conformallab++ is released under the MIT License (see [LICENSE](LICENSE)). +conformallab++ steht unter der MIT-Lizenz (siehe [LICENSE](LICENSE)).