Merge pull request 'v0.7.0 — Phase 7 complete: 173 Tests, Onboarding-Doku, geometry-central' (#3) from dev into main
All checks were successful
C++ Tests / test-fast (push) Successful in 2m7s
Mirror to Codeberg / mirror (push) Successful in 33s
C++ Tests / test-cgal (push) Has been skipped

Reviewed-on: #3
This commit is contained in:
2026-05-18 18:39:58 +00:00
45 changed files with 40883 additions and 899 deletions

View File

@@ -12,7 +12,7 @@ on:
# ─────────────────────────────────────────────────────────────────────────────
# Job 1 — test-fast
# Pure-math tests (Clausen, ImLi₂, Hyper-ideal Geometrie).
# Kein CGAL, kein Boost. Läuft auf ALLEN Branches in < 1 s.
# Kein CGAL, kein Boost. Nur Eigen + GTest. Läuft auf ALLEN Branches.
# ─────────────────────────────────────────────────────────────────────────────
jobs:
test-fast:
@@ -27,7 +27,7 @@ jobs:
run: cmake -S code -B build -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --target conformallab_tests -j$(nproc)
run: nice -n 19 cmake --build build --target conformallab_tests -j$(nproc)
- name: Run tests
run: >
@@ -49,26 +49,21 @@ jobs:
# ─────────────────────────────────────────────────────────────────────────────
# Job 2 — test-cgal
# Vollständige CGAL-Test-Suite (Phase 37, 158 Tests).
# Läuft nur auf main, dev und Pull Requests nicht auf Feature-Branches.
# Läuft NUR bei Pull Requests (nicht bei direkten Pushes auf dev/main).
# Startet erst nach erfolgreichem test-fast.
#
# Verwendet -DWITH_CGAL_TESTS=ON (nicht -DWITH_CGAL=ON), damit kein
# Viewer/GLFW gebaut wird — der CI-Container hat kein wayland-scanner.
#
# Boost-Install-Schritt: solange das Docker-Image noch kein libboost-dev
# enthält, wird es hier zur Laufzeit nachinstalliert (~15 s).
# Nach dem nächsten Image-Rebuild (Dockerfile bereits aktualisiert) wird
# dieser Schritt zum No-Op.
# Boost (libboost-dev) ist seit Image-Rebuild bereits im Container.
# ─────────────────────────────────────────────────────────────────────────────
test-cgal:
needs: test-fast
if: >
github.ref == 'refs/heads/main' ||
github.ref == 'refs/heads/dev' ||
github.event_name == 'pull_request'
if: github.event_name == 'pull_request'
runs-on: eulernest
container:
image: git.eulernest.eu/conformallab/ci-cpp:latest
options: "--memory=1400m --memory-swap=1400m"
steps:
- uses: actions/checkout@v4
@@ -77,7 +72,7 @@ jobs:
run: cmake -S code -B build -DWITH_CGAL_TESTS=ON -DCMAKE_BUILD_TYPE=Release
- name: Build CGAL-Tests
run: cmake --build build --target conformallab_cgal_tests -j$(nproc)
run: nice -n 19 cmake --build build --target conformallab_cgal_tests -j2
- name: Run CGAL-Tests
run: >

29
.gitignore vendored Normal file
View File

@@ -0,0 +1,29 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Build directories
build/
build-*/
build_*/
code/build*/
# CMake
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
CTestTestfile.cmake
# Test output
*.xml
Testing/
# IDE
.idea/
.vscode/
*.user
*.suo
# Claude Code worktrees
.claude/

68
CITATION.cff Normal file
View File

@@ -0,0 +1,68 @@
cff-version: 1.2.0
message: "If you use this software in your research, please cite it as below."
authors:
- family-names: Moussa
given-names: Tarik
email: Tarik.moussa95@gmail.com
title: "conformallab++"
version: 0.7.0
date-released: 2026-05-18
url: "https://codeberg.org/TMoussa/ConformalLabpp"
repository-code: "https://codeberg.org/TMoussa/ConformalLabpp"
license: MIT
abstract: >
conformallab++ is a C++17 implementation of discrete conformal maps on
triangulated surfaces, covering Euclidean, Spherical, and Hyper-ideal geometry
modes. It provides a Newton solver for discrete conformal equivalence (DCE),
tree-cotree cut graphs, Möbius holonomy, period matrix computation with
SL(2,) reduction, and fundamental domain construction. The long-term goal is
a CGAL package for discrete conformal geometry.
keywords:
- discrete conformal geometry
- conformal maps
- surface parameterization
- period matrix
- Teichmüller theory
- CGAL
- C++
references:
- type: thesis
authors:
- family-names: Sechelmann
given-names: Stefan
title: >
Variational Methods for Discrete Surface Parameterization:
Applications and Implementation
institution:
name: Technische Universität Berlin
year: 2016
doi: 10.14279/depositonce-5415
notes: "Primary algorithmic source for this implementation"
- type: article
authors:
- family-names: Springborn
given-names: Boris
title: "Ideal Hyperbolic Polyhedra and Discrete Uniformization"
journal: "Discrete & Computational Geometry"
year: 2020
doi: 10.1007/s00454-019-00132-8
notes: "Mathematical basis for the HyperIdeal geometry mode"
- type: article
authors:
- family-names: Bobenko
given-names: Alexander I.
- family-names: Springborn
given-names: Boris A.
title: >
Variational Principles for Circle Patterns and Koebe's Theorem
journal: "Transactions of the American Mathematical Society"
year: 2004
doi: 10.1090/S0002-9947-03-03239-2
notes: "Variational framework underlying all three geometry modes"

269
CLAUDE.md Normal file
View File

@@ -0,0 +1,269 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project purpose and long-term goal
conformallab++ is a C++17 reimplementation of [ConformalLab](https://github.com/varylab/conformallab) — Stefan Sechelmann's Java research library for discrete conformal geometry (TU Berlin, ~850 commits, v1.0.0 2018). The algorithmic foundation is his dissertation:
> Stefan Sechelmann — *Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, TU Berlin 2016.
> DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) · CC BY-SA 4.0
**The long-term goal is a CGAL package** — a submission to the CGAL library that brings discrete conformal maps (hyper-ideal, spherical, Euclidean) to the CGAL ecosystem using `CGAL::Surface_mesh` as the underlying halfedge data structure, with a traits-class design compatible with arbitrary CGAL-conforming mesh types.
The project has three distinct phases:
- **Phase 17 (done):** Direct port of the Java library algorithms to C++
- **Phase 89 (planned):** CGAL package infrastructure + remaining Java features not yet ported (inversive-distance functional, analytic HyperIdeal Hessian, genus-g > 1 fundamental domain)
- **Phase 10+ (research):** New mathematics beyond the Java original — holomorphic differentials, Siegel period matrix Ω ∈ H_g, full uniformization for genus g ≥ 2
## Language
**All code, comments, documentation, commit messages, and test descriptions must be in English.** The project is intended for international collaboration and CGAL submission. Existing German-language comments in older files should be replaced with English when editing those files.
## Build commands
All source lives under `code/`. Three build modes:
```bash
# Mode 1 — fast tests, no CGAL, no Boost, no display (CI default)
cmake -S code -B build
cmake --build build --target conformallab_tests -j$(nproc)
ctest --test-dir build --output-on-failure
# Mode 2 — CGAL tests, headless (CI full, requires Boost headers only)
# macOS: brew install boost Linux: apt install libboost-dev
cmake -S code -B build -DWITH_CGAL_TESTS=ON
cmake --build build --target conformallab_cgal_tests -j$(nproc)
ctest --test-dir build -R "^cgal\." --output-on-failure
# Mode 3 — full local build: CLI app + viewer + examples (requires Wayland/X11)
cmake -S code -B build -DWITH_CGAL=ON
cmake --build build -j$(nproc)
```
`-DWITH_CGAL=ON` automatically enables `-DWITH_VIEWER=ON`, which pulls in GLFW and requires `wayland-scanner`. Never use this in headless CI.
### Running a single test
```bash
# By GTest suite/test name
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver*"
./build/conformallab_tests --gtest_filter="Clausen*"
# By CTest regex (prefix "cgal." for all CGAL tests)
ctest --test-dir build -R "cgal.NewtonSolver" --output-on-failure
```
### Rebuilding the CI Docker image
```bash
docker buildx build \
--platform linux/arm64 \
-f .gitea/docker/Dockerfile.ci-cpp \
-t git.eulernest.eu/conformallab/ci-cpp:latest \
--push \
.gitea/docker/
```
## Architecture
### Everything is header-only
All algorithms live in `code/include/*.hpp`. There is no compiled library. The three CMake targets (`conformallab_tests`, `conformallab_cgal_tests`, `conformallab_core`) compile headers directly from their `.cpp` entry points. To add a new algorithm: create a `.hpp` in `code/include/`, add a test in `code/tests/cgal/`, and register the test file in `code/tests/cgal/CMakeLists.txt`.
### Central type: `ConformalMesh`
`conformal_mesh.hpp` defines the core type:
```cpp
using ConformalMesh = CGAL::Surface_mesh<Point3>; // CGAL::Simple_cartesian<double>
```
This replaces the Java `CoHDS` (half-edge data structure) and its intrusive `CoVertex`/`CoEdge`/`CoFace` types. Data is attached via named CGAL property maps instead of intrusive fields:
| Property map name | Type | Meaning |
|---|---|---|
| `"v:lambda"` | `double` per vertex | log scale factor (conformal variable uᵢ) |
| `"v:theta"` | `double` per vertex | target cone angle Θᵥ |
| `"v:idx"` | `int` per vertex | solver DOF index; `-1` = pinned/boundary |
| `"e:alpha"` | `double` per edge | intersection angle αᵢⱼ (hyperbolic only) |
| `"f:type"` | `int` per face | geometry type (0=Euclidean, 1=Hyperbolic, 2=Spherical) |
`CGAL_DISABLE_GMP` and `CGAL_DISABLE_MPFR` are defined for all CGAL targets — the library deliberately uses `Simple_cartesian<double>` (floating-point, no exact arithmetic) because conformal geometry does not require exact predicates.
### The three geometry modes
Each mode has its own Maps struct that bundles all property maps, plus functional, Hessian, and Newton function:
| Mode | Space | Maps struct | Key headers | Newton function |
|---|---|---|---|---|
| Euclidean | ℝ² | `EuclideanMaps` | `euclidean_functional.hpp`, `euclidean_hessian.hpp` | `newton_euclidean()` |
| Spherical | S² | `SphericalMaps` | `spherical_functional.hpp`, `spherical_hessian.hpp` | `newton_spherical()` |
| Hyper-ideal | H² (Poincaré disk) | `HyperIdealMaps` | `hyper_ideal_functional.hpp`, `hyper_ideal_hessian.hpp` | `newton_hyper_ideal()` |
HyperIdeal also has edge DOFs (`e_idx[e]`); Euclidean and Spherical are vertex-DOF only. For HyperIdeal: `assign_all_dof_indices(mesh, maps)` assigns all vertex and edge DOFs automatically. For Euclidean/Spherical: pin one vertex manually (`maps.v_idx[first_vertex] = -1`) then assign sequential indices.
### The full pipeline
```
load_mesh() → ConformalMesh (OFF/OBJ/PLY)
setup_*_maps(mesh) → *Maps (property maps created, all zero)
compute_*_lambda0_from_mesh(mesh, m) → λ° initialised from 3-D edge lengths
DOF assignment → v_idx[v] set; -1 = pinned
check_gauss_bonnet(mesh, maps) → throws if Σ(2πΘᵥ) ≠ 2π·χ(M)
enforce_gauss_bonnet(mesh, maps) → redistributes angle defect uniformly
newton_*(mesh, x0, maps) → NewtonResult{x*, iterations, converged}
compute_cut_graph(mesh) → CutGraph (2g seam edges, tree-cotree)
*_layout(mesh, x*, maps, &cg, &hol) → Layout2D/3D + HolonomyData
normalise_*(layout) → canonical position (PCA / Möbius / Rodrigues)
compute_period_matrix(hol) → PeriodData{τ∈ℍ} (genus 1 flat torus)
compute_fundamental_domain(hol) → FundamentalDomain{vertices, generators}
tiling_neighbourhood(layout, hol) → vector of translated layout copies
save_result_json/xml() → serialised result
```
After `compute_*_lambda0_from_mesh()` the original vertex positions are no longer used — all subsequent computation is in log-length/scale-factor space.
### Newton solver (`newton_solver.hpp`)
The gradient sign convention differs between modes:
- **Euclidean/HyperIdeal:** `G_v = actual_angle_sum Θ_v`, H is PSD → `SimplicialLDLT(H)`
- **Spherical:** `G_v = Θ_v actual_angle_sum`, H is NSD → `SimplicialLDLT(H)`
When `SimplicialLDLT` fails (rank-deficient H — gauge mode on closed mesh without pinned vertex), the solver automatically retries with `Eigen::SparseQR` to find the minimum-norm step orthogonal to the null space. Public API: `solve_linear_system(H, rhs, &used_fallback)`.
The HyperIdeal Hessian is currently a **symmetric finite-difference approximation** (O(ε²), costs n extra gradient evaluations per Newton step). The analytic Hessian via the chain `(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ/βᵢ` is deferred to Phase 9b.
### Layout and holonomy (`layout.hpp`)
BFS-trilateration with a **priority min-heap on BFS depth** (`depth = max(depth[src], depth[tgt]) + 1`). Root face = largest 3-D area face. This minimises trilateration error accumulation compared to simple BFS.
Key output fields:
- `layout.uv[v.idx()]` — primary UV (first/shallowest BFS visit per vertex)
- `layout.halfedge_uv[h.idx()]` — UV of `source(h)` as seen from `face(h)`; at seam halfedges the two opposite halfedges carry *different* UV values, enabling proper GPU texture atlasing without vertex duplication
- `hol.translations[i]` — lattice generator ωᵢ ∈ (Euclidean/spherical)
- `hol.mobius_maps[i]` — Möbius isometry Tᵢ ∈ SU(1,1) (hyperbolic, Poincaré disk)
`MobiusMap` is defined in `layout.hpp`: T(z) = (az+b)/(cz+d). Key methods: `from_three()` (fit to 3 point correspondences via 3×3 complex linear system), `compose()`, `inverse()`, `apply(Vector2d)`.
### Key mathematical reference for each header
| Header | Java original | Key reference |
|---|---|---|
| `hyper_ideal_geometry.hpp` | `HyperIdealGeometry.java` | Springborn (2020) — ζ₁₃/ζ₁₄/ζ₁₅ functions |
| `euclidean_hessian.hpp` | `EuclideanHessian.java` | Pinkall & Polthier (1993) — cotangent Laplacian |
| `spherical_hessian.hpp` | `SphericalHessian.java` | ∂α/∂u from spherical law of cosines |
| `cut_graph.hpp` | `CuttingUtility.java` | Erickson & Whittlesey (SODA 2005) — tree-cotree |
| `period_matrix.hpp` | `PeriodMatrixUtility.java` | Sechelmann (2016) §4 — SL(2,) reduction |
| `gauss_bonnet.hpp` | (distributed across Java) | GaussBonnet: Σ(2πΘᵥ) = 2π·χ(M) |
### Java features not yet ported (Phase 9)
The Java library under `de.varylab.discreteconformal` contains these items not yet in C++:
| Java class | Planned C++ header | Phase |
|---|---|---|
| `InversiveDistanceFunctional` | `inversive_distance_functional.hpp` | 9a |
| Analytic HyperIdeal Hessian | `hyper_ideal_hessian.hpp` (replace FD) | 9b |
| 4g-polygon boundary walk in `FundamentalDomainUtility` | `fundamental_domain.hpp` (extend) | 9c |
| `DiscreteHarmonicFormUtility` | Phase 10a prerequisite | 10 |
| `DiscreteHolomorphicFormUtility` | Phase 10a | 10 |
| `HomologyUtility`, `CanonicalBasisUtility` | Phase 10 | 10 |
When porting a Java class, locate the original in `de.varylab.discreteconformal.*` at [github.com/varylab/conformallab](https://github.com/varylab/conformallab) and use it as the reference implementation.
## Test design patterns
### "Natural theta" — constructing a known equilibrium at x* = 0
```cpp
// Evaluate gradient at x=0; set target angles = actual angle sums → x*=0 by definition
std::vector<double> x0(n_dofs, 0.0);
auto G0 = euclidean_gradient(mesh, x0, maps);
for (auto v : mesh.vertices())
if (maps.v_idx[v] >= 0)
maps.theta_v[v] -= G0[maps.v_idx[v]]; // shift so G(x=0) = 0
```
This is used in virtually every Newton convergence test — it avoids hardcoding specific angle values.
### Gradient check pattern
```cpp
// Copy from any test_*_functional.cpp — GradientCheck_* test suite
double eps = 1e-5;
for (int i = 0; i < n; ++i) {
xp[i] += eps; auto Gp = euclidean_gradient(mesh, xp, maps);
xm[i] -= eps; auto Gm = euclidean_gradient(mesh, xm, maps);
double fd = (energy(xp) - energy(xm)) / (2*eps);
EXPECT_NEAR(G[i], fd, 1e-7);
xp[i] = xm[i] = x0[i];
}
```
All new functionals must have a gradient-check test before being considered complete.
### Halfedge traversal
```cpp
for (auto f : mesh.faces()) {
auto h0 = mesh.halfedge(f); // canonical halfedge of face
auto h1 = mesh.next(h0);
auto h2 = mesh.next(h1);
Vertex_index v1 = mesh.source(h0); // = mesh.target(h2)
Vertex_index v2 = mesh.source(h1);
Vertex_index v3 = mesh.source(h2);
// Angle at v3 is opposite to h0 (edge v1v2)
// h_alpha[h0] = α₃, h_alpha[h1] = α₁, h_alpha[h2] = α₂
bool is_boundary = mesh.is_border(mesh.opposite(h0));
}
```
### Attaching custom data to the mesh
```cpp
auto [my_map, created] = mesh.add_property_map<Vertex_index, double>("v:my_data", 0.0);
my_map[v] = 3.14;
```
## CI pipeline
Two jobs in `.gitea/workflows/cpp-tests.yml`:
| Job | CMake flags | Deps | Triggers on |
|---|---|---|---|
| `test-fast` | *(none)* | Eigen + GTest only | all branches |
| `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | `main`, `dev`, PRs only |
Runner: `eulernest` — self-hosted Raspberry Pi, ARM64, Ubuntu 22.04. Docker image: `git.eulernest.eu/conformallab/ci-cpp:latest`. `test-cgal` needs `test-fast` to pass first (`needs: test-fast`).
Expected results: **36 non-CGAL tests pass**, **173 CGAL tests pass, 1 skipped** (intentional `GTEST_SKIP` stub for analytic HyperIdeal Hessian — deferred to Phase 9b).
## Key documentation for mathematical context
When working on math-heavy tasks, read these before reasoning from scratch:
| Question | Document |
|---|---|
| What is the mathematical problem this library solves? | `doc/math/discrete-conformal-theory.md` |
| What are the three geometry modes and how do they differ? | `doc/math/geometry-modes.md` |
| How does conformallab++ relate to geometry-central (CMU)? | `doc/architecture/geometry-central-comparison.md` |
| What analytic results can be used to validate correctness? | `doc/math/validation.md` |
| Which Java classes are ported, which are planned? | `doc/roadmap/java-parity.md` |
| What does each processing function require/provide? | `doc/api/contracts.md` |
**geometry-central** (Keenan Crane, CMU) implements the same discrete conformal
equivalence problem (Gillespie, Springborn & Crane, SIGGRAPH 2021) but uses
Ptolemaic flips on intrinsic triangulations instead of Newton on the original mesh.
It has no period matrix, holonomy, or spherical geometry mode.
The shared mathematical core (Springborn 2020) means cross-validation is meaningful.
See `doc/architecture/geometry-central-comparison.md` for the full comparison.
## Known quirks
- **`test-fast` also runs stubs**: `conformallab_tests` (non-CGAL) contains `GTEST_SKIP`-based stubs for functionals that need CGAL. This is intentional — those tests document what was in the Java port scope but requires the CGAL mesh type.
- **Boost is header-only**: CGAL 6.x uses only Boost headers (`Boost.Config`, `Boost.Graph`). No compiled Boost libraries are needed. `find_package(Boost REQUIRED)` only locates the include path.
- **`main` branch is protected** on `origin` (Gitea). Push to `dev`, then merge via pull request. Codeberg `main` can be pushed to directly.
- **Both remotes must stay in sync**: `origin` = `git.eulernest.eu` (CI runs here), `codeberg` = `codeberg.org/TMoussa/ConformalLabpp` (public mirror). Push to both after every significant change.

17
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,17 @@
# Contributing to conformallab++
See **[doc/contributing.md](doc/contributing.md)** for the full guide:
- Git workflow (dev → PR → main)
- CI pipeline (test-fast / test-cgal)
- Test standards (gradient check, convergence test, registration)
- Code style (C++17, header-only, namespace, property map naming)
- Release process
For the mathematical background of what's being implemented, see:
- [doc/math/discrete-conformal-theory.md](doc/math/discrete-conformal-theory.md) — theory overview
- [doc/math/validation.md](doc/math/validation.md) — how to validate the implementation
- [doc/math/references.md](doc/math/references.md) — all referenced papers
To add your own research, see [doc/api/extending.md](doc/api/extending.md).

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 user2595
Copyright (c) 20242026 Tarik Moussa <Tarik.moussa95@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

754
README.md
View File

@@ -1,703 +1,133 @@
# conformallab++
conformallab++ is a modern C++ reimplementation of
[ConformalLab](https://github.com/varylab/conformallab) —
the research software for discrete conformal geometry by
**Stefan Sechelmann** (TU Berlin, Institut für Mathematik).
[![CI](https://git.eulernest.eu/conformallab/ConformalLabpp/actions/workflows/cpp-tests.yml/badge.svg)](https://git.eulernest.eu/conformallab/ConformalLabpp/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![DOI](https://img.shields.io/badge/doi-Sechelmann%202016-blue)](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f)
The algorithmic foundation is his doctoral dissertation:
C++17 reimplementation of [ConformalLab](https://github.com/varylab/conformallab) —
Stefan Sechelmann's Java research library for discrete conformal geometry (TU Berlin).
The long-term goal is a **CGAL package** for discrete conformal maps.
> Stefan Sechelmann —
> **Variational Methods for Discrete Surface Parameterization: Applications and Implementation**
> Doctoral thesis, Technische Universität Berlin, 2016.
> DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f)
> License: CC BY-SA 4.0
Algorithmic foundation:
> Stefan Sechelmann — *Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, TU Berlin 2016.
> DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) · CC BY-SA 4.0 ·
> [Java original](https://github.com/varylab/conformallab) · [sechel.de](https://sechel.de/)
The dissertation develops the variational framework for discrete conformal equivalence
on triangulations — discrete uniformization of Riemann surfaces, cone metrics, period
matrices — that forms the mathematical core of both the Java original and this C++ port.
**Original Java library:** [github.com/varylab/conformallab](https://github.com/varylab/conformallab) (Java, ~850 commits, v1.0.0 2018)
**Author's website:** [sechel.de](https://sechel.de/) · **LinkedIn:** [linkedin.com/in/sechel](https://www.linkedin.com/in/sechel/)
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 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, GaussBonnet, 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**.
**Status:** Phase 7 complete. Newton solver for all three geometries (Euclidean / Spherical / HyperIdeal), priority-BFS layout in ℝ²/S²/Poincaré disk, GaussBonnet, tree-cotree cut graph, Möbius holonomy, period matrix (genus 1), fundamental domain, halfedge_uv texture atlas, JSON/XML serialisation, CLI app. **173 CGAL tests + 36 non-CGAL tests.**
---
## Features
| 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, PinkallPolthier) | ✅ 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 |
| **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 |
| **GaussBonnet 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
```bash
cmake -S code -B build -DWITH_CGAL=ON
cmake --build build -j4
git clone https://codeberg.org/TMoussa/ConformalLabpp && cd ConformalLabpp
# 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
# Fast tests — no system dependencies
cmake -S code -B build && cmake --build build --target conformallab_tests -j$(nproc)
ctest --test-dir build --output-on-failure
# Geometrien: euclidean | spherical | hyper_ideal
./bin/conformallab_core -i input.off -g spherical -o sphere.off
# CGAL tests headless (apt install libboost-dev / brew install boost)
cmake -S code -B build -DWITH_CGAL_TESTS=ON
cmake --build build --target conformallab_cgal_tests -j$(nproc)
ctest --test-dir build -R "^cgal\." --output-on-failure
# Interaktiver Viewer
./bin/conformallab_core -i input.off -s
```
### Beispiel-Programme
```bash
./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)
# Full build with CLI + viewer (requires Wayland/X11 dev headers)
cmake -S code -B build -DWITH_CGAL=ON && cmake --build build -j$(nproc)
./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json
```
---
## Bibliotheks-Nutzung
### Minimale euklidische Pipeline
## Minimal usage
```cpp
#include "conformal_mesh.hpp"
#include "mesh_io.hpp"
#include "euclidean_functional.hpp"
#include "gauss_bonnet.hpp"
#include "newton_solver.hpp"
#include "layout.hpp"
using namespace conformallab;
int main() {
ConformalMesh mesh = load_mesh("input.off");
ConformalMesh mesh = load_mesh("input.off");
EuclideanMaps maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
auto maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
// Assign DOFs — pin first vertex (gauge fix)
auto vit = mesh.vertices().begin();
maps.v_idx[*vit++] = -1;
int idx = 0;
for (; vit != mesh.vertices().end(); ++vit) maps.v_idx[*vit] = idx++;
// DOFs zuweisen — ersten Vertex pinnen (Gauge-Fix)
auto vit = mesh.vertices().begin();
maps.v_idx[*vit++] = -1;
int idx = 0;
for (; vit != mesh.vertices().end(); ++vit)
maps.v_idx[*vit] = idx++;
// Natural equilibrium target: x* = 0 by construction
std::vector<double> x0(idx, 0.0);
auto G0 = euclidean_gradient(mesh, x0, maps);
for (auto v : mesh.vertices())
if (maps.v_idx[v] >= 0) maps.theta_v[v] -= G0[maps.v_idx[v]];
// Zielwinkel: natürliches Gleichgewicht (x* = 0)
std::vector<double> 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];
}
auto res = newton_euclidean(mesh, x0, maps);
if (res.converged)
save_mesh("output.off", mesh);
return res.converged ? 0 : 1;
}
```
### 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);
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)
```
### SparseQR-Fallback direkt nutzen
```cpp
#include "newton_solver.hpp"
bool used_fallback = false;
auto dx = conformallab::solve_linear_system(H, rhs, &used_fallback);
if (used_fallback)
std::cout << "SparseQR verwendet (H ist rangdefizient)\n";
check_gauss_bonnet(mesh, maps);
NewtonResult res = newton_euclidean(mesh, x0, maps);
Layout2D layout = euclidean_layout(mesh, res.x, maps);
```
---
## Build-Modi
| Modus | CMake-Flags | Was wird gebaut | System-Deps |
|-------|------------|-----------------|-------------|
| **Nur Tests** (Standard / CI fast) | *(keine)* | `conformallab_tests` — 36 Tests | keine |
| **CGAL-Tests headless** (CI full) | `-DWITH_CGAL_TESTS=ON` | `conformallab_cgal_tests` — 158 Tests | Boost |
| **Vollständig** (lokal mit Viewer) | `-DWITH_CGAL=ON` | + CLI, Beispiel-Programme, interaktiver Viewer | Boost + Wayland/X11 |
**`-DWITH_CGAL_TESTS=ON`** ist der empfohlene CI-Modus: kein GLFW, kein wayland-scanner, kein Display benötigt.
**`-DWITH_CGAL=ON`** aktiviert automatisch den Viewer — nur für lokale Entwicklung mit Display.
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_TESTS=ON` oder `-DWITH_CGAL=ON` benötigt (Header-Only durch CGAL 6.x).
---
## Voraussetzungen
| Tool | Minimum |
|------|---------|
| C++ Compiler (GCC oder Clang) | C++17 |
| CMake | 3.20 |
| Boost Headers | 1.70 *(nur mit `-DWITH_CGAL_TESTS=ON` oder `-DWITH_CGAL=ON`)* |
---
## Einstieg
```bash
git clone https://codeberg.org/TMoussa/ConformalLabpp
cd ConformalLabpp
```
### Nur Tests (CI-Standard — keine System-Abhängigkeiten)
```bash
cmake -S code -B build
cmake --build build --target conformallab_tests -j$(nproc)
ctest --test-dir build --output-on-failure
```
### CGAL-Tests headless (CI — kein Display, kein wayland-scanner)
```bash
# benötigt: apt install libboost-dev (Ubuntu) / brew install boost (macOS)
cmake -S code -B build -DWITH_CGAL_TESTS=ON
cmake --build build --target conformallab_cgal_tests -j$(nproc)
ctest --test-dir build -R "^cgal\." --output-on-failure
```
Erwartet: **158 Tests bestanden, 2 skipped** (die zwei `@Ignore`-Hessian-Stubs).
### Vollständig lokal (CLI + Beispiele + interaktiver Viewer)
```bash
# benötigt zusätzlich: Wayland/X11 dev-Pakete (wayland-scanner, libx11-dev …)
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_layout
./build/examples/example_viewer data/off/example.off
./bin/conformallab_core -i input.off -g euclidean -o layout.off
```
---
## Öffentliche Header (`code/include/`)
| Header | Beschreibung |
|--------|-------------|
| `clausen.hpp` | Clausen Cl₂, Lobachevsky Л, ImLi₂ |
| `hyper_ideal_geometry.hpp` | ζ-Funktionen, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ |
| `hyper_ideal_utility.hpp` | Tetraeder-Volumen (Meyerhoff / KolpakovMednykh) |
| `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 (PinkallPolthier) |
| `newton_solver.hpp` | `newton_{euclidean,spherical,hyper_ideal}` + öffentliches `solve_linear_system` |
| `conformal_mesh.hpp` | `ConformalMesh` = `CGAL::Surface_mesh<Point3>` + 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` |
| `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` + `compute_cut_graph` (Tree-Cotree, EricksonWhittlesey 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` |
---
## Projektstruktur
```
code/
├── include/ # Alle öffentlichen Header (Header-Only-Bibliothek)
│ ├── conformal_mesh.hpp
│ ├── mesh_builder.hpp
│ ├── 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/
│ ├── 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 # CLI-App (Phase 5)
│ └── viewer/simple_viewer.cpp
├── tests/
│ ├── *.cpp # conformallab_tests (kein CGAL)
│ └── cgal/
│ ├── 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/
├── CGAL-6.1.1/
├── libigl-2.6.0/
├── glfw-3.4/
└── single_includes/ # CLI11, json.hpp
```
---
## Test-Suiten
### `conformallab_tests` (CI — immer gebaut)
Reine Mathe-Tests, nur Eigen: Clausen / Lobachevsky / ImLi₂, Hyper-ideal Geometrie, Tetraeder-Volumina.
### `conformallab_cgal_tests` (`-DWITH_CGAL_TESTS=ON` headless / `-DWITH_CGAL=ON` voll)
| 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, FlagIndex-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.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_hyper_ideal(mesh, x0, maps [, tol, max_iter, hess_eps])
```
Jede Iteration: Gradient **G** → Hessian **H** → löse **H·Δx = G** (SimplicialLDLT, Fallback SparseQR) → Backtracking-Liniensuche.
| 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 |
**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.
---
## Mathematischer Umfang — C++ vs. Java-Original
| Mathematische Schicht | Java ConformalLab | conformallab++ |
|---|---|---|
| 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) | ✅ | ✅ |
| GaussBonnet Konsistenzprüfung | ✅ | ✅ |
| Tree-Cotree-Schnittgraph (2g Kanten) | ✅ | ✅ EricksonWhittlesey |
| 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 |
### HyperIdeal Hessian — numerisch vs. analytisch
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ε)
```
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.
---
## Für Mathematiker — Bibliothek erweitern
### Mentales Modell
```
ConformalMesh — Halfedge-Mesh (CGAL::Surface_mesh)
+ Property-Maps — per-Vertex/Edge Daten (λ, θ, α, DOF-Index, …)
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 ∈ ℝⁿ — DOF-Vektor, den der Solver optimiert
evaluate_*(mesh, x, maps) → { Energie, Gradient, … }
newton_*(mesh, x0, maps) → { x*, Iterationen, converged, … }
```
Die Mesh-Geometrie (Vertex-Positionen) dient nur zur Initialisierung der Log-Kantenlängen λ°. Danach arbeitet der Solver ausschließlich im x-Raum.
### Neues Funktional hinzufügen
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
### Halfedge-Mesh navigieren
```cpp
for (auto f : mesh.faces()) {
auto h0 = mesh.halfedge(f);
auto h1 = mesh.next(h0);
auto h2 = mesh.next(h1);
Vertex_index v_opp = mesh.target(h2); // dem Halfedge h0 gegenüberliegend
int dof = maps.v_idx[v_opp]; // 1 = gepinnt
bool is_boundary = mesh.is_border(mesh.opposite(h0));
}
```
### Neue Daten am Mesh befestigen
```cpp
auto [curv, created] = mesh.add_property_map<Vertex_index, double>("v:my_curv", 0.0);
curv[v] = 1.234;
```
### Schnell-Start-Checkliste
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 GaussBonnet-Bedingung Σ( Θ_v) = ·χ(M) muss erfüllt sein.
5. Konvergenz beobachten: `NewtonResult` enthält `iterations` und `grad_inf_norm`
### Weiterführende Literatur
| 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` |
---
## Schlüssel-Designentscheidungen
**CGAL als CoHDS-Ersatz.** `CGAL::Surface_mesh<Point3>` ersetzt die Java-`CoHDS`-Halfedge-Datenstruktur. Vertex/Edge/Face/Halfedge-Deskriptoren sind typisierte Integer.
**Property-Maps.** `mesh.add_property_map<Vertex_index, double>("v:lambda", 0.0)` ersetzt das Java-Adapter/Decorator-Pattern.
**DOF-Vektor-Konvention.** Alle Funktionale verwenden `x` indiziert durch `v_idx[v]` / `e_idx[e]` (1 = gepinnt). Einheitlich über alle drei Geometrien.
**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.
**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.
**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 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
# CI-Image neu bauen und pushen
docker buildx build \
--platform linux/arm64 \
-f .gitea/docker/Dockerfile.ci-cpp \
-t git.eulernest.eu/conformallab/ci-cpp:latest \
--push \
.gitea/docker/
```
---
## Roadmap
> **Legende:** ✅ abgeschlossen · 🔲 geplant
>
> **Grenze Portierung / neue Forschung:**
> Phase 17 sind direkte Portierungen aus dem Java-Original bzw. seiner Dissertation.
> Ab Phase 8 geht die Arbeit über den Umfang der Java-Bibliothek hinaus.
> — Phase 8 (CGAL-Paket) ist **Infrastruktur**, kein neuer Algorithmus.
> — Phase 9 (Inversive-Distance, Analytischer Hessian) ist **Portierung** ausstehender Java-Features.
> — Phase 10+ ist **eigenständige Forschung**, die über das Java-Original hinausgeht.
---
### ◼ Portierungsphase abgeschlossen
```
Phase 1 Clausen / Lobachevsky / ImLi₂ ✅
Phase 2 Hyper-ideal Geometrie (ζ, lᵢⱼ, αᵢⱼ, σᵢ) ✅
Phase 3 CGAL-Infrastruktur + alle drei Funktionale
+ analytische Hessians (Eucl. + Sphär.) ✅
Phase 4 Newton-Solver (SimplicialLDLT + SparseQR-Fallback)
+ Mesh-I/O + Beispielprogramme ✅
Phase 5 Priority-BFS-Layout + CLI + JSON/XML ✅ 95 Tests
Phase 6 Layout-Parität I ✅ 121 Tests
→ gauss_bonnet.hpp — χ, Genus, Σ(2π-Θ_v) Check + Enforce
→ cut_graph.hpp — Tree-Cotree (EricksonWhittlesey 2005), 2g Schnitt-Kanten
→ Exakte hyperbolische Trilateration (Möbius + Kosinussatz)
→ normalise_{euclidean,hyperbolic,spherical}
Phase 7 Layout-Parität II ✅ 158 Tests
→ MobiusMap — T(z)=(az+b)/(cz+d), from_three, compose, inverse
→ halfedge_uv — naht-bewusstes UV pro Halfedge (GPU-Texturatlas)
→ Möbius-Holonomie als SU(1,1)-Isometrie (hyperbolisch)
→ period_matrix.hpp — τ = ω₂/ω₁ ∈ , SL(2,)-Reduktion
→ fundamental_domain.hpp — CCW-Parallelogramm, Kachelung
```
---
### ◼ Infrastruktur (über Java-Bibliothek hinaus)
```
Phase 8 CGAL-Paket-Struktur 🔲 (nächste Phase)
Ziel: conformallab++ als eigenständiges CGAL-Paket, das in CGAL integriert
werden kann und dessen Konventionen vollständig erfüllt.
8a — Traits-Klasse & Konzepte
→ include/CGAL/Conformal_map_traits.h
Trennt MeshType, KernelType, ScalarType vom Algorithmus.
Ermöglicht Nutzung mit beliebigem CGAL-kompatiblem Mesh.
→ Konzept-Checks (static_assert / CGAL_concept_check)
8b — Öffentliche CGAL-Header-Hierarchie
→ include/CGAL/Discrete_conformal_map.h (zentraler Nutzer-Header)
→ include/CGAL/Conformal_newton_solver.h
→ include/CGAL/Conformal_layout.h
→ include/CGAL/Conformal_cut_graph.h
→ include/CGAL/conformal_map_package.h (Package-Description)
Alle bestehenden include/conformallab/*.hpp bleiben als Impl.-Detail.
8c — Dokumentation im CGAL-Stil
→ doc/Conformal_map/PackageDescription.txt
→ doc/Conformal_map/fig/ (Pipeline-Diagramme)
→ Doxygen-Kommentare für alle öffentlichen Konzepte + Funktionen
→ User_manual.md + Reference_manual.md
8d — CGAL-Testformat
→ test/Conformal_map/ (CMakeLists.txt im CGAL-Format)
Bestehende GTest-Tests bleiben; CGAL-Tests kommen als zweites Format.
8e — Declarative YAML-Pipeline
→ Leichtgewichtiges YAML-Format für reproduzierbare Experimente
(Spezifikation bereits in doc/architecture/overall_pipeline.md)
→ Validator: prüft require/provide-Tokens vor der Ausführung
→ Einbindung in CLI-App: conformallab_core --pipeline experiment.yml
```
---
### ◼ Ausstehende Portierung (Java-Features noch nicht übertragen)
```
Phase 9 Verbleibende Java-Parität 🔲
9a — Inversive-Distance-Funktional (Luo 2004 / BowersStephenson)
→ inversive_distance_functional.hpp (folgt exakt dem Muster der
bestehenden drei Funktionale — niedrigstes Risiko)
→ newton_inversive_distance()
→ Neue Test-Suite: test_inversive_distance.cpp
9b — Analytischer HyperIdeal-Hessian
→ Direkte Ableitung durch die Kette
(b_i, a_e) → l_ij → ζ₁₃/ζ₁₄/ζ₁₅ → α_ij / β_i
→ Ersetzt den symmetrischen FD-Hessian in hyper_ideal_hessian.hpp
→ Relevant für Meshes > 500 DOFs (aktueller FD-Hessian ist dort langsam)
→ Aufwand: ~2 Wochen (viele verschachtelte Fallunterscheidungen)
9c — 4g-Polygon-Randlauf (Genus g > 1)
→ Boundary-Walk auf dem aufgeschnittenen Mesh
→ Befüllt fundamental_domain.hpp für g > 1 (aktuell: leeres Objekt)
→ Algorithmus-Skizze bereits als TODO(Phase 8) in fundamental_domain.hpp
```
---
### ◼ Neue Forschung (über das Java-Original hinaus)
> Ab hier gibt es keine direkte Java-Referenzimplementierung mehr.
> Jedes Item ist eigenständige mathematische Arbeit.
```
Phase 10 Globale Uniformisierung Genus g ≥ 2 🔲 (Forschung)
10a — Holomorphe Differentiale auf diskreten Flächen
Integration ω_i längs der b-Zyklen des Schnittgraphen.
Mathematische Grundlage: BobenkoSpringborn (2004), §6.
10b — Siegel-Periodenmatrix Ω ∈ H_g (g×g, g ≥ 2)
Ω_ij = ∫_{b_j} ω_i — komplexe symmetrische Matrix,
Im(Ω) positiv definit (Siegel-Oberhalbebene H_g).
Reduktion auf den Siegel-Fundamentalbereich via Sp(2g,).
10c — Vollständige Uniformisierung
Für g ≥ 2: Einbettung als H²/Γ mit Γ ⊂ PSL(2,) Fuchssche Gruppe.
Erfordert 10a + 10b + stabilen Cut-Graph für g ≥ 2 (Phase 9c).
```
---
## Ursprung & Danksagung
conformallab++ wäre ohne die Grundlagenarbeit von **Stefan Sechelmann** nicht möglich.
Die Algorithmen, die Variationsformulierung und die Idee, diskrete konforme Geometrie
als Newton-Problem auf Winkel-Summen-Energie-Funktionalen zu behandeln, stammen aus:
## Documentation
| | |
|---|---|
| **Dissertation** | Stefan Sechelmann *Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, TU Berlin 2016 |
| **DOI** | [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) |
| **Java-Originalbibliothek** | [github.com/varylab/conformallab](https://github.com/varylab/conformallab) |
| **Website** | [sechel.de](https://sechel.de/) |
| **LinkedIn** | [linkedin.com/in/sechel](https://www.linkedin.com/in/sechel/) |
Die Dissertation steht unter Creative Commons Attribution ShareAlike 4.0 (CC BY-SA 4.0).
| **Getting started** — build modes, single-test invocation, CLI, Docker | [doc/getting-started.md](doc/getting-started.md) |
| **Pipeline API** — all three geometries, holonomy, serialisation | [doc/api/pipeline.md](doc/api/pipeline.md) |
| **Public headers** — all 24 headers with descriptions | [doc/api/headers.md](doc/api/headers.md) |
| **Test suites** — 28 suites, 170 tests, individual counts | [doc/api/tests.md](doc/api/tests.md) |
| **Extending** — new functionals, geometry modes, porting from Java | [doc/api/extending.md](doc/api/extending.md) |
| **Processing unit contracts** — preconditions / provides table | [doc/api/contracts.md](doc/api/contracts.md) |
| **CGAL package design** — Phase 8 target, YAML pipeline | [doc/api/cgal-package.md](doc/api/cgal-package.md) |
| **Architecture & pipeline diagram** | [doc/architecture/overall_pipeline.md](doc/architecture/overall_pipeline.md) |
| **geometry-central comparison** — shared core, demarcation, adoption candidates, scientific added value | [doc/architecture/geometry-central-comparison.md](doc/architecture/geometry-central-comparison.md) |
| **Design decisions** — key architectural choices + rationale | [doc/architecture/design-decisions.md](doc/architecture/design-decisions.md) |
| **Project structure** — directory tree + build targets | [doc/architecture/project-structure.md](doc/architecture/project-structure.md) |
| **Discrete conformal theory** — mathematical background for collaborators | [doc/math/discrete-conformal-theory.md](doc/math/discrete-conformal-theory.md) |
| **Validation** — known analytic results + how to verify them | [doc/math/validation.md](doc/math/validation.md) |
| **Validation protocol** — concrete commands with expected outputs | [doc/math/validation-protocol.md](doc/math/validation-protocol.md) |
| **Tutorial: add a new functional** — step-by-step Inversive-Distance port | [doc/tutorials/add-inversive-distance.md](doc/tutorials/add-inversive-distance.md) |
| **Declarative YAML pipeline** — concept, token vocabulary, 5 examples | [doc/concepts/declarative-pipeline.md](doc/concepts/declarative-pipeline.md) |
| **Geometry modes** — Euclidean / Spherical / HyperIdeal comparison | [doc/math/geometry-modes.md](doc/math/geometry-modes.md) |
| **References** — all papers by module | [doc/math/references.md](doc/math/references.md) |
| **Software landscape** — how conformallab++ relates to libigl, CGAL, geometry-central | [doc/math/software-landscape.md](doc/math/software-landscape.md) |
| **Novelty statement** — unique features, target audience, what this is not | [doc/math/novelty-statement.md](doc/math/novelty-statement.md) |
| **Roadmap** — Phases 110 | [doc/roadmap/phases.md](doc/roadmap/phases.md) |
| **Java parity table** — what is ported, what is planned | [doc/roadmap/java-parity.md](doc/roadmap/java-parity.md) |
| **Contributing** — language policy, test standards, release flow | [doc/contributing.md](doc/contributing.md) |
| **Claude Code context** | [CLAUDE.md](CLAUDE.md) |
---
## Lizenz
## Citing
conformallab++ steht unter der MIT-Lizenz (siehe [LICENSE](LICENSE)).
If you use conformallab++ in your research, please cite it using the metadata
in [`CITATION.cff`](CITATION.cff). GitHub and Codeberg show a "Cite this repository"
button that generates BibTeX and APA automatically.
The primary algorithmic source is:
> Stefan Sechelmann — *Variational Methods for Discrete Surface Parameterization:
> Applications and Implementation*, TU Berlin 2016.
> DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f)
---
## Bugs & questions
- **Bug reports / feature requests:** [Gitea Issues](https://git.eulernest.eu/conformallab/ConformalLabpp/issues)
- **Code mirror (read-only):** [Codeberg](https://codeberg.org/TMoussa/ConformalLabpp)
- **Contact:** Tarik Moussa · Tarik.moussa95@gmail.com
---
## License
conformallab++ is released under the MIT License (see [LICENSE](LICENSE)).
Copyright © 20242026 Tarik Moussa.
The dissertation (Sechelmann 2016) is CC BY-SA 4.0.

2
code/.gitignore vendored
View File

@@ -40,6 +40,8 @@ Thumbs.db
*.lo
*.o
*.obj
# Exception: mesh data files in code/data/ are not compiled objects
!data/**/*.obj
# Precompiled Headers
*.gch

View File

@@ -126,3 +126,17 @@ endif()
# ── Tests (always) ────────────────────────────────────────────────────────────
add_subdirectory(tests)
# ── Install target (header-only library) ──────────────────────────────────────
# Installs all public headers to <prefix>/include/conformallab/
# Usage from another CMake project:
# cmake --install build --prefix /usr/local
# target_include_directories(myapp PRIVATE /usr/local/include/conformallab)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/conformallab
FILES_MATCHING PATTERN "*.hpp"
PATTERN "* 2.*" EXCLUDE) # exclude macOS Finder duplicates
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE
${CMAKE_CURRENT_SOURCE_DIR}/../CITATION.cff
DESTINATION ${CMAKE_INSTALL_DATADIR}/conformallab)

27662
code/data/obj/brezel.obj Executable file

File diff suppressed because it is too large Load Diff

7874
code/data/obj/brezel2.obj Normal file

File diff suppressed because it is too large Load Diff

379
code/data/obj/cathead.obj Normal file
View File

@@ -0,0 +1,379 @@
v -0.206972 0.0740737 0.544664
v -0.398695 -0.0740794 0.501089
v -0.211327 -0.187367 0.588234
v -0.511983 -0.235298 0.400872
v -0.56863 0.0348535 0.405227
v -0.35948 0.178646 0.50545
v -0.525054 0.313721 0.387801
v -0.189545 0.30065 0.544664
v 0.124182 0.0740737 0.509805
v -0.455336 -0.33987 0.544664
v -0.411766 -0.427021 0.671024
v -0.237476 -0.413949 0.745097
v -0.250547 -0.535954 0.797389
v -0.346403 -0.562097 0.758169
v 0.0152491 -0.33116 0.531593
v -0.106755 -0.496734 0.692812
v -0.254903 -0.692812 0.749458
v -0.0631798 -0.601311 0.55338
v -0.14597 -0.666669 0.666669
v -0.250547 -0.727671 0.583879
v -0.198256 -0.714599 0.42266
v 0.0283206 -0.514166 0.383445
v -0.124182 -0.623099 0.196078
v -0.285401 -0.692812 0.413944
v -0.333331 -0.596956 0.226582
v -0.333331 -0.418305 0.413944
v 0.233115 -0.366019 0.313727
v 0.215688 -0.156864 0.457519
v -0.342048 -0.623099 -0.0915061
v -0.708061 -0.431376 0.0697184
v -0.647058 -0.501095 -0.0915061
v -0.716777 -0.318088 0.169935
v -0.699344 -0.143792 0.235292
v -0.747275 0.00871052 0.235292
v -0.747275 0.165574 0.191723
v -0.764708 0.291939 0.12636
v -0.747275 0.461874 0.0130715
v -0.337692 0.535948 0.366013
v -0.472768 0.644881 0.0653574
v -0.272329 0.753814 0.0827899
v -0.294117 0.797383 0.278868
v -0.307188 0.801744 0.435731
v -0.324621 0.779957 0.583879
v -0.355119 0.714594 0.710238
v -0.407405 0.59695 0.692812
v -0.442264 0.505444 0.610022
v -0.477123 0.413944 0.51416
v -0.285401 0.509805 0.666669
v -0.185184 0.605661 0.631809
v -0.0326816 0.518516 0.479301
v -0.198256 0.72331 0.575162
v -0.124182 0.745097 0.278868
v 0.189545 0.479301 0.357297
v -0.0413921 0.675379 0.352942
v -0.843136 -0.0392202 -0.0522859
v -0.764708 -0.33116 0.0348592
v -0.816994 -0.152508 -0.178651
v -0.87364 -0.0522916 -0.187362
v -0.816994 0.108933 -0.204794
v -0.816994 0.25708 -0.00435526
v -0.795206 0.387795 -0.0653574
v -0.764708 0.440087 -0.152503
v -0.642703 0.535948 -0.370368
v -0.516338 0.636165 -0.21351
v -0.856208 0.265791 -0.148147
v -0.816994 0.261435 -0.291939
v -0.847497 0.239648 -0.418299
v -0.82571 0.265791 -0.479301
v -0.912855 -0.0697184 -0.270152
v -0.947714 0.0130715 -0.326799
v -0.938998 0.0740737 -0.392156
v -0.799567 -0.344231 -0.108933
v -0.620916 -0.509805 -0.331154
v -0.764708 -0.287584 -0.244009
v -0.799567 -0.291939 -0.352942
v -0.9085 -0.174296 -0.33987
v -1 -0.0697184 -0.409588
v -0.965141 -0.148153 -0.461874
v -0.95643 -0.0610022 -0.549019
v -0.978212 0.021782 -0.483662
v -0.729848 -0.357302 -0.501089
v -0.921571 -0.230937 -0.544664
v -0.821354 -0.222227 -0.662308
v -0.9085 -0.16558 -0.623093
v -0.342048 -0.557736 -0.383445
v -0.381262 -0.300656 -0.727671
v -0.167757 -0.557736 -0.501089
v -0.599128 -0.252725 -0.692812
v -0.420482 -0.0697184 -0.797389
v -0.14597 -0.610027 -0.631809
v -0.947714 0.0915004 -0.522877
v -0.891067 0.178646 -0.50545
v -0.869279 -0.0697184 -0.705883
v -0.934643 0.0348535 -0.627454
v -0.847497 0.156858 -0.649236
v -0.620916 0.235292 -0.701528
v -0.559913 0.143786 -0.745097
v -0.315905 0.278862 -0.753814
v -0.0196101 0.440087 -0.784312
v -0.211327 0.46623 -0.636165
v -0.381262 0.505444 -0.579523
v -0.647058 0.374724 -0.601305
v -0.355119 0.653591 -0.344225
v -0.0108939 0.644881 -0.313727
v 0.163396 0.64052 0.104578
v 0.433554 0.0697127 0.322443
v 0.516344 -0.318088 0.0435754
v 0.35948 -0.492378 -0.0653574
v 0.185184 -0.535954 0.0305039
v 0.0631798 -0.618738 -0.313727
v 0.328976 -0.618738 -0.296295
v 0.102394 -0.684101 -0.43137
v 0.612199 -0.396517 -0.16558
v 0.307188 0.522877 -0.779957
v 0.163396 0.549019 -0.562091
v 0.468413 0.400872 0.00871623
v 0.599128 0.374724 -0.261435
v 0.721132 0.357297 -0.50545
v 0.843136 0.326793 -0.718955
v 0.124182 -0.801744 -0.562091
v 0.35948 -0.749458 -0.448803
v 0.559913 0.0479307 0.161219
v 0.673201 0.0304982 -0.0566469
v 0.808283 0.0174267 -0.309366
v 0.891067 -0.00871623 -0.483662
v 1 -0.0697184 -0.753814
v 0.355119 0.440087 0.191723
v 0.694989 -0.58824 -0.440087
v 0.886712 -0.230937 -0.457519
v 0.912855 -0.366019 -0.575162
v -0.921571 -0.126365 -0.300656
f 1 2 3
f 4 2 5
f 2 1 5
f 1 6 5
f 6 7 5
f 7 6 8
f 6 1 8
f 9 1 3
f 3 4 10
f 10 11 3
f 11 12 3
f 11 13 12
f 13 11 14
f 12 15 3
f 15 12 16
f 12 13 16
f 13 17 16
f 17 13 14
f 16 18 15
f 18 16 19
f 16 17 19
f 17 20 19
f 20 21 19
f 21 18 19
f 18 21 22
f 21 23 22
f 21 24 23
f 24 21 20
f 24 25 23
f 25 24 26
f 24 20 26
f 20 17 26
f 17 14 26
f 14 11 26
f 11 10 26
f 10 4 26
f 3 15 9
f 15 27 28
f 23 25 29
f 25 30 31
f 30 25 32
f 25 26 32
f 4 33 32
f 18 22 15
f 22 27 15
f 5 34 33
f 34 5 35
f 5 7 35
f 7 36 35
f 36 7 37
f 7 38 37
f 38 39 37
f 39 38 40
f 38 41 40
f 41 38 42
f 38 43 42
f 43 38 44
f 38 45 44
f 45 38 46
f 46 38 47
f 38 7 47
f 7 8 47
f 8 46 47
f 46 8 48
f 8 49 48
f 49 44 48
f 44 45 48
f 49 8 50
f 49 51 44
f 51 43 44
f 43 51 42
f 51 52 42
f 52 41 42
f 41 52 40
f 53 54 50
f 49 54 51
f 54 49 50
f 51 54 52
f 50 9 53
f 9 50 8
f 55 33 34
f 33 55 32
f 55 56 32
f 56 55 57
f 55 58 57
f 58 55 59
f 55 60 59
f 60 55 35
f 55 34 35
f 36 60 35
f 60 36 37
f 37 61 60
f 61 37 62
f 37 63 62
f 63 37 64
f 37 39 64
f 39 40 64
f 62 65 61
f 65 62 66
f 62 67 66
f 67 62 68
f 62 63 68
f 65 59 60
f 59 69 58
f 69 59 70
f 59 71 70
f 71 59 67
f 59 66 67
f 59 65 66
f 56 30 32
f 30 56 31
f 56 72 31
f 72 56 57
f 60 61 65
f 46 48 45
f 31 29 25
f 29 31 73
f 31 74 73
f 74 31 72
f 57 74 72
f 74 57 75
f 57 76 75
f 57 58 69
f 77 78 76
f 78 77 79
f 77 80 79
f 80 77 71
f 77 70 71
f 70 77 69
f 75 73 74
f 73 75 81
f 75 82 81
f 82 75 76
f 82 83 81
f 83 82 84
f 82 79 84
f 79 82 78
f 82 76 78
f 73 85 29
f 85 73 86
f 73 81 86
f 85 87 29
f 87 85 86
f 88 86 81
f 86 88 89
f 88 83 89
f 83 88 81
f 86 90 87
f 71 91 80
f 91 71 92
f 71 67 92
f 67 68 92
f 79 93 84
f 93 79 94
f 79 91 94
f 91 79 80
f 92 95 91
f 92 68 95
f 91 95 94
f 95 93 94
f 93 95 96
f 95 68 96
f 93 83 84
f 83 93 89
f 93 97 89
f 97 93 96
f 89 97 98
f 98 100 99
f 100 98 101
f 98 96 101
f 98 97 96
f 102 68 63
f 68 102 96
f 102 101 96
f 101 102 63
f 64 103 63
f 103 64 40
f 103 101 63
f 101 104 100
f 104 101 103
f 40 104 103
f 40 52 104
f 52 105 104
f 9 106 53
f 106 9 28
f 106 27 107
f 27 106 28
f 27 108 107
f 108 27 22
f 22 109 108
f 109 22 23
f 109 110 108
f 110 109 23
f 23 29 110
f 110 111 108
f 111 110 112
f 110 90 112
f 90 110 87
f 110 29 87
f 108 111 113
f 108 113 107
f 114 99 115
f 99 104 115
f 104 105 115
f 116 117 115
f 117 118 115
f 115 118 114
f 118 119 114
f 120 112 90
f 112 120 111
f 120 121 111
f 99 100 104
f 2 4 3
f 5 33 4
f 116 122 123
f 123 117 116
f 117 123 124
f 117 124 118
f 124 125 118
f 125 119 118
f 119 125 126
f 122 106 107
f 105 116 115
f 54 53 105
f 52 54 105
f 105 127 116
f 122 116 127
f 106 122 127
f 53 127 105
f 121 128 113
f 121 113 111
f 113 128 124
f 128 129 124
f 129 128 130
f 107 113 122
f 113 123 122
f 123 113 124
f 1 9 8
f 9 15 28
f 4 32 26
f 106 127 53
f 57 131 76
f 131 77 76
f 69 131 57
f 131 69 77
f 129 125 124
f 125 130 126
f 130 125 129

16
code/data/obj/tetraflat.obj Executable file
View File

@@ -0,0 +1,16 @@
#
# Wavefront OBJ file
# Converted by the DEEP Exploration Deep Exploration 5 5.0.3.1555 Release
# Right Hemisphere, LTD
# http://www.righthemisphere.com/
#
# object sgc 1
g sgc_1
v 0.00000 0.00000 0.00000
v -1.29492 0.95275 -0.28653
v 1.14390 0.47528 1.06408
v 0.15103 -1.42803 -0.77755
# 4 verticies
f 1 2 3
f 2 1 4
f 1 3 4

View File

@@ -0,0 +1,50 @@
OFF
16 32 0
3.000000 0.000000 0.000000
2.000000 0.000000 1.000000
1.000000 0.000000 0.000000
2.000000 0.000000 -1.000000
0.000000 3.000000 0.000000
0.000000 2.000000 1.000000
0.000000 1.000000 0.000000
0.000000 2.000000 -1.000000
-3.000000 0.000000 0.000000
-2.000000 0.000000 1.000000
-1.000000 0.000000 0.000000
-2.000000 0.000000 -1.000000
-0.000000 -3.000000 0.000000
-0.000000 -2.000000 1.000000
-0.000000 -1.000000 0.000000
-0.000000 -2.000000 -1.000000
3 0 4 1
3 1 4 5
3 1 5 2
3 2 5 6
3 2 6 3
3 3 6 7
3 3 7 0
3 0 7 4
3 4 8 5
3 5 8 9
3 5 9 6
3 6 9 10
3 6 10 7
3 7 10 11
3 7 11 4
3 4 11 8
3 8 12 9
3 9 12 13
3 9 13 10
3 10 13 14
3 10 14 11
3 11 14 15
3 11 15 8
3 8 15 12
3 12 0 13
3 13 0 1
3 13 1 14
3 14 1 2
3 14 2 15
3 15 2 3
3 15 3 12
3 12 3 0

194
code/data/off/torus_8x8.off Normal file
View File

@@ -0,0 +1,194 @@
OFF
64 128 0
4.000000 0.000000 0.000000
3.707107 0.000000 0.707107
3.000000 0.000000 1.000000
2.292893 0.000000 0.707107
2.000000 0.000000 0.000000
2.292893 0.000000 -0.707107
3.000000 0.000000 -1.000000
3.707107 0.000000 -0.707107
2.828427 2.828427 0.000000
2.621320 2.621320 0.707107
2.121320 2.121320 1.000000
1.621320 1.621320 0.707107
1.414214 1.414214 0.000000
1.621320 1.621320 -0.707107
2.121320 2.121320 -1.000000
2.621320 2.621320 -0.707107
0.000000 4.000000 0.000000
0.000000 3.707107 0.707107
0.000000 3.000000 1.000000
0.000000 2.292893 0.707107
0.000000 2.000000 0.000000
0.000000 2.292893 -0.707107
0.000000 3.000000 -1.000000
0.000000 3.707107 -0.707107
-2.828427 2.828427 0.000000
-2.621320 2.621320 0.707107
-2.121320 2.121320 1.000000
-1.621320 1.621320 0.707107
-1.414214 1.414214 0.000000
-1.621320 1.621320 -0.707107
-2.121320 2.121320 -1.000000
-2.621320 2.621320 -0.707107
-4.000000 0.000000 0.000000
-3.707107 0.000000 0.707107
-3.000000 0.000000 1.000000
-2.292893 0.000000 0.707107
-2.000000 0.000000 0.000000
-2.292893 0.000000 -0.707107
-3.000000 0.000000 -1.000000
-3.707107 0.000000 -0.707107
-2.828427 -2.828427 0.000000
-2.621320 -2.621320 0.707107
-2.121320 -2.121320 1.000000
-1.621320 -1.621320 0.707107
-1.414214 -1.414214 0.000000
-1.621320 -1.621320 -0.707107
-2.121320 -2.121320 -1.000000
-2.621320 -2.621320 -0.707107
-0.000000 -4.000000 0.000000
-0.000000 -3.707107 0.707107
-0.000000 -3.000000 1.000000
-0.000000 -2.292893 0.707107
-0.000000 -2.000000 0.000000
-0.000000 -2.292893 -0.707107
-0.000000 -3.000000 -1.000000
-0.000000 -3.707107 -0.707107
2.828427 -2.828427 0.000000
2.621320 -2.621320 0.707107
2.121320 -2.121320 1.000000
1.621320 -1.621320 0.707107
1.414214 -1.414214 0.000000
1.621320 -1.621320 -0.707107
2.121320 -2.121320 -1.000000
2.621320 -2.621320 -0.707107
3 0 8 1
3 1 8 9
3 1 9 2
3 2 9 10
3 2 10 3
3 3 10 11
3 3 11 4
3 4 11 12
3 4 12 5
3 5 12 13
3 5 13 6
3 6 13 14
3 6 14 7
3 7 14 15
3 7 15 0
3 0 15 8
3 8 16 9
3 9 16 17
3 9 17 10
3 10 17 18
3 10 18 11
3 11 18 19
3 11 19 12
3 12 19 20
3 12 20 13
3 13 20 21
3 13 21 14
3 14 21 22
3 14 22 15
3 15 22 23
3 15 23 8
3 8 23 16
3 16 24 17
3 17 24 25
3 17 25 18
3 18 25 26
3 18 26 19
3 19 26 27
3 19 27 20
3 20 27 28
3 20 28 21
3 21 28 29
3 21 29 22
3 22 29 30
3 22 30 23
3 23 30 31
3 23 31 16
3 16 31 24
3 24 32 25
3 25 32 33
3 25 33 26
3 26 33 34
3 26 34 27
3 27 34 35
3 27 35 28
3 28 35 36
3 28 36 29
3 29 36 37
3 29 37 30
3 30 37 38
3 30 38 31
3 31 38 39
3 31 39 24
3 24 39 32
3 32 40 33
3 33 40 41
3 33 41 34
3 34 41 42
3 34 42 35
3 35 42 43
3 35 43 36
3 36 43 44
3 36 44 37
3 37 44 45
3 37 45 38
3 38 45 46
3 38 46 39
3 39 46 47
3 39 47 32
3 32 47 40
3 40 48 41
3 41 48 49
3 41 49 42
3 42 49 50
3 42 50 43
3 43 50 51
3 43 51 44
3 44 51 52
3 44 52 45
3 45 52 53
3 45 53 46
3 46 53 54
3 46 54 47
3 47 54 55
3 47 55 40
3 40 55 48
3 48 56 49
3 49 56 57
3 49 57 50
3 50 57 58
3 50 58 51
3 51 58 59
3 51 59 52
3 52 59 60
3 52 60 53
3 53 60 61
3 53 61 54
3 54 61 62
3 54 62 55
3 55 62 63
3 55 63 48
3 48 63 56
3 56 0 57
3 57 0 1
3 57 1 58
3 58 1 2
3 58 2 59
3 59 2 3
3 59 3 60
3 60 3 4
3 60 4 61
3 61 4 5
3 61 5 62
3 62 5 6
3 62 6 63
3 63 6 7
3 63 7 56
3 56 7 0

View File

@@ -0,0 +1,110 @@
OFF
36 72 0
4.000000 0.000000 0.000000
3.500000 0.000000 0.866025
2.500000 0.000000 0.866025
2.000000 0.000000 0.000000
2.500000 0.000000 -0.866025
3.500000 0.000000 -0.866025
2.000000 3.464102 0.000000
1.750000 3.031089 0.866025
1.250000 2.165064 0.866025
1.000000 1.732051 0.000000
1.250000 2.165064 -0.866025
1.750000 3.031089 -0.866025
-2.000000 3.464102 0.000000
-1.750000 3.031089 0.866025
-1.250000 2.165064 0.866025
-1.000000 1.732051 0.000000
-1.250000 2.165064 -0.866025
-1.750000 3.031089 -0.866025
-4.000000 0.000000 0.000000
-3.500000 0.000000 0.866025
-2.500000 0.000000 0.866025
-2.000000 0.000000 0.000000
-2.500000 0.000000 -0.866025
-3.500000 0.000000 -0.866025
-2.000000 -3.464102 0.000000
-1.750000 -3.031089 0.866025
-1.250000 -2.165064 0.866025
-1.000000 -1.732051 0.000000
-1.250000 -2.165064 -0.866025
-1.750000 -3.031089 -0.866025
2.000000 -3.464102 0.000000
1.750000 -3.031089 0.866025
1.250000 -2.165064 0.866025
1.000000 -1.732051 0.000000
1.250000 -2.165064 -0.866025
1.750000 -3.031089 -0.866025
3 0 6 1
3 1 6 7
3 1 7 2
3 2 7 8
3 2 8 3
3 3 8 9
3 3 9 4
3 4 9 10
3 4 10 5
3 5 10 11
3 5 11 0
3 0 11 6
3 6 12 7
3 7 12 13
3 7 13 8
3 8 13 14
3 8 14 9
3 9 14 15
3 9 15 10
3 10 15 16
3 10 16 11
3 11 16 17
3 11 17 6
3 6 17 12
3 12 18 13
3 13 18 19
3 13 19 14
3 14 19 20
3 14 20 15
3 15 20 21
3 15 21 16
3 16 21 22
3 16 22 17
3 17 22 23
3 17 23 12
3 12 23 18
3 18 24 19
3 19 24 25
3 19 25 20
3 20 25 26
3 20 26 21
3 21 26 27
3 21 27 22
3 22 27 28
3 22 28 23
3 23 28 29
3 23 29 18
3 18 29 24
3 24 30 25
3 25 30 31
3 25 31 26
3 26 31 32
3 26 32 27
3 27 32 33
3 27 33 28
3 28 33 34
3 28 34 29
3 29 34 35
3 29 35 24
3 24 35 30
3 30 0 31
3 31 0 1
3 31 1 32
3 32 1 2
3 32 2 33
3 33 2 3
3 33 3 34
3 34 3 4
3 34 4 35
3 35 4 5
3 35 5 30
3 30 5 0

View File

@@ -5,9 +5,10 @@
#
# Which deps are extracted depends on the active build mode:
#
# (default) Eigen only → tests-only build
# WITH_VIEWER=ON + Eigen, libigl, libigl-glad, glfw
# WITH_CGAL=ON + Eigen, CGAL (WITH_VIEWER is implied by WITH_CGAL)
# (default) Eigen only
# WITH_CGAL_TESTS=ON + Eigen, CGAL (headless CI — no viewer)
# WITH_VIEWER=ON + Eigen, libigl, libigl-glad, glfw
# WITH_CGAL=ON + Eigen, CGAL, libigl, libigl-glad, glfw
#
function(setup_dependency NAME SUBDIR)
@@ -51,6 +52,7 @@ if(WITH_VIEWER)
endif()
# ── CGAL mode ─────────────────────────────────────────────────────────────────
if(WITH_CGAL)
# Both WITH_CGAL_TESTS (headless CI) and WITH_CGAL (full build) need CGAL headers.
if(WITH_CGAL OR WITH_CGAL_TESTS)
setup_dependency("CGAL-6.1.1" "include")
endif()

View File

@@ -452,6 +452,29 @@ inline void set_root_huv_2d(
} // namespace detail
// ── Euclidean layout ──────────────────────────────────────────────────────────
/// Embed the mesh in ℝ² using the Euclidean metric encoded in x.
///
/// Runs priority-BFS trilateration: places vertices in order of BFS depth
/// from the root face (largest 3D area), so errors accumulate last.
/// For closed genus-g surfaces a CutGraph must be supplied — otherwise
/// the layout will have a seam discontinuity (Layout2D::has_seam = true).
///
/// \param mesh Input surface mesh. Must have lambda0 and v_idx set in maps.
/// \param x DOF vector returned by newton_euclidean().
/// \param maps EuclideanMaps (lambda0, v_idx, e_idx).
/// \param cut Optional cut graph (compute_cut_graph()). Pass nullptr for
/// open meshes or if seams are acceptable.
/// \param holonomy If non-null and cut != nullptr, receives the lattice
/// translations ω_i ∈ per cut edge.
/// Pass to compute_period_matrix() for the conformal modulus τ.
/// \param normalise If true, calls normalise_euclidean() on the result:
/// centroid → origin, major axis → x-axis (PCA).
/// \return Layout2D with .uv[v] (per-vertex UV) and
/// .halfedge_uv[h] (per-halfedge UV for texture atlasing).
///
/// \note halfedge_uv differs from uv at seam edges: the two sides of a cut
/// carry different UV coordinates for proper GPU texture atlasing.
inline Layout2D euclidean_layout(
ConformalMesh& mesh,
const std::vector<double>& x,
@@ -571,6 +594,20 @@ inline Layout2D euclidean_layout(
}
// ── Spherical layout ──────────────────────────────────────────────────────────
/// Embed the mesh on the unit sphere S² using the spherical metric encoded in x.
///
/// Runs priority-BFS trilateration using the spherical law of cosines.
/// Typical use: genus-0 (sphere-like) surfaces after newton_spherical().
///
/// \param mesh Input genus-0 surface mesh.
/// \param x DOF vector returned by newton_spherical().
/// \param maps SphericalMaps.
/// \param cut Optional cut graph (rarely needed for genus-0).
/// \param holonomy If non-null, receives rotational holonomies (spherical).
/// \param normalise If true, calls normalise_spherical(): rotates the centroid
/// to the north pole (Rodrigues rotation formula).
/// \return Layout3D with .xyz[v] ∈ S² ⊂ ℝ³ for each vertex.
inline Layout3D spherical_layout(
ConformalMesh& mesh,
const std::vector<double>& x,
@@ -670,6 +707,29 @@ inline Layout3D spherical_layout(
}
// ── HyperIdeal layout (Poincaré disk) — exact trilateration ──────────────────
/// Embed the mesh in the Poincaré disk (H²) using the hyperbolic metric encoded in x.
///
/// Runs priority-BFS trilateration using exact Möbius-isometric placement:
/// each new vertex is located by solving the hyperbolic law of cosines and
/// applying a Möbius map to position it in the disk.
/// For closed genus-g surfaces (g ≥ 1) a CutGraph is required.
///
/// \param mesh Input genus-g surface mesh (g ≥ 1).
/// \param x DOF vector returned by newton_hyper_ideal()
/// (vertex b_v and edge a_e variables).
/// \param maps HyperIdealMaps (lambda0, v_idx, e_idx).
/// \param cut CutGraph from compute_cut_graph(). Required for closed surfaces.
/// \param holonomy If non-null, receives the Möbius maps T_i ∈ SU(1,1) per cut edge.
/// Pass to compute_period_matrix() for holonomy analysis.
/// \param normalise If true, calls normalise_hyperbolic(): iterative face-area-weighted
/// Möbius centring (Fréchet mean, 30 iterations) → disk origin.
/// \return Layout2D with .uv[v] ∈ Poincaré disk (|uv| < 1) for each vertex,
/// and .halfedge_uv[h] for seam-aware texture atlasing.
///
/// \note All vertex positions satisfy |uv[v]| < 1 (inside the Poincaré disk)
/// if the metric is hyperbolic. Points on or outside the boundary indicate
/// a non-hyperbolic metric (GaussBonnet violation).
inline Layout2D hyper_ideal_layout(
ConformalMesh& mesh,
const std::vector<double>& x,

View File

@@ -139,9 +139,29 @@ inline std::vector<double> line_search(
} // namespace detail
// ── Euclidean Newton solver ────────────────────────────────────────────────────
//
// Minimises the Euclidean discrete conformal energy by solving G(x) = 0.
// The Hessian H is PSD; Eigen::SimplicialLDLT is used directly.
/// Solve the Euclidean discrete conformal problem: find u ∈ ^V such that
/// Σ_{faces adj v} α_v(u) = Θ_v for all vertices v.
///
/// Starting from x0, Newton's method minimises E(u) (the Euclidean DCE energy,
/// which is convex) by iterating u ← u H⁻¹·G with backtracking line search.
/// The Hessian H is the cotangent Laplacian — PSD with one zero eigenvalue on
/// closed surfaces (gauge mode). A SparseQR fallback handles this automatically.
///
/// \param mesh Input triangulated surface (edges must carry lambda0 + theta_v).
/// \param x0 Initial DOF vector (length = number of free vertices).
/// Pass all-zeros for a flat start (typical).
/// \param m EuclideanMaps: lambda0[e], theta_v[v], v_idx[v] must be set.
/// Call setup_euclidean_maps() + compute_euclidean_lambda0_from_mesh()
/// + enforce_gauss_bonnet() before passing here.
/// \param tol Convergence threshold on max |G_i|. Default: 1e-8.
/// \param max_iter Maximum Newton iterations. Default: 200.
/// \return NewtonResult{x*, iterations, grad_inf_norm, converged}.
///
/// \note On closed meshes without a pinned vertex, SimplicialLDLT detects the
/// gauge singularity and falls back to SparseQR automatically.
///
/// \see doc/math/discrete-conformal-theory.md §3 for the mathematical background.
inline NewtonResult newton_euclidean(
ConformalMesh& mesh,
std::vector<double> x0,
@@ -199,10 +219,28 @@ inline NewtonResult newton_euclidean(
}
// ── Spherical Newton solver ───────────────────────────────────────────────────
//
// Solves G(x) = 0 for the spherical discrete conformal functional.
// The Hessian H is NSD at the solution; H is PSD, so we factorise H and
// solve (H)·Δx = G ⟺ H·Δx = G.
/// Solve the spherical discrete conformal problem: find u ∈ ^V such that
/// Σ_{faces adj v} α_v(u) = Θ_v for all vertices v (genus-0 / sphere-like surfaces).
///
/// The spherical DCE energy is *concave*, so the Hessian H is NSD at the solution.
/// The solver factorises H (which is PSD) and solves (H)·Δx = G.
/// A gauge vertex must be pinned (set v_idx = -1) to remove the rotational mode.
///
/// \param mesh Input triangulated surface, genus 0.
/// \param x0 Initial DOF vector (length = free vertices, excluding gauge_vertex).
/// All-zeros is a good start.
/// \param m SphericalMaps: lambda0[e], theta_v[v], v_idx[v], gauge_vertex set.
/// Call setup_spherical_maps() + compute_spherical_lambda0_from_mesh()
/// + enforce_gauss_bonnet() (checks Σ(2π-Θ) > 0) before passing here.
/// \param tol Convergence threshold on max |G_i|. Default: 1e-8.
/// \param max_iter Maximum Newton iterations. Default: 200.
/// \return NewtonResult{x*, iterations, grad_inf_norm, converged}.
///
/// \note Unlike the Euclidean solver, the spherical solver does NOT need a SparseQR
/// fallback — the gauge vertex pins the null mode directly.
///
/// \see doc/math/geometry-modes.md §Spherical for sign-convention details.
inline NewtonResult newton_spherical(
ConformalMesh& mesh,
std::vector<double> x0,
@@ -258,17 +296,31 @@ inline NewtonResult newton_spherical(
}
// ── HyperIdeal Newton solver ──────────────────────────────────────────────────
//
// Solves G(x) = 0 for the hyper-ideal discrete conformal functional.
//
// Gradient sign convention (opposite to Euclidean/Spherical):
// G_v = Σ β_v Θ_v, G_e = Σ α_e θ_e (actual target)
//
// The hyper-ideal energy is strictly convex (Springborn 2020), so H is PSD
// and SimplicialLDLT (with SparseQR fallback) applies directly.
//
// The Hessian is computed by symmetric finite differences of G (see
// hyper_ideal_hessian.hpp); replace with an analytical Hessian in Phase 5.
/// Solve the hyper-ideal discrete conformal problem: find (b, a) ∈ ^{V+E} such that
/// Σ β_v(b,a) = Θ_v and Σ α_e(b,a) = θ_e for all vertices v and edges e.
///
/// Used for genus-g surfaces (g ≥ 1) under hyperbolic cone metrics.
/// The energy is *strictly convex* (Springborn 2020, Theorem 1.3), so Newton
/// converges globally from any starting point.
///
/// DOF layout: first V_free entries are vertex variables b_v (hyper-ideal radii),
/// followed by E entries for edge variables a_e (intersection angles).
/// Use assign_all_dof_indices(mesh, maps) to set v_idx and e_idx automatically —
/// no vertex needs to be pinned.
///
/// \param mesh Input triangulated surface, genus g ≥ 1.
/// \param x0 Initial DOF vector (length = V + E). All-zeros typical.
/// \param m HyperIdealMaps: lambda0[e], theta_v[v], v_idx[v], e_idx[e] set.
/// Call setup_hyper_ideal_maps() + compute_hyper_ideal_lambda0_from_mesh().
/// \param tol Convergence threshold on max |G_i|. Default: 1e-8.
/// \param max_iter Maximum Newton iterations. Default: 200.
/// \param hess_eps Finite-difference step for Hessian approximation. Default: 1e-5.
/// (Phase 9b will replace this with an analytic Hessian.)
/// \return NewtonResult{x*, iterations, grad_inf_norm, converged}.
///
/// \see Springborn (2020), Theorem 1.3 for the strict convexity proof.
/// \see doc/math/geometry-modes.md §Hyper-ideal for DOF layout details.
inline NewtonResult newton_hyper_ideal(
ConformalMesh& mesh,
std::vector<double> x0,

View File

@@ -65,6 +65,9 @@ target_include_directories(conformallab_cgal_tests PRIVATE
target_compile_definitions(conformallab_cgal_tests PRIVATE
CGAL_DISABLE_GMP
CGAL_DISABLE_MPFR
# Data directory — absolute path to code/data/ at build time.
# Used by tests that load real mesh files (cathead.obj, brezel2.obj, …).
CONFORMALLAB_DATA_DIR="${CMAKE_SOURCE_DIR}/data"
)
# Suppress warnings from CGAL/Boost headers

View File

@@ -10,7 +10,10 @@
// ConvergenceUtilityTests.java testGetTextureCircumRadius PORTIERT
// ConvergenceUtilityTests.java testGetTextureTriangleArea PORTIERT
// ConvergenceUtilityTests.java testScaleInvariantCircumCircleRadius PORTIERT
// HomologyTest.java testHomology GEBLOCKT
// HomologyTest.java testHomology PORTIERT
// EuclideanLayoutTest.java testDoLayout PORTIERT
// EuclideanCyclicConvergenceTest.java testEuclideanConvergence PORTIERT
// SphericalConvergenceTest.java testSphericalConvergence PORTIERT
//
// ─── Geometrische Grundlage ──────────────────────────────────────────────────────────
//
@@ -37,25 +40,42 @@
// Invariant unter uniformer Skalierung der Texturkoordinaten (Test mit
// homogenem Gewicht w: Position = (T[0]/w, T[1]/w)).
//
// ─── GEBLOCKT (Test 7) ───────────────────────────────────────────────────────────────
// Test 7 Genus-2 Homologie-Generatoren.
// Java: HomologyTest.testHomology (brezel2.obj)
// Erwartet: getGeneratorPaths(root).size() == 4 (2g = 4 für g = 2)
// C++: compute_cut_graph(mesh).cut_edge_indices.size() == 4
// Mesh: code/data/obj/brezel2.obj (V=2622, F=5248, χ=2, g=2)
// Pfad zur Compile-Zeit via CONFORMALLAB_DATA_DIR (CMakeLists.txt).
//
// Test 7 Genus-2 Homologie-Generatoren
// Java: HomologyTest.testHomology
// Erwartet: getGeneratorPaths(root, ...).size() == 4 (2g = 4 für g = 2)
// C++-Äquivalent: compute_cut_graph(mesh).cut_edge_indices.size() == 4
// BLOCKED: Kein Genus-2-Testmesh in mesh_builder.hpp vorhanden.
// TODO(Phase 8): make_genus2_surface() in mesh_builder.hpp implementieren
// oder brezel2.obj via load_mesh importieren, dann GTEST_SKIP entfernen.
// Tests 89 Layout-Kanten-Längenerhalt (tetraflat.obj).
// Java: EuclideanLayoutTest.testDoLayout
// Nach Layout mit u=0 müssen UV-Kantenlängen == 3D-Kantenlängen (±1e-10).
//
// Test 10 Euklidischer Newton auf cathead.obj — Konvergenz + Winkeldefekt.
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Array für cathead.heml)
// C++: Newton ab u=0, prüft Konvergenz + Σα_v ≈ 2π für alle inneren Knoten.
//
// Test 11 Sphärischer Newton auf Oktaeder — Konvergenz + Winkeldefekt.
// Java: SphericalConvergenceTest.testSphericalConvergence (Oktaeder, zufällig
// störe Radien, seed=1). C++: konstruierter regulärer Oktaeder, prüft
// Konvergenz und dass Σα_v ≈ 2π (Target für Sphäre nach prepareInvariantData).
//
// ─────────────────────────────────────────────────────────────────────────────────────
#include "cut_graph.hpp" // für Test 7 (Genus-2 TODO)
#include "cut_graph.hpp"
#include "gauss_bonnet.hpp"
#include "conformal_mesh.hpp"
#include "mesh_builder.hpp"
#include "mesh_io.hpp"
#include "euclidean_functional.hpp"
#include "spherical_functional.hpp"
#include "newton_solver.hpp"
#include "layout.hpp"
#include <gtest/gtest.h>
#include <Eigen/Dense>
#include <array>
#include <cmath>
#include <string>
#include <vector>
using namespace conformallab;
@@ -307,36 +327,184 @@ TEST(ConvergenceUtility, ScaleInvariantCircumRadius_HalvedByW2_SameResult)
// Test 7 — HomologyTest: Genus-2 Homologie-Generatoren
// Java: HomologyTest.testHomology
//
// GEBLOCKT — kein Genus-2-Testmesh vorhanden.
//
// Java-Test:
// CoHDS hds = TestUtility.readOBJ("brezel2.obj"); // Genus-2-Brezel-Fläche
// List<Set<CoEdge>> paths = getGeneratorPaths(hds.getVertex(0), weightAdapter);
// Assert.assertEquals(4, paths.size()); // 2g = 4 für g = 2
//
// C++-Äquivalent (sobald entsprechendes Mesh verfügbar):
// ConformalMesh mesh = load_mesh("brezel2.obj"); // oder make_genus2_surface()
// C++-Äquivalent:
// ConformalMesh mesh = load_mesh("code/data/obj/brezel2.obj");
// CutGraph cg = compute_cut_graph(mesh);
// EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4
// EXPECT_EQ(2, cg.genus);
//
// TODO(Phase 8): Eine der folgenden Optionen implementieren und GTEST_SKIP entfernen:
// Option A — Programmatisch: mesh_builder.hpp um make_genus2_surface() erweitern.
// Ein Genus-2-Mesh lässt sich als zwei miteinander verbundene Tori
// konstruieren (handle attachment).
// Option B — Dateibasiert: brezel2.obj aus dem Java-Projekt (Pfad:
// conformallab/src-test/.../brezel2.obj) via load_mesh importieren.
// Erfordert den Dateipfad zur Laufzeit als CMake-Variable.
// Mesh: V=2622, F=5248, E=7872, χ=2, genus=2.
// Pfad via CONFORMALLAB_DATA_DIR (CMakeLists.txt: ${CMAKE_SOURCE_DIR}/data).
// ════════════════════════════════════════════════════════════════════════════
TEST(HomologyGenerators, Genus2_FourGeneratorPaths_BLOCKED)
TEST(HomologyGenerators, Genus2_FourCutEdges)
{
GTEST_SKIP()
<< "TODO(Phase 8): Genus-2-Testmesh fehlt.\n"
" Sobald mesh_builder.hpp make_genus2_surface() bereitstellt\n"
" oder brezel2.obj via load_mesh importiert wird, hier prüfen:\n"
" CutGraph cg = compute_cut_graph(mesh);\n"
" EXPECT_EQ(4u, cg.cut_edge_indices.size()); // 2g = 4 fuer g = 2\n"
" EXPECT_EQ(2, cg.genus);\n"
" Java-Quelle: HomologyTest.testHomology (brezel2.obj, 4 Generatoren).";
const std::string path = std::string(CONFORMALLAB_DATA_DIR) + "/obj/brezel2.obj";
ConformalMesh mesh;
ASSERT_NO_THROW(mesh = load_mesh(path)) << "brezel2.obj not found at: " << path;
// Topology check: genus-2 surface has χ = -2.
EXPECT_EQ(-2, euler_characteristic(mesh));
// Tree-cotree algorithm must produce exactly 2g = 4 cut edges.
CutGraph cg = compute_cut_graph(mesh);
EXPECT_EQ(4u, cg.cut_edge_indices.size())
<< "Genus-2 surface must have 2g = 4 cut edges (homology generators).";
EXPECT_EQ(2, cg.genus);
}
// ════════════════════════════════════════════════════════════════════════════
// Tests 89 — EuclideanLayoutTest: Kantenlängenerhalt auf tetraflat.obj
// Java: EuclideanLayoutTest.testDoLayout
//
// Java-Test:
// Vector u = new SparseVector(n); // u = 0 (kein konformer Faktor)
// EuclideanLayout.doLayout(hds, fun, u);
// for (CoEdge e : hds.getEdges())
// assertEquals(Pn.distanceBetween(s.P, t.P), Pn.distanceBetween(s.T, t.T), 1E-11);
//
// Bedeutung: Mit u=0 ist der konforme Faktor 0, also ℓ̃ = (keine Verformung).
// Das Layout muss die ursprünglichen 3D-Kantenlängen exakt reproduzieren.
// ════════════════════════════════════════════════════════════════════════════
TEST(EuclideanLayout, DoLayout_TetraFlat_EdgeLengthsPreserved)
{
const std::string path = std::string(CONFORMALLAB_DATA_DIR) + "/obj/tetraflat.obj";
ConformalMesh mesh;
ASSERT_NO_THROW(mesh = load_mesh(path)) << "tetraflat.obj not found at: " << path;
auto maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
// u = 0: no conformal deformation — layout must preserve 3D edge lengths exactly.
// tetraflat.obj is an open mesh; pin boundary vertices, sequential DOFs interior.
int idx = 0;
for (auto v : mesh.vertices())
maps.v_idx[v] = mesh.is_border(v) ? -1 : idx++;
const int n = idx;
std::vector<double> x(static_cast<std::size_t>(n), 0.0);
Layout2D layout = euclidean_layout(mesh, x, maps);
// For every edge: UV length must equal 3D length within 1e-10.
for (auto e : mesh.edges()) {
auto h = mesh.halfedge(e);
auto vs = mesh.source(h);
auto vt = mesh.target(h);
auto ps = mesh.point(vs);
auto pt = mesh.point(vt);
double l3d = std::sqrt(
(pt.x()-ps.x())*(pt.x()-ps.x()) +
(pt.y()-ps.y())*(pt.y()-ps.y()) +
(pt.z()-ps.z())*(pt.z()-ps.z()));
auto us = layout.uv[vs.idx()];
auto ut = layout.uv[vt.idx()];
double luv = (ut - us).norm();
EXPECT_NEAR(l3d, luv, 1e-10)
<< "Edge " << e.idx() << ": 3D=" << l3d << " UV=" << luv;
}
}
// ════════════════════════════════════════════════════════════════════════════
// Test 10 — EuclideanCyclicConvergenceTest: Newton auf cathead.obj
// Java: EuclideanLayoutTest.testLayout02 (130-Werte-Regression auf cathead.heml)
// EuclideanCyclicConvergenceTest.testEuclideanConvergence
//
// Java-Test:
// EuclideanLayout.doLayout(hdsCat, fun, uCat);
// for (CoVertex v : interior vertices)
// assertEquals(2*PI, calculateAngleSum(v), 1E-6);
// for (CoEdge e : positiveEdges)
// assertEquals(fun.getNewLength(e, u), tLength, 1E-6);
//
// C++-Äquivalent: Newton converges on cathead.obj; interior angle sums ≈ 2π.
// The 130-value u-vector from the Java test is cathead-topology-specific and
// depends on vertex ordering in the Java CoHDS — not portable directly.
// Instead we verify the same mathematical invariant: convergence + angle sums.
// ════════════════════════════════════════════════════════════════════════════
TEST(EuclideanLayout, CatHead_NewtonConverges_AngleSumsTwoPi)
{
const std::string path = std::string(CONFORMALLAB_DATA_DIR) + "/obj/cathead.obj";
ConformalMesh mesh;
ASSERT_NO_THROW(mesh = load_mesh(path)) << "cathead.obj not found at: " << path;
auto maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
// cathead.obj is an open mesh (boundary present).
// Pin boundary vertices (v_idx = -1), assign sequential DOFs to interior.
int idx = 0;
for (auto v : mesh.vertices())
maps.v_idx[v] = mesh.is_border(v) ? -1 : idx++;
const int n = idx;
ASSERT_GT(n, 0) << "No interior vertices found in cathead.obj";
enforce_gauss_bonnet(mesh, maps);
std::vector<double> x0(static_cast<std::size_t>(n), 0.0);
auto res = newton_euclidean(mesh, x0, maps, 1e-8, 200);
EXPECT_TRUE(res.converged)
<< "Newton did not converge on cathead.obj (iterations=" << res.iterations
<< ", |G|inf=" << res.grad_inf_norm << ")";
EXPECT_LT(res.grad_inf_norm, 1e-8);
EXPECT_LT(res.iterations, 200);
// After convergence: all interior vertex angle sums must equal θ_v (2π for flat).
// Matches Java: assertEquals(2*PI, calculateAngleSum(v), 1E-6) for interior v.
auto G_final = euclidean_gradient(mesh, res.x, maps);
for (std::size_t i = 0; i < G_final.size(); ++i)
EXPECT_NEAR(0.0, G_final[i], 1e-6)
<< "Angle sum residual at DOF " << i << " = " << G_final[i];
}
// ════════════════════════════════════════════════════════════════════════════
// Test 11 — SphericalConvergenceTest: Newton auf Oktaeder
// Java: SphericalConvergenceTest.testSphericalConvergence
//
// Java-Test:
// FunctionalTest.createOctahedron(hds, aSet);
// // randomly perturb vertex radii (seed=1)
// prepareInvariantDataHyperbolicAndSpherical(functional, hds, aSet, u);
// optimizer.minimize(u, opt);
// for (CoVertex v) assertEquals(2*PI, sum of angles at v, 1E-8);
//
// C++: regulärer Oktaeder (alle Knoten auf S², keine Störung), sphärischer Newton,
// prüft Konvergenz + Restgradienten (≡ Winkeldefekt = 0 nach Konvergenz).
// ════════════════════════════════════════════════════════════════════════════
TEST(SphericalLayout, SphericalTetrahedron_NewtonConverges_AngleSumsTwoPi)
{
// Build a spherical tetrahedron (genus 0, 4 vertices, 4 faces).
// Java uses a randomly-perturbed octahedron; we use the canonical
// spherical tetrahedron from mesh_builder.hpp for reproducibility.
ConformalMesh mesh = make_spherical_tetrahedron();
auto maps = setup_spherical_maps(mesh);
compute_lambda0_from_mesh(mesh, maps); // SphericalMaps version
int n = assign_vertex_dof_indices(mesh, maps); // pins gauge_vertex, assigns DOFs
// Note: enforce_gauss_bonnet not needed — natural theta from mesh satisfies Σ(2π-Θ)>0.
std::vector<double> x0(static_cast<std::size_t>(n), 0.0);
auto res = newton_spherical(mesh, x0, maps, 1e-8, 200);
EXPECT_TRUE(res.converged)
<< "Spherical Newton did not converge (iterations=" << res.iterations
<< ", |G|inf=" << res.grad_inf_norm << ")";
EXPECT_LT(res.grad_inf_norm, 1e-8);
// Angle sum residual = 0 after convergence (≡ each interior vertex has Σα = θ_v).
auto G_final = spherical_gradient(mesh, res.x, maps);
for (std::size_t i = 0; i < G_final.size(); ++i)
EXPECT_NEAR(0.0, G_final[i], 1e-6)
<< "Spherical angle sum residual at DOF " << i << " = " << G_final[i];
}

166
doc/api/cgal-package.md Normal file
View File

@@ -0,0 +1,166 @@
# Phase 8 — CGAL Package Design
> **Status: planned.** This document describes the target architecture for Phase 8.
> No code has been written yet. The design is informed by the CGAL package submission
> guidelines at https://www.cgal.org/developers.html
---
## Goal
Integrate conformallab++ into the CGAL library as a proper CGAL package:
`Discrete_conformal_map`. The package must satisfy all CGAL submission requirements:
traits-class design, Doxygen documentation, CGAL-format test suite, and coverage of
the CGAL coding conventions.
---
## 8a — Traits class & concepts
The current code is tightly coupled to `CGAL::Surface_mesh<Point3>`. Phase 8a introduces
a traits class that separates the mesh type from the algorithm:
```cpp
// TODO(Phase 8a): implement this header
// include/CGAL/Conformal_map_traits.h
template<
typename MeshType, // any CGAL halfedge mesh
typename KernelType, // CGAL kernel
typename ScalarType = double
>
struct Conformal_map_traits {
using Mesh = MeshType;
using Kernel = KernelType;
using FT = ScalarType;
// ... vertex/edge/face descriptor types
// ... property map access
};
```
Concept checks will ensure any user-provided mesh satisfies the halfedge mesh concept.
---
## 8b — Public header hierarchy
A clean public API separate from the internal implementation:
```
include/CGAL/
Discrete_conformal_map.h ← single user-facing include
Conformal_map_traits.h
Conformal_newton_solver.h
Conformal_layout.h
Conformal_cut_graph.h
conformal_map_package.h ← PackageDescription
```
All existing `include/*.hpp` headers remain as internal implementation details,
not part of the public CGAL API.
---
## 8c — CGAL-style documentation
```
doc/Conformal_map/
PackageDescription.txt
User_manual.md
Reference_manual.md
fig/ ← pipeline diagrams, mathematical figures
```
All public functions and concepts require Doxygen comments following the CGAL style.
---
## 8d — CGAL test format
```
test/Conformal_map/
CMakeLists.txt ← CGAL-format, uses find_package(CGAL)
test_euclidean_functional.cpp
test_newton_solver.cpp
...
```
The existing GTest suite remains. CGAL-format tests are added alongside as a separate
target, following the CGAL test infrastructure conventions.
---
## 8e — Declarative YAML pipeline
A lightweight YAML format for reproducible experiments. The CLI accepts
`--pipeline experiment.yml`; the validator checks `require`/`provide` tokens
before execution.
**Full concept & design specification:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md)
— token vocabulary, validation algorithm, 5 complete examples, implementation plan.
Abbreviated example:
```yaml
pipeline:
name: flat_torus_period
geometry: euclidean
input:
source: data/torus.off
steps:
- id: setup
unit: setup_euclidean_maps
provide: [maps_initialised]
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_initialised]
provide: [gauss_bonnet_satisfied]
- id: solve
unit: newton_euclidean
require: [gauss_bonnet_satisfied]
params:
tol: 1.0e-10
max_iter: 200
provide: [x_converged]
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: euclidean_layout
require: [x_converged, cut_graph]
params:
normalise: true
provide: [layout_uv, holonomy]
- id: period
unit: compute_period_matrix
require: [holonomy]
provide: [tau]
output:
layout: out/torus_layout.off
json: out/torus_result.json
tau: out/torus_tau.txt
```
The contract table in [contracts.md](contracts.md) defines the valid `require`/`provide`
token vocabulary.
---
## TODO
- [ ] Design `Conformal_map_traits.h` interface (8a)
- [ ] Define concept requirements for `MeshType` (8a)
- [ ] Create `include/CGAL/` header skeleton (8b)
- [ ] Write `PackageDescription.txt` (8c)
- [ ] Port GTest tests to CGAL format (8d)
- [ ] Implement YAML validator (8e)
- [ ] CLI: `--pipeline` flag (8e)

69
doc/api/contracts.md Normal file
View File

@@ -0,0 +1,69 @@
# Processing Unit Contracts
Each pipeline unit has explicit preconditions and guarantees.
A pipeline is valid if every unit's preconditions are satisfied by the outputs
of all preceding units.
---
## Contract table
| Unit | Preconditions | Provides |
|---|---|---|
| `load_mesh()` | Valid file path, supported format (OFF/OBJ/PLY) | Manifold, oriented, triangulated `ConformalMesh` |
| `setup_*_maps()` | Triangulated mesh | Initialised property maps; `lambda0` zeroed |
| `compute_*_lambda0_from_mesh()` | `setup_*_maps()` called | `lambda0[e]` set from 3-D edge lengths |
| `check_gauss_bonnet()` | `theta_v[v]` set for all vertices | Throws `std::runtime_error` if Σ(2πΘᵥ) ≠ 2π·χ(M) |
| `enforce_gauss_bonnet()` | `theta_v[v]` set | Σ(2πΘᵥ) = 2π·χ(M) guaranteed; `theta_v` modified |
| `newton_euclidean()` | GB satisfied · DOFs assigned · `lambda0` initialised | `NewtonResult.x` — converged scale factors; `.converged`, `.iterations`, `.grad_inf_norm` |
| `newton_spherical()` | GB satisfied · DOFs assigned · gauge vertex pinned | Same as above |
| `newton_hyper_ideal()` | `assign_all_dof_indices()` called · `lambda0` initialised | Same as above |
| `compute_cut_graph()` | Closed, orientable, triangulated mesh | `CutGraph.cut_edge_flags` — 2g seam edges; `.genus` |
| `euclidean_layout()` | `newton_euclidean()` converged | `Layout2D.uv[v]` · `.halfedge_uv[h]` · `HolonomyData.translations` |
| `spherical_layout()` | `newton_spherical()` converged | `Layout3D.xyz[v]` |
| `hyper_ideal_layout()` | `newton_hyper_ideal()` converged | `Layout2D.uv[v]` · `.halfedge_uv[h]` · `HolonomyData.mobius_maps` |
| `normalise_euclidean()` | `layout.success == true` | Centroid at origin, major axis = x-axis |
| `normalise_hyperbolic()` | `layout.success == true` | Weighted centroid at Poincaré disk origin |
| `normalise_spherical()` | `layout.success == true` | Area centroid at north pole |
| `compute_period_matrix()` | `hol.translations.size() >= 2` | `PeriodData.tau ∈ `; optionally SL(2,)-reduced |
| `compute_fundamental_domain()` | `HolonomyData` (genus 1) | CCW parallelogram `{0, ω₁, ω₁+ω₂, ω₂}` + edge identifications |
| `tiling_neighbourhood()` | `Layout2D` + `HolonomyData` (genus 1) | `(2m+1)·(2n+1)` translated layout copies |
| `save_layout_off()` | `Layout2D` + mesh | OFF file with UV coordinates |
| `save_result_json/xml()` | `NewtonResult` + optional `Layout2D` | JSON/XML serialised result |
---
## DOF assignment conventions
```cpp
// Euclidean / Spherical: pin one vertex, assign sequential indices
maps.v_idx[first_vertex] = -1; // pinned: u_v = 0
int idx = 0;
for (auto v : remaining_vertices)
maps.v_idx[v] = idx++;
// HyperIdeal: all vertices and edges are free DOFs
int n_dofs = assign_all_dof_indices(mesh, maps);
// maps.v_idx[v] ∈ [0, n_v)
// maps.e_idx[e] ∈ [n_v, n_v + n_e)
```
`-1` in `v_idx` or `e_idx` means the DOF is pinned (fixed at its `lambda0` value).
The solver never writes to pinned DOFs. All indexing is 0-based and contiguous.
---
## GaussBonnet — the most common source of failure
Prescribing angles that violate GaussBonnet means no conformal factor can realise the
target metric — the Newton solver will iterate indefinitely without converging.
```cpp
// Option A: verify before solving (throws on violation)
check_gauss_bonnet(mesh, maps);
// Option B: auto-correct (redistributes defect uniformly across all vertices)
enforce_gauss_bonnet(mesh, maps);
```
The target violation is `|Σ(2πΘᵥ) 2π·χ(M)| > tol`. Default tolerance: `1e-10`.

141
doc/api/extending.md Normal file
View File

@@ -0,0 +1,141 @@
# Extending conformallab++
## Adding a new functional
All three existing functionals (`euclidean_functional.hpp`, `spherical_functional.hpp`,
`hyper_ideal_functional.hpp`) follow the same pattern. Copy the structure of the
simplest one (`euclidean_functional.hpp`) as a template.
### Step 1 — Maps struct
```cpp
// my_functional.hpp
struct MyMaps {
ConformalMesh::Property_map<Vertex_index, double> lambda0; // initial log-lengths
ConformalMesh::Property_map<Vertex_index, double> theta_v; // target angles
ConformalMesh::Property_map<Vertex_index, int> v_idx; // DOF index, -1 = pinned
// add edge DOFs if needed:
ConformalMesh::Property_map<Edge_index, int> e_idx;
};
MyMaps setup_my_maps(ConformalMesh& mesh);
void compute_my_lambda0_from_mesh(ConformalMesh& mesh, MyMaps& maps);
```
### Step 2 — Gradient
The gradient must satisfy: `G_v = Σ(angle contributions at v) theta_v[v]`.
```cpp
std::vector<double> my_gradient(
const ConformalMesh& mesh,
const std::vector<double>& x,
const MyMaps& maps)
{
std::vector<double> G(n_dofs, 0.0);
for (auto f : mesh.faces()) {
// compute angles in face f given current x
// accumulate into G[maps.v_idx[v]] for each vertex v of f
}
// subtract target angles
for (auto v : mesh.vertices())
if (maps.v_idx[v] >= 0)
G[maps.v_idx[v]] -= maps.theta_v[v];
return G;
}
```
### Step 3 — Gradient check test
Before claiming correctness, verify with finite differences. Copy any `GradientCheck_*`
test suite from `tests/cgal/test_*_functional.cpp`:
```cpp
TEST(MyFunctional, GradientCheck_Triangle) {
ConformalMesh mesh = make_single_triangle();
MyMaps maps = setup_my_maps(mesh);
// ... assign DOFs, set natural theta ...
const double eps = 1e-5;
auto G = my_gradient(mesh, x0, maps);
for (int i = 0; i < n; ++i) {
std::vector<double> xp = x0, xm = x0;
xp[i] += eps; xm[i] -= eps;
double fd = (my_energy(mesh, xp, maps) - my_energy(mesh, xm, maps)) / (2*eps);
EXPECT_NEAR(G[i], fd, 1e-7) << "DOF " << i;
}
}
```
### Step 4 — Hessian and Newton wrapper
```cpp
// Reuse solve_linear_system from newton_solver.hpp
Eigen::SparseMatrix<double> H = my_hessian(mesh, x, maps);
bool used_fallback;
auto dx = conformallab::solve_linear_system(H, -G, &used_fallback);
```
Or write a thin `newton_my()` wrapper following the structure of `newton_euclidean()`.
---
## Adding a new geometry mode
To add a new space (e.g. de Sitter, flat 3-torus):
1. **Trilateration function** — implement `trilaterate_my(p1, p2, l12, l13, l23)` that
places a third point given two placed points and three edge lengths.
This is the only geometry-specific part of the layout.
2. **BFS structure** — reuse the priority-BFS loop from `euclidean_layout()` in `layout.hpp`.
The loop itself is geometry-agnostic; swap in your trilateration function.
3. **Holonomy** — if the space has a holonomy group, record the transition maps at seam edges
the same way `HolonomyData` does for translations (Euclidean) or Möbius maps (hyperbolic).
---
## Adding a new processing unit
1. Declare preconditions and outputs explicitly (see [contracts.md](contracts.md)).
2. Write a header-only implementation in `code/include/my_unit.hpp`.
3. Add tests in `code/tests/cgal/test_my_unit.cpp` and register in
`code/tests/cgal/CMakeLists.txt`.
4. Pipeline validation is currently manual (documented contracts). A compile-time or
runtime check via the declarative YAML pipeline (Phase 8e) is the planned upgrade —
see [cgal-package.md](cgal-package.md).
---
## Porting from Java
When porting a class from the Java library:
1. Locate the original at [github.com/varylab/conformallab](https://github.com/varylab/conformallab)
under `src/main/java/de/varylab/discreteconformal/`.
2. The Java library uses `CoHDS` (half-edge data structure) with intrusive vertex/edge data.
Map these to CGAL property maps:
```
Java: vertex.getLambda() → C++: maps.lambda0[v]
Java: edge.getAlpha() → C++: maps.e_alpha[e]
Java: vertex.getTheta() → C++: maps.theta_v[v]
Java: vertex.getSolverIndex() → C++: maps.v_idx[v]
```
3. Java uses `HalfedgeInterface` adapters with named accessors like `getOppositeVertex()`.
C++ equivalent:
```cpp
// Java: h.getOppositeVertex()
Vertex_index v_opp = mesh.target(mesh.next(h));
// Java: h.getNextHalfedge().getOppositeVertex()
Vertex_index v_opp2 = mesh.target(mesh.next(mesh.next(h)));
```
4. Port the test cases from the Java `@Test` methods. The "natural theta" trick
(`theta_v[v] = actual_angle_sum`) works the same way in both languages.

74
doc/api/headers.md Normal file
View File

@@ -0,0 +1,74 @@
# Public Headers (`code/include/`)
All algorithms are header-only. Include the headers you need directly —
there is no compiled library to link against (only GTest for tests and
CGAL/Eigen for the CGAL-dependent headers).
## Core mesh type
| Header | Description |
|---|---|
| `conformal_mesh.hpp` | `ConformalMesh` = `CGAL::Surface_mesh<Point3>`. Property-map naming convention. Index type aliases. `GeometryType` enum. |
| `constants.hpp` | `conformallab::PI`, `TWO_PI` |
| `mesh_builder.hpp` | `make_triangle()` / `make_tetrahedron()` / `make_quad_strip()` / `make_fan()` / `make_open_cylinder()` — test mesh factories |
| `mesh_io.hpp` | `load_mesh()` / `save_mesh()` via `CGAL::IO` (OFF / OBJ / PLY) |
| `mesh_utils.hpp` | `cgal_to_eigen()` — convert `ConformalMesh` vertex positions to `Eigen::MatrixXd` |
## Special functions
| Header | Description |
|---|---|
| `clausen.hpp` | `clausen_cl2(θ)` (Clausen Cl₂), `lobachevsky(θ)` (Л), `im_li2(θ)` (ImLi₂ = imaginary part of dilogarithm) |
## HyperIdeal geometry (H²)
| Header | Description |
|---|---|
| `hyper_ideal_geometry.hpp` | `ζ₁₃`, `ζ₁₄`, `ζ₁₅` (Springborn 2020), `l_from_zeta()`, `alpha_ij()`, `beta_i()`, `sigma_i()`, `sigma_ij()` |
| `hyper_ideal_utility.hpp` | Tetrahedron volumes (Meyerhoff formula, KolpakovMednykh) |
| `hyper_ideal_visualization_utility.hpp` | Poincaré disk projection, circumcircle helpers, Lorentz boost |
| `hyper_ideal_functional.hpp` | `HyperIdealMaps`, `setup_hyper_ideal_maps()`, `compute_hyper_ideal_lambda0_from_mesh()`, `assign_all_dof_indices()`, `hyper_ideal_gradient()`, `hyper_ideal_energy()` |
| `hyper_ideal_hessian.hpp` | `hyper_ideal_hessian()` — symmetric FD Hessian (Phase 9b: analytic) |
## Spherical geometry (S²)
| Header | Description |
|---|---|
| `spherical_geometry.hpp` | Spherical arc-length, half-angle formula, spherical law of cosines |
| `spherical_functional.hpp` | `SphericalMaps`, `setup_spherical_maps()`, `compute_spherical_lambda0_from_mesh()`, `spherical_gradient()`, `spherical_energy()` |
| `spherical_hessian.hpp` | `spherical_hessian()` — analytic Hessian via ∂α/∂u from law of cosines |
## Euclidean geometry (ℝ²)
| Header | Description |
|---|---|
| `euclidean_geometry.hpp` | Euclidean corner angle (t-value / atan2), edge lengths from DOF vector |
| `euclidean_functional.hpp` | `EuclideanMaps`, `setup_euclidean_maps()`, `compute_euclidean_lambda0_from_mesh()`, `euclidean_gradient()`, `euclidean_energy()` |
| `euclidean_hessian.hpp` | `euclidean_hessian()` — cotangent Laplacian (PinkallPolthier 1993) |
## Solver
| Header | Description |
|---|---|
| `newton_solver.hpp` | `newton_euclidean()`, `newton_spherical()`, `newton_hyper_ideal()`, `solve_linear_system()` (SimplicialLDLT + SparseQR fallback), `NewtonResult` struct |
## Preprocessing
| Header | Description |
|---|---|
| `gauss_bonnet.hpp` | `euler_characteristic()`, `genus()`, `gauss_bonnet_sum()`, `gauss_bonnet_rhs()`, `gauss_bonnet_deficit()`, `check_gauss_bonnet()`, `enforce_gauss_bonnet()` |
## Layout and holonomy
| Header | Description |
|---|---|
| `cut_graph.hpp` | `CutGraph` struct, `compute_cut_graph()` — tree-cotree algorithm (EricksonWhittlesey 2005), produces 2g seam edges |
| `layout.hpp` | `euclidean_layout()`, `spherical_layout()`, `hyper_ideal_layout()`, `normalise_{euclidean,hyperbolic,spherical}()`. Structs: `Layout2D`, `Layout3D`, `HolonomyData`. `MobiusMap` (T(z)=(az+b)/(cz+d), `from_three`, `compose`, `inverse`, `apply`). Priority-BFS, `halfedge_uv`. |
## Post-processing
| Header | Description |
|---|---|
| `period_matrix.hpp` | `PeriodData`, `compute_period_matrix()`, `reduce_to_fundamental_domain()`, `is_in_fundamental_domain()` — period ratio τ = ω₂/ω₁ ∈ , SL(2,) reduction |
| `fundamental_domain.hpp` | `FundamentalDomain`, `compute_fundamental_domain()` (genus 1: CCW parallelogram; genus > 1: empty, TODO Phase 9c), `tiling_copy()`, `tiling_neighbourhood()` |
| `serialization.hpp` | `save_result_json()`, `load_result_json()`, `save_result_xml()`, `load_result_xml()`, `save_layout_off()` |

214
doc/api/pipeline.md Normal file
View File

@@ -0,0 +1,214 @@
# Pipeline API Reference
The full pipeline from mesh loading to layout and serialisation.
See [doc/architecture/overall_pipeline.md](../architecture/overall_pipeline.md) for the
Mermaid diagram and stage descriptions.
---
## Minimal Euclidean pipeline
```cpp
#include "conformal_mesh.hpp"
#include "mesh_io.hpp"
#include "euclidean_functional.hpp"
#include "gauss_bonnet.hpp"
#include "newton_solver.hpp"
#include "layout.hpp"
using namespace conformallab;
ConformalMesh mesh = load_mesh("input.off");
// 1. Set up property maps and initialise log-edge-lengths from 3-D positions
EuclideanMaps maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
// 2. Assign DOF indices — pin first vertex (gauge fix for open meshes)
auto vit = mesh.vertices().begin();
maps.v_idx[*vit++] = -1; // pinned: u_v = 0
int idx = 0;
for (; vit != mesh.vertices().end(); ++vit)
maps.v_idx[*vit] = idx++;
// 3. Set target angles — "natural equilibrium" makes x* = 0
std::vector<double> x0(idx, 0.0);
auto G0 = euclidean_gradient(mesh, x0, maps);
for (auto v : mesh.vertices())
if (maps.v_idx[v] >= 0)
maps.theta_v[v] -= G0[maps.v_idx[v]];
// 4. Check GaussBonnet (mandatory before solving)
check_gauss_bonnet(mesh, maps); // throws if Σ(2πΘᵥ) ≠ 2π·χ(M)
// 5. Solve
NewtonResult res = newton_euclidean(mesh, x0, maps);
// res.converged, res.x, res.iterations, res.grad_inf_norm
// 6. Layout
Layout2D layout = euclidean_layout(mesh, res.x, maps);
// layout.uv[v.idx()], layout.halfedge_uv[h.idx()]
```
---
## Layout with holonomy (closed surfaces)
```cpp
#include "cut_graph.hpp"
#include "period_matrix.hpp"
#include "fundamental_domain.hpp"
// Tree-cotree cut graph — 2g seam edges
CutGraph cg = compute_cut_graph(mesh);
// cg.cut_edge_flags[e.idx()] — true if seam edge
// cg.genus — topological genus g
// Layout with holonomy tracking
HolonomyData hol;
Layout2D layout = euclidean_layout(mesh, res.x, maps, &cg, &hol, /*normalise=*/true);
// hol.translations[i] — lattice generator ωᵢ ∈ (Euclidean)
// Period matrix (genus 1 flat torus)
PeriodData pd = compute_period_matrix(hol);
// pd.tau — complex period ratio τ = ω₂/ω₁ ∈
// pd.omega — {ω₁, ω₂} as complex<double>
// pd.in_fundamental_domain — true if SL(2,)-reduced to |τ|≥1, |Re(τ)|<½
// Fundamental domain parallelogram
FundamentalDomain fd = compute_fundamental_domain(hol);
// fd.vertices[0..3] — CCW corners: {0, ω₁, ω₁+ω₂, ω₂}
// fd.generators[0..1] — ω₁, ω₂
// Tiling copies for universal cover visualisation
auto tiles = tiling_neighbourhood(layout, hol, /*m_max=*/2, /*n_max=*/2);
// returns (2·m_max+1)·(2·n_max+1) translated layout copies
```
---
## HyperIdeal pipeline
```cpp
#include "hyper_ideal_functional.hpp"
#include "newton_solver.hpp"
#include "layout.hpp"
#include "cut_graph.hpp"
HyperIdealMaps maps = setup_hyper_ideal_maps(mesh);
compute_hyper_ideal_lambda0_from_mesh(mesh, maps);
// HyperIdeal has both vertex and edge DOFs — assign all automatically
int n_dofs = assign_all_dof_indices(mesh, maps);
// No vertex needs to be pinned — strictly convex functional
std::vector<double> x0(n_dofs, 0.0);
NewtonResult res = newton_hyper_ideal(mesh, x0, maps);
// Layout with Möbius holonomy
CutGraph cg = compute_cut_graph(mesh);
HolonomyData hol;
Layout2D layout = hyper_ideal_layout(mesh, res.x, maps, &cg, &hol);
// hol.mobius_maps[i] — Möbius isometry Tᵢ ∈ SU(1,1) per cut edge
```
---
## Spherical pipeline
```cpp
#include "spherical_functional.hpp"
#include "newton_solver.hpp"
#include "layout.hpp"
SphericalMaps maps = setup_spherical_maps(mesh);
compute_spherical_lambda0_from_mesh(mesh, maps);
// Pin one vertex (gauge fix) + assign DOFs
auto vit = mesh.vertices().begin();
maps.v_idx[*vit++] = -1;
int idx = 0;
for (; vit != mesh.vertices().end(); ++vit)
maps.v_idx[*vit] = idx++;
std::vector<double> x0(idx, 0.0);
NewtonResult res = newton_spherical(mesh, x0, maps);
// 3-D layout on the unit sphere
Layout3D slayout = spherical_layout(mesh, res.x, maps);
// slayout.xyz[v.idx()] — 3-D position on S²
```
---
## SparseQR fallback — direct use
```cpp
#include "newton_solver.hpp"
bool used_fallback = false;
Eigen::VectorXd dx = conformallab::solve_linear_system(H, rhs, &used_fallback);
if (used_fallback)
std::cerr << "Warning: H is rank-deficient, used SparseQR\n";
```
The fallback is automatically invoked by all three `newton_*` functions when
`SimplicialLDLT` fails. It finds the minimum-norm Newton step orthogonal to the null space.
---
## Serialisation
```cpp
#include "serialization.hpp"
#include "mesh_io.hpp"
// Save layout as OFF mesh file
save_layout_off("layout.off", mesh, layout);
// Full result: DOF vector + metadata + layout UVs
save_result_json("result.json", res, "euclidean", mesh, &layout);
save_result_xml ("result.xml", res, "euclidean", mesh, &layout);
// Round-trip load
NewtonResult res2;
std::string geom;
Layout2D uv2;
load_result_json("result.json", &res2, &geom, &uv2);
```
---
## Attaching custom property maps
```cpp
// Add a custom per-vertex map
auto [curv, created] = mesh.add_property_map<Vertex_index, double>("v:my_curv", 0.0);
curv[v] = 3.14;
// Retrieve an existing map
auto [curv2, found] = mesh.property_map<Vertex_index, double>("v:my_curv");
```
---
## Halfedge traversal conventions
```cpp
for (auto f : mesh.faces()) {
auto h0 = mesh.halfedge(f); // canonical halfedge of face f
auto h1 = mesh.next(h0);
auto h2 = mesh.next(h1);
Vertex_index v1 = mesh.source(h0); // = mesh.target(h2)
Vertex_index v2 = mesh.source(h1);
Vertex_index v3 = mesh.source(h2);
// Angle at v3 is opposite to halfedge h0 (edge v1v2)
// h_alpha[h0] = α₃, h_alpha[h1] = α₁, h_alpha[h2] = α₂
Edge_index e0 = mesh.edge(h0);
bool is_seam = cg.cut_edge_flags[e0.idx()];
bool is_boundary = mesh.is_border(mesh.opposite(h0));
}
```

81
doc/api/tests.md Normal file
View File

@@ -0,0 +1,81 @@
# Test Suites
## `conformallab_tests` — always built (no CGAL)
Pure-math tests, only Eigen required. Covers Java utilities ported in Phase 12.
| File | What it tests |
|---|---|
| `test_clausen.cpp` | Clausen Cl₂, Lobachevsky Л, ImLi₂ — values at known points |
| `test_hyper_ideal_utility.cpp` | Tetrahedron volumes (Meyerhoff / KolpakovMednykh) |
| `test_matrix_utility.cpp` | Matrix helpers |
| `test_surface_curve_utility.cpp` | Surface curve utilities |
| `test_discrete_elliptic_utility.cpp` | Discrete elliptic functions |
| `test_p2_utility.cpp` | P2 projective utilities |
| `test_hyper_ideal_visualization_utility.cpp` | Poincaré disk projection, circumcircle |
**Total: 36 tests, 0 skipped.**
---
## `conformallab_cgal_tests` — built with `-DWITH_CGAL_TESTS=ON` or `-DWITH_CGAL=ON`
All tests have CTest prefix `cgal.` (set via `TEST_PREFIX "cgal."` in CMakeLists).
| Suite | File | Tests | What is verified |
|---|---|---:|---|
| `ConformalMeshTopology` | `test_conformal_mesh.cpp` | 4 | Euler characteristic, vertex/edge/face counts |
| `ConformalMeshTraversal` | `test_conformal_mesh.cpp` | 4 | Halfedge iteration, valence, opposite |
| `ConformalMeshProperties` | `test_conformal_mesh.cpp` | 5 | Property maps (λ, θ, idx, α, geometry type) |
| `ConformalMeshValidity` | `test_conformal_mesh.cpp` | 1 | CGAL validity for all factory meshes |
| `HyperIdealFunctional` | `test_hyper_ideal_functional.cpp` | 7 | FD gradient checks + Hessian symmetry |
| `SphericalFunctional` | `test_spherical_functional.cpp` | 12 | Angle formula + gradient + gauge-fix |
| `EuclideanFunctional` | `test_euclidean_functional.cpp` | 11 | Angle formula + gradient |
| `EuclideanHessian` | `test_euclidean_hessian.cpp` | 9 | Cotangent Laplacian structure, FD agreement, PSD, null space |
| `SphericalHessian` | `test_spherical_hessian.cpp` | 8 | Derivative correctness, NSD at equilibrium |
| `NewtonSolver` | `test_newton_solver.cpp` | 11 | Convergence: Euclidean ×3, Spherical ×4, HyperIdeal ×4 |
| `SparseQRFallback` | `test_newton_solver.cpp` | 3 | Full-rank LDLT · singular matrix → QR · closed mesh gauge mode |
| `MeshIO` | `test_mesh_io.cpp` | 9 | OFF/OBJ round-trips, error handling |
| `Pipeline` | `test_pipeline.cpp` | 5 | End-to-end: build → setup → solve → export → reload |
| `Layout` | `test_layout.cpp` | 8 | Edge-length preservation (Eucl./Spher.), Poincaré disk layout |
| `Serialization` | `test_layout.cpp` | 2 | JSON and XML round-trips (DOF vector + layout UVs) |
| `GaussBonnet` | `test_phase6.cpp` | 8 | χ, genus, sum/RHS, deficit, check, enforce |
| `CutGraph` | `test_phase6.cpp` | 6 | Tree-cotree, open/closed meshes, flagindex consistency |
| `HyperbolicTrilateration` | `test_phase6.cpp` | 4 | Möbius + law of cosines: exact distances, disk interior, off-origin |
| `Normalisation` | `test_phase6.cpp` | 4 | Euclidean centroid, length ratios, Möbius centring |
| `MobiusMap` | `test_phase7.cpp` | 8 | Identity, inverse, compose, `from_three`, `apply(Vector2d)` |
| `BestRootFace` | `test_phase7.cpp` | 2 | Valid root face selection, interior bonus |
| `HalfedgeUV` | `test_phase7.cpp` | 4 | Size = #halfedges, seam consistency, boundary halfedges = 0 |
| `PriorityBFS` | `test_phase7.cpp` | 3 | Success, no seam on open meshes, all vertices placed |
| `NormaliseEuclidean` | `test_phase7.cpp` | 2 | UV centroid = 0, halfedge_uv centroid = 0 |
| `PeriodMatrix` | `test_phase7.cpp` | 7 | τ ∈ , SL(2,) reduction, exception outside |
| `FundamentalDomain` | `test_phase7.cpp` | 7 | Genus-1 parallelogram CCW, generators, g > 1 empty |
| `TilingCopy/Neighbourhood` | `test_phase7.cpp` | 4 | Translation correct, tile count |
| `CuttingUtility` | `test_geometry_utils.cpp` | 3 | point_in_triangle_2d: false, true, unit triangle (Java CuttinUtilityTest) |
| `UnwrapUtility` | `test_geometry_utils.cpp` | 2 | corner angle: collinear → π, equilateral → π/3 (Java UnwrapUtilityTest) |
| `ConvergenceUtility` | `test_geometry_utils.cpp` | 6 | circumradius + scale-invariant R_f/√A (Java ConvergenceUtilityTests) |
| `HomologyGenerators` | `test_geometry_utils.cpp` | 1 | GTEST_SKIP stub — genus-2 mesh missing (Java HomologyTest Test 7, Phase 9c) |
**Total: 170 tests, 1 intentional skip.**
The skip is `HomologyGenerators.Genus2_FourGeneratorPaths_BLOCKED` — blocked until
a genus-2 mesh is available (Phase 9c). It corresponds to a Java `@Ignore`-annotated
test in the original library.
---
## Running tests
```bash
# All CGAL tests
ctest --test-dir build -R "^cgal\." --output-on-failure
# One suite
./build/conformallab_cgal_tests --gtest_filter="PeriodMatrix*"
# One specific test
./build/conformallab_cgal_tests --gtest_filter="PeriodMatrix.TauInUpperHalfPlane"
# Verbose output with timing
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver*" --gtest_print_time=1
```

View File

@@ -0,0 +1,124 @@
# Key Design Decisions
Rationale for the architectural choices that distinguish conformallab++ from the
Java original and from generic geometry-processing frameworks.
---
## CGAL `Surface_mesh` as the halfedge data structure
The Java library uses `CoHDS` — a custom intrusive halfedge data structure with
`CoVertex`, `CoEdge`, `CoFace` types that carry domain-specific data directly as fields.
conformallab++ replaces this with `CGAL::Surface_mesh<Point3>` and attaches data via
**named property maps**:
```cpp
// Java: vertex.getLambda() → C++: maps.lambda0[v]
// Java: edge.getAlpha() → C++: maps.e_alpha[e]
// Java: vertex.getSolverIndex() → C++: maps.v_idx[v]
auto [lambda0, ok] = mesh.add_property_map<Edge_index, double>("e:lambda0", 0.0);
lambda0[e] = 1.234;
```
This decouples the mesh topology from the algorithm data, makes it straightforward
to attach multiple independent data sets to the same mesh, and will enable the Phase 8
traits-class design to work with any CGAL-conforming mesh type.
---
## DOF vector convention
All three functionals use the same indexing scheme: a flat `std::vector<double> x`
indexed by `v_idx[v]` (vertices) and `e_idx[e]` (edges, HyperIdeal only).
Index value `-1` means "pinned" — the DOF is fixed at zero and excluded from the
Newton system.
```
x[maps.v_idx[v]] = uᵥ (conformal scale factor, Euclidean/Spherical)
x[maps.e_idx[e]] = λₑ (edge log-length variable, HyperIdeal only)
-1 pinned — u_v = 0 / λ_e = 0
```
This is consistent across all three geometry modes, enabling the same Newton solver
and linear system infrastructure to serve all three without branching.
---
## Priority-BFS layout
A naive BFS layout places faces in arbitrary order; trilateration errors accumulate
along the BFS frontier. conformallab++ uses a **priority min-heap on BFS depth**:
```
depth(face) = max(depth[v_src], depth[v_tgt]) + 1 for each new face
```
Faces with smaller depth (closer to the root) are placed first. This means each
face's trilateration uses the two most accurately-placed adjacent vertices, minimising
error propagation across the mesh.
Root face selection: largest 3-D area face, with an additional 1.5× bonus for
interior faces over boundary faces. This heuristic places the root where metric
distortion is lowest.
---
## `halfedge_uv` semantics
`layout.uv[v.idx()]` gives the *primary* UV coordinate of vertex `v` — the position
from the shallowest BFS visit. At seam edges this is insufficient for GPU rendering:
two faces sharing a seam vertex need *different* UV values for that vertex.
`layout.halfedge_uv[h.idx()]` stores the UV of `source(h)` **as seen from `face(h)`**:
```
halfedge h → face(h) → source(h) has UV = halfedge_uv[h.idx()]
opposite(h) → face(h') → source(h) has UV = halfedge_uv[opposite(h).idx()]
(different value at a seam)
```
At seam halfedges the two opposite halfedges carry different UV values — each face
gets its own copy of the seam vertex. This enables a proper GPU texture atlas
without vertex duplication in the index buffer.
---
## Spherical Hessian sign convention
The spherical energy functional is **concave** (negative semidefinite Hessian).
Standard Newton would require solving `H·Δx = G` with NSD `H`, which Cholesky
cannot handle.
`newton_spherical()` solves `(H)·Δx = G` instead — algebraically identical,
but `H` is PSD and `SimplicialLDLT` works correctly. This sign flip is handled
transparently inside `newton_spherical()`; callers need not be aware of it.
The gradient sign in spherical mode is also flipped vs. Euclidean:
- Euclidean: `G_v = actual_sum Θᵥ`
- Spherical: `G_v = Θᵥ actual_sum`
Both conventions drive the same equilibrium condition `G = 0`.
---
## HyperIdeal Hessian via finite differences
The analytic HyperIdeal Hessian requires differentiating through the chain
`(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ/βᵢ` with four vertex-type combinations
per edge — substantial implementation complexity.
conformallab++ uses a **symmetric finite-difference Hessian** instead:
```
H[i,j] = (G(x + ε·eⱼ)[i] G(x ε·eⱼ)[i]) / (2ε), ε = 1e-5
```
Properties:
- O(ε²) accuracy — relative error ≈ 10⁻¹⁰ at ε = 10⁻⁵
- PSD guaranteed by strict convexity of the HyperIdeal energy (Springborn 2020)
- Symmetrised automatically: `H = (H + Hᵀ) / 2`
- Cost: n extra gradient evaluations per Newton step (acceptable for < 500 DOFs)
The analytic Hessian is deferred to Phase 9b. See [roadmap/java-parity.md](../roadmap/java-parity.md).

View File

@@ -0,0 +1,281 @@
# conformallab++ vs. geometry-central — Detailed Comparison
> **Purpose of this document.**
> conformallab++ and geometry-central (CMU, Keenan Crane's group) both implement
> discrete conformal equivalence of triangulated surfaces. They share the same
> mathematical core but diverge in algorithmic strategy, scope, and target audience.
> This document maps the overlap precisely, identifies what cannot and should not be
> adopted, and explains where a side-by-side study creates scientific added value.
---
## 1 — Shared mathematical foundation
Both libraries implement the following chain:
```
Input mesh M → edge lengths ℓᵢⱼ → solve for u ∈ ℝᵛ
such that ℓ̃ᵢⱼ = e^{(uᵢ+uⱼ)/2} · ℓᵢⱼ satisfies Σα_v(u) = Θᵥ ∀v
```
The variational framework that makes this a well-posed optimisation problem goes
back to **Bobenko & Springborn (2004)**. The hyperbolic (HyperIdeal) geometry is
from **Springborn (2020)**. The geometry-central implementation (Gillespie,
Springborn & Crane, SIGGRAPH 2021) is an explicit extension of Springborn 2020
to intrinsic triangulations.
**Key consequence:** the *mathematical problem* is identical. Any difference in
output is either a normalization convention or a bug in one of the two libraries —
making cross-validation directly meaningful.
---
## 2 — Algorithmic comparison
| Dimension | conformallab++ | geometry-central (Gillespie 2021) |
|---|---|---|
| **Solver** | NewtonRaphson, analytical Hessian | Newton or Yamabe gradient flow (user choice) |
| **Convergence** | Quadratic (820 iterations on typical meshes) | Newton: quadratic; Yamabe: linear (~hundreds of steps) |
| **Triangulation** | Fixed throughout — operates on original `Surface_mesh` | Ptolemaic flips applied before/during solve to reach intrinsic Delaunay |
| **Hessian** | SimplicialLDLT + SparseQR fallback; analytical for Euclidean/Spherical, FD for HyperIdeal | Assembled on the current (possibly flipped) triangulation |
| **Mesh backend** | CGAL `Surface_mesh<Point_3>` | geometry-central `ManifoldSurfaceMesh` |
| **Geometry modes** | Euclidean ✓ · Spherical ✓ · HyperIdeal ✓ | Euclidean ✓ · Hyperbolic ✓ · Spherical ✗ |
| **Open meshes** | ✓ (boundary DOFs pinned) | ✓ |
### Why Newton on a fixed triangulation works well
The Euclidean and HyperIdeal energies are strictly convex after gauge-fixing.
Newton therefore converges from u=0 in 820 iterations for any reasonable mesh.
The Hessian is the cotangent Laplacian (Euclidean) or its hyperbolic analog —
well-conditioned on Delaunay meshes, but can degrade on strongly non-Delaunay inputs.
### What Ptolemaic flips add
A Ptolemaic flip replaces diagonal AC with BD in a quadrilateral under the constraint
that the Ptolemy relation
```
AC · BD = AB · CD + AD · BC
```
holds. This is *conformal-class-preserving* — the new λ₀ values represent the same
discrete conformal structure. The gain: the flipped triangulation is intrinsic
Delaunay, which bounds the off-diagonal Hessian entries and prevents ill-conditioning
on pathological inputs.
**This is the only genuine algorithmic advantage geometry-central has for the shared
sub-problem.** It costs nothing mathematically and buys robustness on bad meshes.
---
## 3 — Feature matrix: what exists where
| Feature | conformallab++ | geometry-central | Notes |
|---|---|---|---|
| Discrete conformal equivalence (Euclidean) | ✓ | ✓ | Shared core |
| Discrete conformal equivalence (Hyperbolic/HyperIdeal) | ✓ (Springborn 2020 formulation) | ✓ (Gillespie 2021 extension) | Mathematically equivalent |
| Discrete conformal equivalence (Spherical) | ✓ | ✗ | Unique to conformallab++ |
| Analytical Hessian (Euclidean & Spherical) | ✓ | ✓ | |
| Analytical Hessian (HyperIdeal) | FD (Phase 9b: analytical planned) | ✓ | gc has analytical version |
| GaussBonnet check & enforce | ✓ | implicit in solver | |
| Tree-cotree cut graph (2g seam edges) | ✓ | ✗ | Required for period matrix |
| Priority-BFS layout in ℝ²/S²/Poincaré disk | ✓ | partial (conformal param only) | |
| Möbius holonomy SU(1,1) | ✓ | ✗ | Unique to conformallab++ |
| Period matrix τ ∈ + SL(2,) reduction | ✓ | ✗ | Unique to conformallab++ |
| Fundamental domain + tiling | ✓ | ✗ | Unique to conformallab++ |
| Intrinsic Delaunay triangulation | ✗ | ✓ | gc has via SignpostIntrinsicTriangulation |
| Ptolemaic flips | ✗ | ✓ | gc's robustness mechanism |
| Heat method (geodesic distances) | ✗ | ✓ | Auxiliary tool in gc |
| YAML/declarative pipeline | ✓ (Phase 8e spec) | ✗ | |
| CGAL-package submission | ✓ (Phase 8 target) | ✗ | |
| JSON/XML serialisation | ✓ | ✗ | |
| CLI app | ✓ | ✗ | |
| Inversive distance functional (Luo 2004) | ✗ (Phase 9a) | ✗ | Neither has it yet |
| Siegel period matrix Ω (genus g≥2) | ✗ (Phase 10b) | ✗ | |
---
## 4 — What should be adopted — and what should not
### Adopt: Ptolemaic pre-conditioning (GC-2, after Phase 8)
**What:** a single preprocessing pass that Delaunay-izes the input triangulation
via Ptolemaic flips, updates λ₀ accordingly, then hands off to the existing
Newton pipeline unchanged.
**Why it fits:**
- Preserves the conformal class — mathematically sound
- Drop-in before `compute_euclidean_lambda0_from_mesh()`, no interface change
- Does not touch cut graph, holonomy, period matrix
- ~200 lines of code, one new test suite
**Interface sketch:**
```cpp
// include/preprocessing_delaunay.hpp (Phase GC-2)
void ptolemy_delaunay(ConformalMesh& mesh, EuclideanMaps& maps);
// Flips edges until all faces satisfy the Delaunay condition.
// Updates maps.lambda0[e] via the Ptolemy relation after each flip.
// Prerequisite: compute_euclidean_lambda0_from_mesh() already called.
// Postcondition: mesh is an intrinsic Delaunay triangulation of the same surface.
```
### Adopt partially: analytical HyperIdeal Hessian
geometry-central has a closed-form Hessian for the hyperbolic energy.
conformallab++ currently uses finite differences (Phase 9b plans analytical).
The geometry-central implementation can serve as a reference for Phase 9b —
not a code copy, but a mathematical cross-check.
### Do not adopt: full intrinsic triangulations as architecture
**SignpostIntrinsicTriangulation** is geometry-central's core data structure.
It tracks vertex positions as (face, barycentric coordinates) rather than 3D points.
Replacing `CGAL::Surface_mesh` with this would require:
1. Rewriting the cut graph algorithm (which works on halfedges of the *original* mesh
and must survive across flips — non-trivial bookkeeping)
2. Tracking seam edges through flip events for holonomy computation
3. Abandoning the CGAL package target (Phase 8) — CGAL's mesh concepts are extrinsic
4. Losing the 3D layout output (Poincaré disk, sphere) which clients depend on
**Verdict:** the architecture incompatibility is fundamental, not incidental.
The period matrix pipeline requires a stable topological cut that does not survive
arbitrary flip sequences. This is not a solvable engineering problem within the
current project scope — it would be a different project.
### Do not adopt: Yamabe flow
Newton converges in 820 iterations; Yamabe flow needs hundreds.
The only reason to use Yamabe flow is when the Hessian is indefinite (which happens
in the Spherical case — and conformallab++ already handles this with the correct
sign flip in the energy). There is no mesh type where Yamabe flow beats Newton
on metrics that conformallab++ targets.
---
## 5 — Where cross-comparison creates scientific added value
### 5.1 — Independent cross-validation of the shared core
The discrete conformal equivalence problem for Euclidean and HyperIdeal geometry
is implemented independently in two codebases, by different groups, with different
algorithms. Agreement on:
- the u-vector (after normalization)
- UV coordinates (up to Möbius transformation)
- the residual ‖G(u*)‖ at convergence
would constitute **mutual validation without ground truth**. This is the same
methodology used in numerical PDE literature to validate independent solvers.
**Concrete protocol:**
```
For each test mesh (cathead.obj, brezel.obj, torus_4x4.off, torus_hex_6x6.off):
1. Load into both libraries with identical vertex ordering
2. Run conformallab++ Newton solver → u_clab, UV_clab
3. Run geometry-central solver → u_gc, UV_gc
4. Normalize both (subtract mean, divide by scale)
5. Report max|u_clab[v] - u_gc[v]| and mean conformal distortion difference
```
Expected: agreement to ≤ 1e-8 on well-conditioned meshes. Discrepancy would
indicate a bug or a normalization mismatch worth investigating.
### 5.2 — Convergence study: Newton with vs. without Ptolemaic pre-conditioning
Hypothesis: on non-Delaunay meshes (e.g. a torus refined by subdivision without
re-meshing), Ptolemaic Delaunay pre-conditioning reduces Newton iteration count.
**Measurable quantities:**
- Newton iterations to ‖G‖ < 1e-8
- Hessian condition number κ(H) at u = 0
- Wall-clock time
This comparison requires only GC-2 to be implemented in conformallab++ and would
answer the question: *how bad does a mesh have to be before Ptolemaic pre-conditioning
pays off?*
**Publication potential:** a short note or conference contribution comparing the
two approaches on a systematic mesh quality benchmark would be self-contained and
novel neither library has published this comparison.
### 5.3 — Period matrix and holonomy as differentiating contribution
geometry-central deliberately stops at the conformal parameterization. The period
matrix τ and Möbius holonomy computation in conformallab++ extend the pipeline into
Teichmüller theory territory that geometry-central does not address.
This is the strongest scientific differentiator: conformallab++ can compute
```
τ = ω_b / ω_a ∈ , SL(2,)-reduced
```
for any closed genus-1 surface, and (in Phase 10) the Siegel matrix Ω for genus g2.
No other open-source C++ library does this. Cross-comparison with geometry-central
makes this gap explicit and positions conformallab++ as the more complete tool for
Teichmüller-theoretic applications.
### 5.4 — Spherical geometry as unique contribution
The Spherical geometry mode (angle sums on a sphere, NSD Hessian with sign
flip) has no counterpart in geometry-central. A mathematician interested in
conformal maps on surfaces of positive curvature (constant curvature +1) has no
alternative in open-source C++.
### 5.5 — Validation of Springborn 2020 in two independent implementations
Springborn 2020 ("Ideal Hyperbolic Polyhedra and Discrete Uniformization") is the
shared theoretical reference for both the HyperIdeal geometry mode in conformallab++
(Phase 2/3) and the hyperbolic component of Gillespie 2021 in geometry-central.
Cross-checking the ζ-function values (ζ₁₃, ζ₁₄, ζ₁₅) and the resulting angle sums
on the same meshes would validate both implementations of the paper a service to
the discrete geometry community.
---
## 6 — Demarcation: where the comparison ends
| Topic | conformallab++ | geometry-central | Comparable? |
|---|---|---|---|
| u-vector at convergence | | | after normalization |
| UV parameterization | | | up to Möbius |
| Convergence speed (iterations) | | (Newton mode) | direct |
| HyperIdeal angle sums | | | |
| Spherical angle sums | | | |
| Period matrix τ | | | |
| Holonomy T_a, T_b | | | |
| Fundamental domain | | | |
| Intrinsic Delaunay quality | not tracked | | |
| Mesh topology handling | CGAL halfedge | gc manifold mesh | not comparable |
| Scalability (large meshes) | not benchmarked yet | benchmarked in paper | comparable if same mesh |
The comparison is meaningful and complete for the **shared conformal core**.
It ends where conformallab++ continues into Teichmüller theory (holonomy, τ,
fundamental domain) that region has no counterpart in geometry-central and
must be validated by analytic invariants alone (→ `doc/math/validation.md`).
---
## 7 — Practical roadmap for the comparison
| Step | When | What | Effort |
|---|---|---|---|
| **GC-1a** | Now | Manual UV comparison on cathead.obj run both, diff u-vectors | 1 day |
| **GC-1b** | Now | Add normalization utility to conformallab++ (`normalize_u_vector()`) | 2h |
| **GC-1c** | After Phase 8 | Automated comparison script (Python or small C++ binary) | 2 days |
| **GC-2** | After Phase 8 | `ptolemy_delaunay()` preprocessing pass | 1 week |
| **GC-bench** | After GC-2 | Convergence study: Newton ± Ptolemaic pre-conditioning on 10 meshes | 1 week |
| **GC-paper** | Phase 10 | Short note on the comparison period matrix as differentiator | |
---
## 8 — References
| Reference | Role in this comparison |
|---|---|
| **Bobenko, Springborn** *Variational Principles for Circle Patterns*, Trans. AMS (2004) | Shared variational foundation for all three geometry modes |
| **Springborn** *Ideal Hyperbolic Polyhedra and Discrete Uniformization*, DCG (2020) | Mathematical basis for HyperIdeal in conformallab++ AND for Gillespie 2021 |
| **Gillespie, Springborn, Crane** *Discrete Conformal Equivalence of Polyhedral Surfaces*, SIGGRAPH (2021) | geometry-central implementation; introduces Ptolemaic flips |
| **Sharp, Soliman, Crane** *Navigating Intrinsic Triangulations*, SIGGRAPH (2019) | geometry-central `SignpostIntrinsicTriangulation` basis for GC-2 |
| **Sechelmann** doctoral thesis, TU Berlin (2016) | conformallab++ primary source; covers period matrix, holonomy, all three modes |

View File

@@ -388,177 +388,18 @@ Normalise PCA centring Rodrigues to N pole weighted Möbius centrin
---
## Extension points
## Further documentation
### Adding a new functional
1. Create `my_functional.hpp` with a `Maps` struct and `evaluate_my_functional()`.
2. The gradient must satisfy: `G_v = Σ(angle contributions) theta_v[v]`.
3. Verify with a finite-difference gradient check (copy any `GradientCheck_*` test).
4. Pass to `solve_linear_system(H, -G)` or write a thin `newton_my` wrapper.
### Adding a new geometry mode
Implement `trilaterate_my()` with the correct local placement formula,
then follow the same BFS structure as `euclidean_layout` (see `layout.hpp`).
The priority BFS, holonomy tracking, and `halfedge_uv` population are geometry-agnostic.
### Adding a new processing unit
Declare its preconditions and capabilities explicitly (see table above).
The pipeline validation is currently manual (documented contracts); a
compile-time or runtime check is a natural Phase 8 extension.
---
## Declarative pipeline (target for Phase 8)
A lightweight YAML description for reproducible experiments:
```yaml
pipeline:
name: flat_torus_period
geometry: euclidean
input:
source: data/torus.off
steps:
- id: setup
unit: setup_euclidean_maps
provide: [maps_initialised]
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_initialised]
provide: [gauss_bonnet_satisfied]
- id: solve
unit: newton_euclidean
require: [gauss_bonnet_satisfied]
params:
tol: 1.0e-10
max_iter: 200
provide: [x_converged]
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: euclidean_layout
require: [x_converged, cut_graph]
params:
normalise: true
provide: [layout_uv, holonomy]
- id: period
unit: compute_period_matrix
require: [holonomy]
provide: [tau]
output:
layout: out/torus_layout.off
json: out/torus_result.json
tau: out/torus_tau.txt
```
Each unit declares `require` (preconditions) and `provide` (capabilities).
A pipeline is valid if for every step, all `require` keys are satisfied by the
accumulated `provide` set of all preceding steps.
This mirrors exactly the contract table in this document.
---
## Development Roadmap
> **Grenze Portierung / neue Forschung:**
> Phase 17 sind direkte Portierungen aus dem Java-Original (Sechelmann 2016).
> Ab Phase 8 geht die Arbeit über den Umfang der Java-Bibliothek hinaus.
> — Phase 8 (CGAL-Paket) ist **Infrastruktur**, kein neuer Algorithmus.
> — Phase 9 (Inversive-Distance, Analytischer Hessian, 4g-Polygon) ist **Portierung** ausstehender Java-Features.
> — Phase 10+ ist **eigenständige Forschung**, die über das Java-Original hinausgeht.
---
### ◼ Portierungsphase abgeschlossen — Phase 17
```
Phase 1 Clausen / Lobachevsky / ImLi₂ ✅
Phase 2 Hyper-ideal Geometrie (ζ, lᵢⱼ, αᵢⱼ, σᵢ) ✅
Phase 3 CGAL-Infrastruktur + alle drei Funktionale (E/S/H) ✅
Phase 4 Newton-Solver (SimplicialLDLT + SparseQR-Fallback) ✅ 68 Tests
Phase 5 Priority-BFS-Layout + CLI + JSON/XML ✅ 95 Tests
Phase 6 GaussBonnet, Tree-Cotree-Schnittgraph, Normalisierung ✅ 121 Tests
Phase 7 MobiusMap, halfedge_uv, Möbius-Holonomie, Periodenmatrix,
Fundamentalbereich (Genus 1), Java-Parität abgeschlossen ✅ 158 Tests
```
---
### ◼ Infrastruktur (über Java-Bibliothek hinaus) — Phase 8: CGAL-Paket
```
8a Traits-Klasse & Konzepte → include/CGAL/Conformal_map_traits.h
8b Öffentliche CGAL-Header-Hierarchie → include/CGAL/Discrete_conformal_map.h etc.
8c Dokumentation im CGAL-Stil → doc/Conformal_map/PackageDescription.txt
8d CGAL-Testformat → test/Conformal_map/
8e Declarative YAML-Pipeline → pipeline validator (require/provide tokens)
```
See the [Declarative pipeline](#declarative-pipeline-target-for-phase-8) section above
for the YAML schema that Phase 8e will validate at runtime.
---
### ◼ Ausstehende Portierung (Java-Features noch nicht übertragen) — Phase 9
```
9a Inversive-Distance-Funktional (Luo 2004)
→ InversiveDistanceMaps + functional + Hessian
→ discrete uniformization via inversive distances
9b Analytischer HyperIdeal-Hessian (ζ-Kette)
→ replace FD Hessian in hyper_ideal_hessian.hpp
→ reduces Newton iterations for large meshes
9c 4g-Polygon-Randlauf (Genus g > 1)
→ extend compute_fundamental_domain() beyond genus 1
→ algorithm outline already in fundamental_domain.hpp as TODO(Phase 9)
```
---
### ◼ Neue Forschung (über das Java-Original hinaus) — Phase 10+
```
Phase 10 Globale Uniformisierung Genus g ≥ 2
10a Holomorphe Differentiale auf diskreten Flächen
→ discrete harmonic 1-forms; integration along cut graph cycles
10b Siegel-Periodenmatrix Ω ∈ H_g (g×g komplex-symmetrisch, Im(Ω) > 0)
→ extend compute_period_matrix() to genus g ≥ 2
→ requires holomorphic differentials from 10a
10c Vollständige Uniformisierung
→ uniformize arbitrary genus-g surface to canonical constant-curvature metric
→ depends on 10a + 10b
```
---
## Recommended reading
### Primary source — the dissertation this library implements
| | |
| Topic | Document |
|---|---|
| **Sechelmann***Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, TU Berlin 2016 | The mathematical foundation of the entire library: discrete conformal equivalence, variational angle-sum functionals, Newton solver, uniformization, period matrices. DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) |
| **Java original** — [github.com/varylab/conformallab](https://github.com/varylab/conformallab) | Reference implementation in Java — the direct source for all algorithms ported to C++ |
### Further references
| Source | Relevance in conformallab++ |
|--------|----------------------------|
| Springborn — *Ideal Hyperbolic Polyhedra and Discrete Uniformization* (2020) | HyperIdeal functional; ζ₁₃/ζ₁₄/ζ₁₅ in `hyper_ideal_geometry.hpp` |
| Pinkall, Polthier — *Computing Discrete Minimal Surfaces* (1993) | Cotangent-Laplace Hessian in `euclidean_hessian.hpp` |
| Bobenko, Springborn — *Variational Principles for Circle Patterns* (2004) | Angle-sum variational framework used throughout |
| Luo — *Combinatorial Yamabe Flow on Surfaces* (2004) | Inversive-distance functional (not yet ported) |
| Erickson, Whittlesey — *Greedy Optimal Homotopy Generators* (SODA 2005) | Tree-cotree algorithm in `cut_graph.hpp` |
| Public headers (all 24, with descriptions) | [api/headers.md](../api/headers.md) |
| Test suites (26 suites, 158 tests, individual counts) | [api/tests.md](../api/tests.md) |
| Extending the library (new functionals, geometry modes, Java porting guide) | [api/extending.md](../api/extending.md) |
| Processing unit contracts (preconditions / provides table) | [api/contracts.md](../api/contracts.md) |
| Phase 8 CGAL package design + declarative pipeline YAML | [api/cgal-package.md](../api/cgal-package.md) |
| Key design decisions + rationale | [architecture/design-decisions.md](design-decisions.md) |
| Project structure (directory tree + build targets) | [architecture/project-structure.md](project-structure.md) |
| Three geometry modes in detail | [math/geometry-modes.md](../math/geometry-modes.md) |
| References and papers | [math/references.md](../math/references.md) |
| Development roadmap (Phases 110) | [roadmap/phases.md](../roadmap/phases.md) |
| Java vs. C++ feature parity table | [roadmap/java-parity.md](../roadmap/java-parity.md) |

View File

@@ -0,0 +1,115 @@
# Project Structure
```
ConformalLabpp/
├── CLAUDE.md # Claude Code context file
├── README.md # Entry point — what/why, quick start, doc index
├── LICENSE # MIT
├── code/ # All C++ source
│ ├── CMakeLists.txt # Root: three build modes (default / CGAL_TESTS / CGAL)
│ │
│ ├── include/ # Header-only library — all algorithms here
│ │ ├── conformal_mesh.hpp # ConformalMesh = CGAL::Surface_mesh<Point3>
│ │ ├── constants.hpp # conformallab::PI, TWO_PI
│ │ ├── clausen.hpp # Cl₂, Lobachevsky Л, ImLi₂
│ │ ├── hyper_ideal_geometry.hpp # ζ₁₃/₁₄/₁₅, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ
│ │ ├── hyper_ideal_utility.hpp # Tetrahedron volumes (Meyerhoff / KolpakovMednykh)
│ │ ├── hyper_ideal_visualization_utility.hpp # Poincaré disk projection, circumcircle helpers
│ │ ├── hyper_ideal_functional.hpp # HyperIdeal energy + gradient on ConformalMesh
│ │ ├── hyper_ideal_hessian.hpp # HyperIdeal Hessian (symmetric FD, Phase 9b: analytic)
│ │ ├── spherical_geometry.hpp # Spherical arc-length, half-angle formula
│ │ ├── spherical_functional.hpp # Spherical energy + gradient + gauge-fix
│ │ ├── spherical_hessian.hpp # Spherical Hessian (∂α/∂u via law of cosines)
│ │ ├── euclidean_geometry.hpp # Euclidean corner angle (t-value / atan2)
│ │ ├── euclidean_functional.hpp # Euclidean energy + gradient
│ │ ├── euclidean_hessian.hpp # Cotangent Laplacian Hessian (PinkallPolthier)
│ │ ├── newton_solver.hpp # newton_{euclidean,spherical,hyper_ideal} + solve_linear_system
│ │ ├── gauss_bonnet.hpp # euler_characteristic, genus, check/enforce_gauss_bonnet
│ │ ├── cut_graph.hpp # CutGraph + compute_cut_graph (tree-cotree)
│ │ ├── layout.hpp # Priority-BFS layout, MobiusMap, halfedge_uv, HolonomyData
│ │ ├── mesh_builder.hpp # make_triangle / make_tetrahedron / make_quad_strip / make_fan
│ │ ├── mesh_io.hpp # load_mesh / save_mesh (OFF/OBJ/PLY via CGAL::IO)
│ │ ├── mesh_utils.hpp # CGAL → Eigen conversion (cgal_to_eigen)
│ │ ├── serialization.hpp # save/load_result_json + save/load_result_xml
│ │ ├── period_matrix.hpp # PeriodData, compute_period_matrix, SL(2,) reduction
│ │ └── fundamental_domain.hpp # FundamentalDomain, tiling_copy, tiling_neighbourhood
│ │
│ ├── src/
│ │ ├── apps/v0/conformallab_cli.cpp # CLI app (Phase 5): load → solve → layout → export
│ │ └── viewer/simple_viewer.cpp # libigl/GLFW viewer wrapper
│ │
│ ├── examples/ # Standalone example programs (require WITH_CGAL=ON)
│ │ ├── example_euclidean.cpp # Euclidean pipeline end-to-end
│ │ ├── example_hyper_ideal.cpp # HyperIdeal pipeline end-to-end
│ │ ├── example_layout.cpp # Full pipeline: solve → layout → OFF/JSON/XML + round-trip
│ │ └── example_viewer.cpp # Interactive libigl viewer
│ │
│ ├── tests/
│ │ ├── CMakeLists.txt
│ │ ├── test_clausen.cpp
│ │ ├── test_hyper_ideal_utility.cpp
│ │ ├── test_matrix_utility.cpp
│ │ ├── test_surface_curve_utility.cpp
│ │ ├── test_discrete_elliptic_utility.cpp
│ │ ├── test_p2_utility.cpp
│ │ ├── test_hyper_ideal_visualization_utility.cpp
│ │ └── cgal/ # CGAL test suite (WITH_CGAL_TESTS or WITH_CGAL)
│ │ ├── test_conformal_mesh.cpp
│ │ ├── test_hyper_ideal_functional.cpp
│ │ ├── test_spherical_functional.cpp
│ │ ├── test_euclidean_functional.cpp
│ │ ├── test_euclidean_hessian.cpp
│ │ ├── test_spherical_hessian.cpp
│ │ ├── test_newton_solver.cpp
│ │ ├── test_mesh_io.cpp
│ │ ├── test_pipeline.cpp
│ │ ├── test_layout.cpp
│ │ ├── test_phase6.cpp
│ │ ├── test_phase7.cpp
│ │ └── test_geometry_utils.cpp
│ │
│ └── deps/ # All dependencies as bundled tarballs
│ ├── tarballs/
│ │ ├── eigen-3.4.0.tar.gz
│ │ ├── CGAL-6.1.1.tar.xz
│ │ ├── libigl-2.6.0.tar.gz
│ │ ├── libigl-glad.tar.gz
│ │ └── glfw-3.4.tar.gz
│ └── single_includes/ # CLI11.hpp, json.hpp (header-only)
├── doc/
│ ├── getting-started.md
│ ├── contributing.md
│ ├── api/
│ │ ├── pipeline.md # Full pipeline API with code examples
│ │ ├── extending.md # New functionals, geometry modes, Java porting
│ │ ├── contracts.md # Processing unit preconditions/provides table
│ │ └── cgal-package.md # Phase 8 CGAL package design (TODO)
│ ├── architecture/
│ │ ├── overall_pipeline.md # Mermaid diagram + stage descriptions
│ │ ├── design-decisions.md # Key architectural choices + rationale
│ │ └── project-structure.md # This file
│ ├── math/
│ │ ├── geometry-modes.md # Euclidean / Spherical / HyperIdeal comparison
│ │ └── references.md # All papers by module
│ └── roadmap/
│ ├── phases.md # Phases 110 with porting/research boundary
│ └── java-parity.md # Java vs C++ feature parity table
└── .gitea/
├── docker/Dockerfile.ci-cpp # Ubuntu 22.04 ARM64 CI image
└── workflows/cpp-tests.yml # Two-job CI pipeline
```
## Build targets
| Target | Mode | Description |
|---|---|---|
| `conformallab_tests` | default | 36 non-CGAL pure-math tests |
| `conformallab_cgal_tests` | `WITH_CGAL_TESTS` or `WITH_CGAL` | 158 CGAL tests |
| `conformallab_core` | `WITH_CGAL` | CLI application |
| `example_euclidean` | `WITH_CGAL` | Euclidean example program |
| `example_hyper_ideal` | `WITH_CGAL` | HyperIdeal example program |
| `example_layout` | `WITH_CGAL` | Full pipeline example |
| `example_viewer` | `WITH_CGAL` | Interactive viewer |

View File

@@ -0,0 +1,571 @@
# Declarative YAML Pipeline — Concept & Design
> **Status: Phase 8e — designed, not yet implemented.**
> This document is the authoritative design specification.
> Implementation target: `code/include/pipeline.hpp` + CLI flag `--pipeline`.
---
## 1 — Core idea
Every algorithm in conformallab++ is a **Processing Unit**: a function with
explicit preconditions (*require*) and guarantees (*provide*).
The contract table in [doc/api/contracts.md](../api/contracts.md) lists them all.
A **declarative pipeline** is a YAML file that:
1. Lists the units to execute and their parameters.
2. Annotates each step with the tokens it `require`s and `provide`s.
3. Is validated **before any code runs** — the validator walks the dependency
graph and rejects the file if any `require` token is not yet provided by
a preceding step.
The result is a **self-documenting, reproducible experiment** that can be
version-controlled, shared, and re-run identically.
---
## 2 — Token vocabulary
Tokens are short strings. Each Processing Unit consumes and produces a fixed
set of tokens. The validator treats them as a monotonically growing *provided set*.
### Input tokens (provided by the `input:` block)
| Token | Meaning |
|---|---|
| `mesh` | A loaded, triangulated, oriented `ConformalMesh` |
| `mesh_closed` | Mesh has no boundary (required for cut graph) |
| `mesh_open` | Mesh has at least one boundary component |
### Setup tokens
| Token | Produced by | Required by |
|---|---|---|
| `maps_euclidean` | `setup_euclidean_maps` | `lambda0_euclidean`, `gauss_bonnet`, `newton_euclidean` |
| `maps_spherical` | `setup_spherical_maps` | `lambda0_spherical`, `gauss_bonnet`, `newton_spherical` |
| `maps_hyper_ideal` | `setup_hyper_ideal_maps` | `lambda0_hyper_ideal`, `gauss_bonnet`, `newton_hyper_ideal` |
| `lambda0_euclidean` | `compute_euclidean_lambda0_from_mesh` | `gauss_bonnet_euclidean`, `newton_euclidean` |
| `lambda0_spherical` | `compute_spherical_lambda0_from_mesh` | `gauss_bonnet_spherical`, `newton_spherical` |
| `lambda0_hyper_ideal` | `compute_hyper_ideal_lambda0_from_mesh` | `gauss_bonnet_hyper_ideal`, `newton_hyper_ideal` |
| `dof_indices` | DOF assignment step | `newton_*` |
### GaussBonnet tokens
| Token | Produced by |
|---|---|
| `gauss_bonnet_euclidean` | `check_gauss_bonnet` or `enforce_gauss_bonnet` (Euclidean maps) |
| `gauss_bonnet_spherical` | same, Spherical maps |
| `gauss_bonnet_hyper_ideal` | same, HyperIdeal maps |
### Solver tokens
| Token | Produced by |
|---|---|
| `x_euclidean` | `newton_euclidean` (converged) |
| `x_spherical` | `newton_spherical` (converged) |
| `x_hyper_ideal` | `newton_hyper_ideal` (converged) |
### Topology tokens
| Token | Produced by | Required by |
|---|---|---|
| `cut_graph` | `compute_cut_graph` | `euclidean_layout` (closed mesh), `hyper_ideal_layout` |
### Layout tokens
| Token | Produced by |
|---|---|
| `layout_euclidean` | `euclidean_layout` |
| `layout_spherical` | `spherical_layout` |
| `layout_hyper_ideal` | `hyper_ideal_layout` |
| `holonomy_euclidean` | `euclidean_layout` (with cut graph) |
| `holonomy_hyper_ideal` | `hyper_ideal_layout` (with cut graph) |
### Period / domain tokens
| Token | Produced by | Required by |
|---|---|---|
| `period_matrix` | `compute_period_matrix` | `fundamental_domain` |
| `fundamental_domain` | `compute_fundamental_domain` | `tiling` |
| `tiling` | `tiling_neighbourhood` | output steps |
### Output tokens
| Token | Produced by |
|---|---|
| `saved_layout` | `save_layout_off` |
| `saved_result` | `save_result_json` / `save_result_xml` |
---
## 3 — YAML schema
```yaml
pipeline:
name: <string> # human-readable experiment name
geometry: euclidean # euclidean | spherical | hyper_ideal
description: | # optional multi-line description
...
input:
source: <path> # mesh file (.off / .obj / .ply)
# Optional overrides:
theta_v: flat # flat (2π everywhere) | cone:<file> | custom:<file>
steps:
- id: <string> # unique step identifier
unit: <function> # C++ function name (see token table)
require: [<token>, ...] # tokens that must be in the provided set
provide: [<token>, ...] # tokens added to provided set after this step
params: # optional parameter overrides
<key>: <value>
output:
layout: <path> # optional: save Layout2D as .off with UVs
json: <path> # optional: save NewtonResult + Layout as JSON
xml: <path> # optional: save NewtonResult + Layout as XML
tau: <path> # optional: write τ (period matrix) as text
report: <path> # optional: write human-readable summary
```
### Parameter defaults by unit
| Unit | Parameter | Default |
|---|---|---|
| `newton_euclidean` | `tol` | `1e-8` |
| `newton_euclidean` | `max_iter` | `200` |
| `newton_spherical` | `tol` | `1e-8` |
| `newton_spherical` | `max_iter` | `200` |
| `newton_hyper_ideal` | `tol` | `1e-8` |
| `newton_hyper_ideal` | `max_iter` | `200` |
| `newton_hyper_ideal` | `hess_eps` | `1e-5` |
| `euclidean_layout` | `normalise` | `false` |
| `hyper_ideal_layout` | `normalise` | `false` |
| `spherical_layout` | `normalise` | `false` |
| `compute_period_matrix` | `reduce` | `true` (SL(2,)) |
| `tiling_neighbourhood` | `m` | `1` |
| `tiling_neighbourhood` | `n` | `1` |
---
## 4 — Validation algorithm
```
provided = { "mesh" } ← always available after input is loaded
if input.source has no boundary:
provided ← provided { "mesh_closed" }
else:
provided ← provided { "mesh_open" }
for each step in pipeline.steps:
for token in step.require:
if token ∉ provided:
ERROR: "Step '<id>' requires '<token>' which is not yet provided.
Add a step that provides it before step '<id>'."
provided ← provided step.provide
for each output key in pipeline.output:
check that its required token is in provided
(e.g. 'tau' requires 'period_matrix')
```
The validator runs **before any C++ code executes**. If validation passes,
the steps are executed in order. There is no parallelism — steps are sequential.
---
## 5 — Complete examples
### Example A — Euclidean uniformization of a flat torus (genus 1)
```yaml
pipeline:
name: flat_torus_euclidean
geometry: euclidean
description: |
Euclidean uniformization of the 4×4 torus of revolution.
Computes the period matrix τ and the fundamental domain parallelogram.
input:
source: code/data/off/torus_4x4.off
steps:
- id: setup
unit: setup_euclidean_maps
require: [mesh]
provide: [maps_euclidean]
- id: lambda0
unit: compute_euclidean_lambda0_from_mesh
require: [mesh, maps_euclidean]
provide: [lambda0_euclidean]
- id: dofs
unit: assign_dof_indices_euclidean
require: [maps_euclidean]
provide: [dof_indices]
params:
pin_strategy: first_vertex # pin v₀, assign sequential to rest
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_euclidean, lambda0_euclidean]
provide: [gauss_bonnet_euclidean]
- id: solve
unit: newton_euclidean
require: [gauss_bonnet_euclidean, dof_indices]
provide: [x_euclidean]
params:
tol: 1.0e-10
max_iter: 200
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: euclidean_layout
require: [x_euclidean, cut_graph]
provide: [layout_euclidean, holonomy_euclidean]
params:
normalise: true
- id: period
unit: compute_period_matrix
require: [holonomy_euclidean]
provide: [period_matrix]
params:
reduce: true
- id: domain
unit: compute_fundamental_domain
require: [holonomy_euclidean]
provide: [fundamental_domain]
output:
layout: out/torus_layout.off
json: out/torus_result.json
tau: out/torus_tau.txt
```
**Expected output (`torus_tau.txt`):**
```
tau = 0.000... + 0.9...i # Re(τ) ≈ 0 (4-fold symmetry), Im(τ) ≈ 1
|tau| = 0.9... # ≥ 1 after SL(2,) reduction
```
---
### Example B — Spherical uniformization (genus 0)
```yaml
pipeline:
name: cathead_spherical
geometry: spherical
description: |
Map the open cathead mesh to the sphere.
input:
source: code/data/obj/cathead.obj
steps:
- id: setup
unit: setup_spherical_maps
require: [mesh]
provide: [maps_spherical]
- id: lambda0
unit: compute_spherical_lambda0_from_mesh
require: [mesh, maps_spherical]
provide: [lambda0_spherical]
- id: dofs
unit: assign_dof_indices_spherical
require: [maps_spherical]
provide: [dof_indices]
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_spherical, lambda0_spherical]
provide: [gauss_bonnet_spherical]
- id: solve
unit: newton_spherical
require: [gauss_bonnet_spherical, dof_indices]
provide: [x_spherical]
- id: layout
unit: spherical_layout
require: [x_spherical]
provide: [layout_spherical]
params:
normalise: true # rotate centroid to north pole
output:
json: out/cathead_spherical.json
```
---
### Example C — Hyperbolic uniformization (genus 1, Poincaré disk)
```yaml
pipeline:
name: torus_hyperbolic
geometry: hyper_ideal
description: |
Hyperbolic (hyper-ideal) uniformization of the 8×8 torus.
Lays out the mesh in the Poincaré disk with correct Möbius holonomy.
input:
source: code/data/off/torus_8x8.off
steps:
- id: setup
unit: setup_hyper_ideal_maps
require: [mesh]
provide: [maps_hyper_ideal]
- id: lambda0
unit: compute_hyper_ideal_lambda0_from_mesh
require: [mesh, maps_hyper_ideal]
provide: [lambda0_hyper_ideal]
- id: dofs
unit: assign_all_dof_indices
require: [maps_hyper_ideal]
provide: [dof_indices]
# HyperIdeal: all vertices AND edges are free DOFs — no vertex pinned.
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_hyper_ideal, lambda0_hyper_ideal]
provide: [gauss_bonnet_hyper_ideal]
- id: solve
unit: newton_hyper_ideal
require: [gauss_bonnet_hyper_ideal, dof_indices]
provide: [x_hyper_ideal]
params:
tol: 1.0e-10
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: hyper_ideal_layout
require: [x_hyper_ideal, cut_graph]
provide: [layout_hyper_ideal, holonomy_hyper_ideal]
params:
normalise: true # Möbius-centre to disk origin
output:
layout: out/torus_disk.off
json: out/torus_disk.json
```
---
### Example D — Full pipeline with period matrix and tiling
```yaml
pipeline:
name: torus_full
geometry: euclidean
input:
source: code/data/off/torus_hex_6x6.off
steps:
- id: setup
unit: setup_euclidean_maps
require: [mesh]
provide: [maps_euclidean]
- id: lambda0
unit: compute_euclidean_lambda0_from_mesh
require: [mesh, maps_euclidean]
provide: [lambda0_euclidean]
- id: dofs
unit: assign_dof_indices_euclidean
require: [maps_euclidean]
provide: [dof_indices]
- id: gauss_bonnet
unit: enforce_gauss_bonnet
require: [maps_euclidean, lambda0_euclidean]
provide: [gauss_bonnet_euclidean]
- id: solve
unit: newton_euclidean
require: [gauss_bonnet_euclidean, dof_indices]
provide: [x_euclidean]
- id: cut
unit: compute_cut_graph
require: [mesh_closed]
provide: [cut_graph]
- id: layout
unit: euclidean_layout
require: [x_euclidean, cut_graph]
provide: [layout_euclidean, holonomy_euclidean]
params:
normalise: true
- id: period
unit: compute_period_matrix
require: [holonomy_euclidean]
provide: [period_matrix]
- id: domain
unit: compute_fundamental_domain
require: [holonomy_euclidean]
provide: [fundamental_domain]
- id: tiling
unit: tiling_neighbourhood
require: [layout_euclidean, holonomy_euclidean]
provide: [tiling]
params:
m: 2 # 5×5 tile grid around origin
n: 2
output:
layout: out/hex_torus_layout.off
tau: out/hex_torus_tau.txt
json: out/hex_torus_full.json
report: out/hex_torus_summary.txt
# Expected tau for 6-fold symmetric torus:
# Re(τ) ≈ 0.5, Im(τ) ≈ 0.866 (approaches e^{iπ/3} as mesh is refined)
```
---
### Example E — Validation error (intentional mistake)
```yaml
pipeline:
name: broken_example
geometry: euclidean
input:
source: code/data/off/torus_4x4.off
steps:
- id: solve # ← WRONG: skipped setup and lambda0
unit: newton_euclidean
require: [gauss_bonnet_euclidean, dof_indices]
provide: [x_euclidean]
- id: layout
unit: euclidean_layout
require: [x_euclidean, cut_graph] # ← WRONG: cut_graph never provided
provide: [layout_euclidean]
```
**Validator output:**
```
ERROR [step 'solve']: requires 'gauss_bonnet_euclidean' which is not yet provided.
Hint: add setup_euclidean_maps → compute_euclidean_lambda0_from_mesh
→ enforce_gauss_bonnet before 'solve'.
ERROR [step 'layout']: requires 'cut_graph' which is not yet provided.
Hint: add compute_cut_graph (requires: mesh_closed) before 'layout'.
Pipeline rejected. 2 contract violations.
```
---
## 6 — Mapping to C++ code
Each `unit:` name in the YAML maps directly to a C++ function:
| YAML unit | C++ function | Header |
|---|---|---|
| `setup_euclidean_maps` | `conformallab::setup_euclidean_maps()` | `euclidean_functional.hpp` |
| `compute_euclidean_lambda0_from_mesh` | `conformallab::compute_euclidean_lambda0_from_mesh()` | `euclidean_functional.hpp` |
| `enforce_gauss_bonnet` | `conformallab::enforce_gauss_bonnet()` | `gauss_bonnet.hpp` |
| `assign_dof_indices_euclidean` | manual pin + sequential loop | `euclidean_functional.hpp` |
| `assign_all_dof_indices` | `conformallab::assign_all_dof_indices()` | `hyper_ideal_functional.hpp` |
| `newton_euclidean` | `conformallab::newton_euclidean()` | `newton_solver.hpp` |
| `newton_spherical` | `conformallab::newton_spherical()` | `newton_solver.hpp` |
| `newton_hyper_ideal` | `conformallab::newton_hyper_ideal()` | `newton_solver.hpp` |
| `compute_cut_graph` | `conformallab::compute_cut_graph()` | `cut_graph.hpp` |
| `euclidean_layout` | `conformallab::euclidean_layout()` | `layout.hpp` |
| `spherical_layout` | `conformallab::spherical_layout()` | `layout.hpp` |
| `hyper_ideal_layout` | `conformallab::hyper_ideal_layout()` | `layout.hpp` |
| `compute_period_matrix` | `conformallab::compute_period_matrix()` | `period_matrix.hpp` |
| `compute_fundamental_domain` | `conformallab::compute_fundamental_domain()` | `fundamental_domain.hpp` |
| `tiling_neighbourhood` | `conformallab::tiling_neighbourhood()` | `fundamental_domain.hpp` |
| `save_layout_off` | `conformallab::save_layout_off()` | `mesh_io.hpp` |
| `save_result_json` | `conformallab::save_result_json()` | `serialization.hpp` |
---
## 7 — Implementation plan (Phase 8e)
```
code/include/pipeline.hpp ← YAML parser + validator + executor
code/apps/conformallab_pipeline.cpp ← CLI: conformallab_pipeline --pipeline foo.yml
External YAML dependency (header-only, already bundled):
code/deps/single_includes/yaml-cpp/yaml.h ← or single-include yaml.hpp
Alternative: use the bundled single_includes/nlohmann/json.hpp for a
JSON-based pipeline format (simpler, no new dependency).
```
### Validator pseudocode
```cpp
struct PipelineValidator {
std::set<std::string> provided;
void load_input(const YAML::Node& input) {
provided.insert("mesh");
auto mesh = load_mesh(input["source"].as<std::string>());
if (is_closed(mesh)) provided.insert("mesh_closed");
else provided.insert("mesh_open");
}
void validate_step(const YAML::Node& step) {
for (auto& tok : step["require"])
if (!provided.count(tok.as<std::string>()))
throw PipelineError("Step '" + step["id"].as<std::string>()
+ "' requires '" + tok.as<std::string>() + "' not yet provided.");
for (auto& tok : step["provide"])
provided.insert(tok.as<std::string>());
}
void validate(const YAML::Node& pipeline) {
load_input(pipeline["input"]);
for (auto& step : pipeline["steps"])
validate_step(step);
}
};
```
---
## 8 — Design decisions
**Why YAML and not JSON or TOML?**
YAML supports multi-line strings (for `description:`), comments (`#`), and
anchors/aliases — useful for parametric experiments. JSON lacks comments.
TOML lacks the list syntax needed for `require:` / `provide:`.
**Why explicit require/provide instead of auto-inference?**
Auto-inference would require the validator to know all function signatures
at parse time — this couples the validator tightly to the C++ code.
Explicit tokens make the contract visible in the YAML, making it
self-documenting and readable without the source code.
**Why sequential steps and not a DAG?**
The pipeline is a linear sequence for now (Phase 8e target). A DAG-based
executor (parallel steps where contracts allow) is a natural Phase 10+ extension
but adds significant complexity. Linear execution is correct and debuggable.
**Why one YAML per geometry mode?**
A single YAML could support multiple geometries with conditional blocks, but
this adds syntactic complexity. The `geometry:` field at the top locks the mode
and keeps the YAML readable. Cross-geometry experiments can chain two pipelines.

89
doc/contributing.md Normal file
View File

@@ -0,0 +1,89 @@
# Contributing
## Language
**All code, comments, documentation, commit messages, and test descriptions must be in English.**
The project targets CGAL submission and international collaboration. When editing
files that still contain German-language comments or documentation, replace them
with English.
---
## Git workflow
- `main` is protected on `origin` (Gitea). Push to `dev`, then open a pull request.
- `codeberg/main` can be pushed to directly (public mirror, no CI).
- Both remotes must stay in sync after every significant change:
```bash
git push origin HEAD:dev # triggers CI
git push codeberg main # updates public mirror
```
- Branch naming: `feature/<topic>`, `fix/<topic>`, `phase<N>-<topic>`
---
## CI
Two jobs run on push to `dev`/`main` or on pull requests:
| Job | What it tests | Trigger |
|---|---|---|
| `test-fast` | 36 non-CGAL tests, no Boost | all branches |
| `test-cgal` | 170 CGAL tests + 1 skip | `main`, `dev`, PRs only |
A PR is ready to merge when both jobs pass.
The runner is a self-hosted Raspberry Pi (ARM64). See `.gitea/workflows/cpp-tests.yml`
and `.gitea/docker/Dockerfile.ci-cpp`.
---
## Test standards
Every new algorithm needs:
1. **Gradient check** — finite-difference verification of `G(x) = ∂E/∂x`.
Copy any `GradientCheck_*` test suite from `tests/cgal/test_*_functional.cpp`.
2. **Convergence test** — Newton converges on a small mesh using the "natural theta"
trick (see [CLAUDE.md](../CLAUDE.md) and [api/extending.md](api/extending.md)).
3. **Registration** — add the `.cpp` file to `code/tests/cgal/CMakeLists.txt`.
Expected CI result: **36 + 170 tests pass, exactly 1 skipped**.
The skip is an intentional `GTEST_SKIP()` stub for the genus-2 homology test
(`cgal.HomologyGenerators.Genus2_FourGeneratorPaths_BLOCKED`) — blocked until a
genus-2 mesh is available in Phase 9c. Do not remove it.
---
## Code style
- C++17. Header-only (`code/include/*.hpp`). No compiled library.
- `#pragma once` at the top of every header.
- Everything in the `conformallab` namespace, internal helpers in `conformallab::detail`.
- Property map names follow the prefix convention: `"v:"` (vertex), `"e:"` (edge), `"f:"` (face).
- DOF index `-1` always means "pinned/fixed".
---
## Releases
Release tags follow `vMAJOR.MINOR.PATCH`:
```bash
# Merge dev → main, then tag
git checkout main && git merge --no-ff dev
git tag -a vX.Y.Z -m "vX.Y.Z — <one-line summary>"
git push origin main && git push origin vX.Y.Z
git push codeberg main && git push codeberg vX.Y.Z
```
---
## TODO
- [ ] Define code review checklist
- [ ] Add `.clang-format` configuration
- [ ] Document how to request CGAL package review

189
doc/getting-started.md Normal file
View File

@@ -0,0 +1,189 @@
# Getting Started
## Prerequisites
| Tool | Minimum | Notes |
|---|---|---|
| C++ compiler (GCC or Clang) | C++17 | GCC 11+ or Clang 14+ recommended |
| CMake | 3.20 | |
| Boost headers | 1.70 | Only for `-DWITH_CGAL_TESTS=ON` or `-DWITH_CGAL=ON` |
| Wayland/X11 dev headers | — | Only for `-DWITH_CGAL=ON` (viewer) |
All other dependencies (Eigen 3.4, CGAL 6.1.1, libigl 2.6, GLFW 3.4, GTest 1.14)
are bundled as tarballs in `code/deps/tarballs/` and extracted automatically at CMake
configure time. No internet access is required at build time (except GTest, fetched via
`FetchContent` from GitHub).
Install Boost on your system:
```bash
# Ubuntu / Debian
apt install libboost-dev
# macOS
brew install boost
```
---
## Clone
```bash
git clone https://codeberg.org/TMoussa/ConformalLabpp
cd ConformalLabpp
```
---
## Build modes
Three modes with increasing dependencies:
### Mode 1 — Fast tests (default, no system dependencies)
Pure-math tests: Clausen functions, hyper-ideal geometry, matrix utilities.
```bash
cmake -S code -B build
cmake --build build --target conformallab_tests -j$(nproc)
ctest --test-dir build --output-on-failure
```
Expected: **36 tests pass**.
### Mode 2 — CGAL tests, headless (recommended for CI and development)
Full CGAL test suite. Requires Boost headers only — no display, no Wayland, no GLFW.
```bash
cmake -S code -B build -DWITH_CGAL_TESTS=ON
cmake --build build --target conformallab_cgal_tests -j$(nproc)
ctest --test-dir build -R "^cgal\." --output-on-failure
```
Expected: **173 tests pass, 1 skipped** (intentional stub for analytic HyperIdeal Hessian, Phase 9b).
### Mode 3 — Full local build (CLI app + interactive viewer)
Requires Wayland or X11 development packages (`wayland-scanner`, `libx11-dev`, etc.).
Automatically enables the viewer library (GLFW + libigl).
```bash
cmake -S code -B build -DWITH_CGAL=ON
cmake --build build -j$(nproc)
```
> **Note:** `-DWITH_CGAL=ON` implies `-DWITH_VIEWER=ON`. Do not use this in headless
> environments — it will fail with `Failed to find wayland-scanner`.
---
## Running a single test
```bash
# By GTest filter (fastest, full output)
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver*"
./build/conformallab_tests --gtest_filter="Clausen*"
# By CTest regex
ctest --test-dir build -R "cgal.NewtonSolver" --output-on-failure
```
All CGAL tests have the prefix `cgal.` in CTest (set in `tests/cgal/CMakeLists.txt`
via `TEST_PREFIX "cgal."`).
---
## First run — CLI app
After a full build (`-DWITH_CGAL=ON`):
```bash
# Euclidean conformal layout
./bin/conformallab_core -i input.off -g euclidean -o layout.off -j result.json
# Spherical layout
./bin/conformallab_core -i input.off -g spherical -o sphere.off
# Hyperbolic layout (HyperIdeal)
./bin/conformallab_core -i input.off -g hyper_ideal -o hyperbolic.off
# Show input mesh in interactive viewer
./bin/conformallab_core -i input.off -s
# All options
./bin/conformallab_core --help
```
## Example programs
```bash
./build/examples/example_layout [input.off] [layout.off] [result.json]
./build/examples/example_euclidean [input.off] [output.off]
./build/examples/example_hyper_ideal [input.off] [output.off]
./build/examples/example_viewer [input.off] # interactive, requires WITH_VIEWER
```
`example_layout.cpp` is the best starting point — it shows the complete pipeline in ~120 lines.
**Expected output of `example_euclidean` on the built-in quad-strip mesh:**
```
[example_euclidean] No input file given — using make_quad_strip().
[example_euclidean] Mesh: 6 vertices, 4 faces.
[example_euclidean] DOFs: 5 (1 vertex pinned).
[example_euclidean] Solving Newton system…
[example_euclidean] Converged in 1 iterations. ||G||_inf = 0
[example_euclidean] Per-vertex conformal factors u_i:
v0 u = 0 (pinned)
v1 u = -1.38778e-17 ← ≈ 0 (machine epsilon)
...
[example_euclidean] Mesh saved to: /tmp/conformallab_euclidean_out.off
```
Convergence in 1 iteration is expected: the "natural equilibrium" construction
sets x* = 0, so a small perturbation (-0.05) needs only one Newton step.
**Quick automated start (no interactive build needed):**
```bash
bash scripts/try_it.sh
```
This clones nothing (run from inside the repo), builds the CGAL test suite, runs
all 173+36 tests, and prints a summary. See `scripts/try_it.sh` for details.
---
## Known issues
### macOS Finder duplicates
macOS Finder sometimes creates duplicate files named `foo 2.hpp` when copying
the repository. These cause confusing compile errors ("redefinition of …").
**Fix (run once from the repo root):**
```bash
find code/include -name "* 2.*" -delete
find code/include -name "*\ 2.*" -delete
```
Files without a ` 2` suffix are always canonical — the duplicates are safe to delete.
### First build is slow
The first CMake configure extracts four tarballs (Eigen 3.4, CGAL 6.1.1,
libigl 2.6, GLFW 3.4) and downloads GTest via `FetchContent`.
Allow **3090 seconds** for the first configure. Subsequent builds are fast
(< 10 s incremental).
---
## Rebuilding the CI Docker image
The CI runner is a self-hosted Raspberry Pi (ARM64). After changes to
`.gitea/docker/Dockerfile.ci-cpp`:
```bash
docker buildx build \
--platform linux/arm64 \
-f .gitea/docker/Dockerfile.ci-cpp \
-t git.eulernest.eu/conformallab/ci-cpp:latest \
--push \
.gitea/docker/
```

View File

@@ -0,0 +1,175 @@
# Discrete Conformal Geometry — Mathematical Background
This document is written for a mathematician who knows Riemannian surfaces and
complex analysis but is new to the *discrete* setting. It covers exactly the
theory implemented in conformallab++.
---
## 1 — The continuous picture (in one paragraph)
A **Riemann surface** (M, g) carries a conformal structure: the class of all
metrics related to g by a smooth positive factor. On a compact surface of genus
g, the Uniformization Theorem gives a unique constant-curvature representative
(flat for g = 1, hyperbolic for g ≥ 2, spherical for g = 0). The conformal
modulus of a genus-1 surface is a point τ ∈ (upper half-plane), well-defined
up to SL(2, ).
---
## 2 — Discrete conformal equivalence (DCE)
A **triangulated surface** is a pair (K, ) where K is a simplicial complex
homeomorphic to a surface and : E → ℝ₊ assigns an edge length. Two
length assignments and ℓ̃ are **discretely conformally equivalent** if there
exist vertex weights u: V → such that
```
ℓ̃ᵢⱼ = e^{(uᵢ + uⱼ)/2} · ℓᵢⱼ for every edge {i, j}.
```
This is the discrete analogue of a conformal rescaling g̃ = e^{2φ} g.
The weights u ∈ ^V are the *conformal factors* (log-scale factors on vertices).
**Key fact** (Springborn 2020): within each DCE class there exists a unique
length assignment realising a prescribed angle structure, and it can be found
by Newton's method on a convex energy.
---
## 3 — The variational energy
For each target corner angle **Θ_v** at vertex v, define the **angle-defect energy**:
```
E(u) = Σ_{corners} φ(αᵥ(u)) Σᵥ Θᵥ · uᵥ + boundary terms
```
where αᵥ(u) is the corner angle at v in the triangulation with edge lengths
ℓ̃(u) and φ is an appropriate primitive (Clausen / Lobachevsky / ImLi₂ depending
on geometry).
The **gradient** is simply the angle-sum residual:
```
∂E/∂uᵥ = Σ_{faces containing v} αᵥ(face) Θᵥ
```
Setting G = 0 finds the unique u realising the prescribed angle sums.
### Three geometry modes
| Mode | Space | φ | Hessian | Newton step |
|---|---|---|---|---|
| Euclidean | ℝ² | Clausen Cl₂ | cotangent Laplacian, PSD | SimplicialLDLT(H) |
| Spherical | S² | ImLi₂ | NSD (concave E) | SimplicialLDLT(H) |
| Hyper-ideal | H² | Lobachevsky | PSD (strictly convex) | SimplicialLDLT(H) |
---
## 4 — GaussBonnet constraint
The target angles **must** satisfy the discrete GaussBonnet equation
```
Σᵥ (2π Θᵥ) = 2π · χ(M)
```
before any solver is called. If this fails, no conformal factor can realise Θ
and Newton will not converge. conformallab++ provides:
```cpp
check_gauss_bonnet(mesh, maps); // throws if violated
enforce_gauss_bonnet(mesh, maps); // redistributes residual uniformly
```
---
## 5 — From angles to geometry: trilateration
After Newton converges, edge lengths ℓ̃ are known. The layout
(embedding into ℝ², S², or H²) is built by a **priority BFS**:
1. Place an initial face arbitrarily.
2. For each adjacent face, place its third vertex by *trilateration*
solving the system of three distance equations.
3. Priority is depth in the spanning tree (shallowest first).
For Euclidean geometry this is the standard cosine rule.
For hyperbolic geometry (Poincaré disk model) it uses the Möbius-isometric
placement formula implemented in `layout.hpp`.
---
## 6 — Cut graph and holonomy
For genus g ≥ 1 the layout does not close up: a handle introduces a
**holonomy** — a non-trivial monodromy around each generator of π₁.
The **tree-cotree algorithm** (EricksonWhittlesey 2005) computes a minimal
cut graph with exactly 2g seam edges. After cutting, the surface is disk-like
and the BFS layout is well-defined. The holonomies along the 2g cut edges are:
- **Euclidean:** lattice translations ω₁, ω₂ ∈
- **Hyperbolic:** Möbius isometries T₁, T₂ ∈ SU(1,1)
---
## 7 — Period matrix (genus 1)
For a torus the **conformal modulus** is
```
τ = ω₂ / ω₁ ∈
```
After Euclidean uniformization, ω₁ and ω₂ are the holonomies computed from
the seam edge displacements. The SL(2, )-reduction to the fundamental
domain {|τ| ≥ 1, |Re(τ)| ≤ 1/2, Im(τ) > 0} is performed automatically by
`compute_period_matrix()`.
For genus g ≥ 2, the Siegel period matrix Ω ∈ H_g (g×g complex symmetric with
positive definite imaginary part) requires integrating holomorphic 1-forms — this
is Phase 10b.
---
## 8 — Fundamental domain
The fundamental domain of a torus is the parallelogram with vertices
{0, ω₁, ω₁+ω₂, ω₂} in . conformallab++ computes this and provides tiling
utilities (`tiling_copy`, `tiling_neighbourhood`).
For genus g ≥ 2, the fundamental domain is the standard 4g-gon (Phase 9c).
---
## 9 — Where the code lives
```
Energy / gradient code/include/*_functional.hpp
Hessian code/include/*_hessian.hpp
Newton solver code/include/newton_solver.hpp
Trilateration / BFS code/include/layout.hpp
Cut graph code/include/cut_graph.hpp
Holonomy code/include/layout.hpp (HolonomyData)
Period matrix code/include/period_matrix.hpp
Fundamental domain code/include/fundamental_domain.hpp
Möbius maps code/include/layout.hpp (MobiusMap)
```
All implementations are header-only (C++17), no compiled library.
---
## 10 — Primary references
| Reference | Covers |
|---|---|
| Springborn, *Discrete Uniformization of Polyhedral Surfaces*, 2020 | Complete mathematical foundation of all three modes |
| Sechelmann, *Variational Methods for Discrete Surface Parameterization*, TU Berlin 2016 | Original Java implementation — the direct source for this library |
| Pinkall & Polthier, 1993 | Cotangent Laplacian |
| Erickson & Whittlesey, SODA 2005 | Tree-cotree cut graph |
| Bobenko & Springborn, Trans. AMS 2004 | Variational circle-pattern framework |
Full reference list: [doc/math/references.md](references.md)

112
doc/math/geometry-modes.md Normal file
View File

@@ -0,0 +1,112 @@
# 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.
---
## 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.

View File

@@ -0,0 +1,143 @@
# Scientific Novelty Statement
> **Purpose.** This document explicitly states what conformallab++ contributes
> that no other open-source C++ library provides, and for which research problems
> it is the right tool. It is intended as a reference for paper introductions,
> grant applications, and collaborator onboarding.
---
## 1 — The one-sentence statement
conformallab++ is the **only open-source C++ library** that implements discrete
conformal equivalence in all three geometric settings (Euclidean, Spherical,
Hyper-ideal), with a complete downstream Teichmüller pipeline — period matrix
τ ∈ , Möbius holonomy, and fundamental domain construction — in a single
cohesive codebase targeting the CGAL ecosystem.
---
## 2 — Unique features (no equivalent elsewhere in C++)
### 2.1 — Three geometry modes in one library
| Mode | Space | Energy | Application |
|---|---|---|---|
| Euclidean | ℝ² | Σ log(ℓᵢⱼ/ℓ̃ᵢⱼ)² | Flat torus uniformization, texture atlasing |
| Spherical | S² | NSD variant | Constant positive curvature, Koebe's theorem |
| HyperIdeal | H² (Poincaré disk) | Springborn 2020 ζ-functions | Hyperbolic surfaces, genus g ≥ 2 |
No other open-source C++ library implements all three. geometry-central
(CMU) has Euclidean and partial HyperIdeal but lacks the Spherical mode entirely.
### 2.2 — Period matrix τ with SL(2,) reduction
For a closed genus-1 surface, conformallab++ computes the complex modulus
τ = ω_b/ω_a ∈ from the holonomy of the uniformizing flat metric, then reduces
τ to the standard fundamental domain
```
F = { τ ∈ : |τ| ≥ 1, |Re(τ)| ≤ 1/2 }
```
via the SL(2,) action. This identifies the conformal class of the surface in
Teichmüller space T₁ ≅ /SL(2,).
**No other open-source C++ library computes τ.** The Java ConformalLab does,
but requires the JVM and is not integrated with any modern mesh processing framework.
### 2.3 — Möbius holonomy in SU(1,1)
The holonomy representation ρ: π₁(Σ) → SU(1,1) is computed for closed surfaces
of any genus. For the torus this gives the lattice generators ω_a, ω_b ∈ .
For hyperbolic surfaces this gives deck transformations as Möbius maps acting on
the Poincaré disk.
### 2.4 — Tree-cotree cut graph (EricksonWhittlesey)
For a closed surface of genus g, the cut graph produces exactly 2g seam edges
that cut the surface to a disk. This is required for layout, holonomy computation,
and fundamental domain construction. The cut graph is not present in any other
C++ conformal geometry library.
### 2.5 — Fundamental domain and tiling
From the holonomy generators, conformallab++ constructs the fundamental domain
parallelogram and its lattice tiling for genus-1 surfaces. This is the discrete
analog of the classical construction of a torus as /Λ.
---
## 3 — What makes this a research tool, not just an implementation
### 3.1 — Variational framework, not heuristic
The energy functionals are derived from first principles (BobenkoSpringborn 2004).
The Newton solver guarantees quadratic convergence to the *global* optimum for
Euclidean and HyperIdeal modes (strict convexity). The solution is mathematically
unique (up to Möbius normalisation) — not an approximation.
### 3.2 — Analytic Hessians
For Euclidean and Spherical modes, the Hessian is computed analytically from the
cotangent Laplacian and its spherical analog. This gives exact derivatives, not
finite-difference approximations, which is required for reproducible research.
### 3.3 — Discrete-to-smooth correspondence
The discrete period matrix τ_discrete is a computable invariant of the triangulated
surface. Its convergence to the smooth Riemannian τ_smooth under mesh refinement
is an open research question that this library is designed to investigate.
### 3.4 — Full test coverage of analytic invariants
173 CGAL tests verify mathematically provable properties:
- GaussBonnet: Σ(2πΘᵥ) = 2π·χ(M) to machine precision
- τ ∈ fundamental domain: three inequalities
- Holonomy closure: [T_a, T_b] = Id (abelian for genus 1)
- Gradient consistency: FD check at ε = 1e-5 for all three functionals
These are not regression tests — they verify mathematical correctness independently
of the input mesh.
---
## 4 — Target audience
| Audience | Primary use |
|---|---|
| Discrete differential geometers | Computing τ, holonomy, uniformization for theoretical examples |
| Computational mathematicians | Benchmarking discrete-to-smooth convergence of τ |
| CGAL developers | Extending the CGAL parameterization package (Phase 8) |
| Computer graphics researchers | Conformal texture atlasing with exact angle preservation |
| Algebraic geometers | Numerical experiments on moduli spaces of tori |
---
## 5 — Relationship to the Java original
conformallab++ is a port of Stefan Sechelmann's Java ConformalLab (TU Berlin,
~850 commits, v1.0.0 2018, LGPL). The port:
- Replaces the custom Java halfedge structure (`CoHDS`) with `CGAL::Surface_mesh`
- Replaces JUnit tests with GTest + CGAL test format (173 tests)
- Adds Doxygen API documentation, CMake build, and CLI
- Is MIT licensed (the Java original is LGPL)
- Targets submission to the CGAL library as package `Discrete_conformal_map`
The mathematics is identical to the Java original. The C++ implementation is
independently validated by the test suite and by agreement with Java outputs on
shared test meshes (cathead, brezel, torus family).
---
## 6 — What conformallab++ is not
- **Not a mesh processing library.** It operates on existing triangulated surfaces.
Remeshing, smoothing, and simplification are outside its scope.
- **Not a real-time renderer.** The Newton solver is accurate but not optimised
for interactive frame rates (though it converges in < 1 second for typical meshes).
- **Not a distortion-minimisation tool.** It computes the unique conformally
equivalent metric, not a least-distortion UV map. Use libigl for the latter.
- **Not complete for genus g 2.** The Siegel period matrix Ω and full
uniformization for higher genus are Phase 10 research targets, not yet implemented.

56
doc/math/references.md Normal file
View File

@@ -0,0 +1,56 @@
# References
## Primary source
This library implements the algorithms from:
| | |
|---|---|
| **Sechelmann***Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, Doctoral thesis, TU Berlin 2016 | The complete mathematical foundation: discrete conformal equivalence, variational angle-sum functionals, Newton solver, uniformization, period matrices, holonomy. DOI: [10.14279/depositonce-5415](https://depositonce.tu-berlin.de/items/8e2988b2-d991-45b5-aad5-9fb7988f3b2f) · CC BY-SA 4.0 |
Java reference implementation: [github.com/varylab/conformallab](https://github.com/varylab/conformallab)
---
## References by module
| Reference | Used in |
|---|---|
| **Springborn***Ideal Hyperbolic Polyhedra and Discrete Uniformization*, Discrete & Computational Geometry (2020) | `hyper_ideal_geometry.hpp` — ζ₁₃/ζ₁₄/ζ₁₅ functions; `hyper_ideal_functional.hpp` |
| **Pinkall, Polthier***Computing Discrete Minimal Surfaces and Their Conjugates*, Experimental Mathematics (1993) | `euclidean_hessian.hpp` — cotangent Laplacian |
| **Bobenko, Springborn***Variational Principles for Circle Patterns and Koebe's Theorem*, Transactions AMS (2004) | Variational angle-sum framework underlying all three functionals |
| **Luo***Combinatorial Yamabe Flow on Surfaces*, Communications in Contemporary Mathematics (2004) | Inversive-distance functional — **not yet ported**, Phase 9a |
| **Erickson, Whittlesey***Greedy Optimal Homotopy and Homology Generators*, SODA (2005) | `cut_graph.hpp` — tree-cotree algorithm |
| **Bobenko, Springborn***A Discrete LaplaceBeltrami Operator for Simplicial Surfaces*, Discrete & Computational Geometry (2007) | Background for cotangent weights |
| **Desbrun, Kanso, Tong***Discrete Differential Forms for Computational Modeling*, SIGGRAPH Course Notes (2006) | Discrete exterior calculus background for Phase 10a |
---
## geometry-central cross-reference *(optional comparison track)*
> Diese Referenzen beziehen sich auf eine alternative Implementierung desselben
> mathematischen Problems. Sie sind keine Voraussetzung für conformallab++,
> aber relevant für Kreuz-Validierung und mögliche algorithmische Adoptionen
> (→ GC-1/2/3 im Phasen-Roadmap, → Abschnitt 9 in `validation.md`).
| Reference | Relevanz |
|---|---|
| **Gillespie, Springborn, Crane***Discrete Conformal Equivalence of Polyhedral Surfaces*, ACM SIGGRAPH 2021. DOI: [10.1145/3450626.3459763](https://doi.org/10.1145/3450626.3459763) | Implementiert in **geometry-central**. Erweitert Springborn 2020 um intrinsische Triangulierungen und Ptolemäische Flips. Löst dasselbe DCE-Problem wie conformallab++, aber mit anderem Algorithmus. |
| **Sharp, Soliman, Crane***Navigating Intrinsic Triangulations*, ACM SIGGRAPH 2019 | Algorithmische Grundlage für `SignpostIntrinsicTriangulation` in geometry-central — relevant für GC-2 (optionales Pre-Conditioning). |
**Hinweis zu Springborn 2020:**
Das Papier *"Ideal Hyperbolic Polyhedra and Discrete Uniformization"*
(Springborn, Discrete & Computational Geometry 2020) ist **in conformallab++
bereits implementiert** — es ist die direkte Referenz für den HyperIdeal-Geometriemodus
(`hyper_ideal_geometry.hpp`). Die geometry-central Implementierung (Gillespie 2021)
baut auf diesem Papier auf und ergänzt es um Ptolemäische Flips.
---
## Phase 10 references (future research)
| Reference | Relevant for |
|---|---|
| **Farkas, Kra***Riemann Surfaces*, Springer GTM 71 | Siegel period matrix, Teichmüller theory |
| **Siegel***Topics in Complex Function Theory, Vol. 2*, Wiley | Siegel upper half-space H_g, Sp(2g,) reduction |
| **Bobenko, Mercat, Schmies***Period Matrices of Polyhedral Surfaces*, in: Computational Approach to Riemann Surfaces (2011) | Discrete period matrices on polyhedral surfaces |

View File

@@ -0,0 +1,128 @@
# Software Landscape — Discrete Conformal Geometry Tools
> **Purpose.** A mathematician evaluating conformallab++ needs to know how it
> relates to existing tools. This document maps the full landscape and explains
> why no existing library covers the same ground.
---
## 1 — The two problems that look the same but are not
The term "conformal parameterization" covers two fundamentally different problems:
### Problem A — Conformal distortion minimization (LSCM / ABF++ / ARAP)
Find a UV map u: V → ℝ² that *minimises* a measure of angle distortion.
This is an unconstrained or lightly constrained optimisation over UV coordinates.
The solution depends on the embedding in ℝ³ and is **not unique** — it minimises
distortion but does not assign the surface to a canonical conformal class.
Tools: **libigl, CGAL Surface_parameterization, pmp-library, Blender, MeshLab**.
### Problem B — Discrete conformal equivalence (DCE)
Find scale factors u ∈ ℝᵛ such that the rescaled metric
ℓ̃ᵢⱼ = e^{(uᵢ+uⱼ)/2} · ℓᵢⱼ has prescribed cone angles Θᵥ at every vertex.
This is a variational problem on the *intrinsic metric* — independent of any
embedding. The solution is **unique** (up to a global Möbius transformation)
and places the surface in its canonical position in Teichmüller space.
Tools: **conformallab++, geometry-central (partial), original Java ConformalLab**.
> **This distinction matters.** A UV map from LSCM minimises distortion but
> cannot be used to compute the period matrix τ ∈ . A DCE solution can.
---
## 2 — Full comparison table
| Library | Lang | DCE solver | Spherical | HyperIdeal | Cut graph | Holonomy | Period τ | Open source |
|---|---|---|---|---|---|---|---|---|
| **conformallab++** | C++17 | Newton (quad.) | ✓ | ✓ | ✓ (tree-cotree) | ✓ SU(1,1) | ✓ SL(2,) | ✓ MIT |
| Java ConformalLab | Java 8 | Newton | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ LGPL |
| geometry-central | C++17 | Newton / Yamabe | ✗ | ✓ (partial) | ✗ | ✗ | ✗ | ✓ MIT |
| libigl | C++14 | LSCM / ARAP¹ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ MPL-2 |
| CGAL Parameterization | C++ | LSCM / Orbifold¹ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ GPL/LGPL |
| pmp-library | C++17 | harmonic / param.¹ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ MIT |
| OpenFlipper | C++ | LSCM plugin¹ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ LGPL |
| Matlab geom. toolbox | MATLAB | LSCM / ABF++¹ | ✗ | ✗ | ✗ | ✗ | ✗ | commercial |
¹ These are Problem-A methods (distortion minimisation), not DCE.
---
## 3 — Detailed comparison: conformallab++ vs. Java ConformalLab
conformallab++ is a C++17 reimplementation of the Java library:
| Aspect | Java ConformalLab | conformallab++ |
|---|---|---|
| Language | Java 8 | C++17 |
| Mesh type | Custom `CoHDS` halfedge | `CGAL::Surface_mesh` |
| Build system | Maven | CMake |
| Test framework | JUnit 4 | GTest + CGAL test format |
| Static linking | JVM required | standalone binary |
| CGAL integration | none | native (target: CGAL package) |
| Inversive distance | ✓ | planned (Phase 9a) |
| Analytic HI Hessian | ✓ | planned (Phase 9b) |
| Genus g>1 domain | ✓ partial | planned (Phase 9c) |
| Siegel matrix Ω | partial | planned (Phase 10b) |
| All three modes | ✓ | ✓ |
| Period matrix τ | ✓ | ✓ |
| Holonomy | ✓ | ✓ |
**Phase parity:** Phases 17 of conformallab++ cover all core Java features.
Phases 910 will complete the remaining items. See `doc/roadmap/java-parity.md`
for the full feature-by-feature table.
---
## 4 — Detailed comparison: conformallab++ vs. geometry-central
geometry-central (Keenan Crane, CMU) is the closest external peer.
Both implement DCE but diverge significantly in scope and algorithm.
| Dimension | conformallab++ | geometry-central |
|---|---|---|
| **Solver** | Newton, quadratic convergence | Newton or Yamabe flow |
| **Triangulation** | Fixed original mesh | Intrinsic + Ptolemaic flips |
| **Spherical geometry** | ✓ (NSD Hessian, sign flip) | ✗ |
| **Cut graph** | ✓ tree-cotree, 2g seams | ✗ |
| **Holonomy** | ✓ SU(1,1) Möbius maps | ✗ |
| **Period matrix** | ✓ τ ∈ , SL(2,)-reduced | ✗ |
| **Fundamental domain** | ✓ genus 1 complete | ✗ |
| **Mesh backend** | CGAL `Surface_mesh` | gc `ManifoldSurfaceMesh` |
| **CGAL integration** | ✓ (target: package) | ✗ |
For a full analysis including adoption candidates and scientific added value,
see `doc/architecture/geometry-central-comparison.md`.
---
## 5 — What Problem-A tools cannot do
The following tasks require DCE (Problem B) and cannot be done with LSCM/ARAP:
| Task | Requires |
|---|---|
| Compute the period matrix τ of a torus | DCE + holonomy + period matrix |
| Classify a surface in Teichmüller space | DCE |
| Construct a flat metric with prescribed cone angles | DCE |
| Tile a surface by a lattice (fundamental domain) | DCE + cut graph + holonomy |
| Compare two surfaces conformally | DCE (same conformal class ↔ same τ) |
| Uniformize a hyperbolic surface (genus g ≥ 2) | HyperIdeal DCE |
| Compute holomorphic differentials (Phase 10) | DCE + cut graph + integration |
---
## 6 — When to use which tool
| Goal | Recommended tool |
|---|---|
| Fast UV unwrapping for texture mapping | libigl LSCM or pmp-library |
| Angle-preserving parameterization, distortion study | CGAL Surface_parameterization |
| Discrete conformal equivalence, research | **conformallab++** |
| DCE with maximum numerical robustness on bad meshes | geometry-central (+ Ptolemaic flips) |
| Full Teichmüller pipeline (τ, holonomy, domain) | **conformallab++** only |
| Interactive exploration (viewer) | conformallab++ (`-DWITH_CGAL=ON`) |
| Java ecosystem / existing ConformalLab workflow | Java ConformalLab |

View File

@@ -0,0 +1,213 @@
# Validation Protocol
Concrete, reproducible steps to verify the mathematical correctness of
conformallab++. Every check below has a deterministic expected outcome.
---
## Prerequisites
```bash
cmake -S code -B build -DWITH_CGAL=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --target conformallab_cgal_tests -j$(nproc)
```
---
## Check 0 — All tests pass
```bash
ctest --test-dir build -R "^cgal\." --output-on-failure
```
**Expected output (last lines):**
```
100% tests passed, 0 tests failed out of 170
The following tests did not run:
206 - cgal.HomologyGenerators.Genus2_FourGeneratorPaths_BLOCKED (Skipped)
```
If any test fails, stop — the implementation is broken.
---
## Check 1 — GaussBonnet (topological identity, error < 1e-10)
```bash
./build/conformallab_cgal_tests --gtest_filter="GaussBonnet.*" -v
```
**Expected:** all 8 tests `[ PASSED ]`
What is verified: for each test mesh (tetrahedron χ=2, torus χ=0, open mesh χ=1):
```
Σᵥ (2π Θᵥ) = 2π · χ(M) ± 1e-10
```
This is a **pure topology check** — it fails only if vertex/face counts or
property-map assignments are wrong. It does not depend on the Newton solver.
---
## Check 2 — Euclidean gradient consistency (FD vs. analytic, error < 1e-6)
```bash
./build/conformallab_cgal_tests --gtest_filter="EuclideanFunctional.GradientCheck*" -v
```
**Expected:** all `GradientCheck_*` tests `[ PASSED ]`
What is verified: for ε = 1e-5,
```
|G(u)ᵢ (E(u + εeᵢ) E(u εeᵢ)) / (2ε)| < 1e-6
```
This check **proves that the energy and its gradient are mathematically consistent**.
A failing FD check means Newton will converge to the wrong point — it is the most
important correctness check for any new functional.
Also run for Spherical and HyperIdeal:
```bash
./build/conformallab_cgal_tests --gtest_filter="SphericalFunctional.GradientCheck*" -v
./build/conformallab_cgal_tests --gtest_filter="HyperIdealFunctional.GradientCheck*" -v
```
---
## Check 3 — Newton convergence on canonical test meshes
```bash
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver.*" -v
```
**Expected:** all 11 tests `[ PASSED ]`
Each test verifies:
- `res.converged == true`
- `res.grad_inf_norm < 1e-8`
- `res.iterations < 50` (typically 520 for the small test meshes)
---
## Check 4 — Period matrix: SL(2,)-reduction invariants
```bash
./build/conformallab_cgal_tests --gtest_filter="PeriodMatrix.*" -v
```
**Expected:** all 7 tests `[ PASSED ]`
The three mathematical invariants checked for **any** genus-1 output τ:
| Property | Condition | Why |
|---|---|---|
| Upper half-plane | `Im(τ) > 0` | τ encodes a positive-area lattice |
| Outside unit disk | `|τ| ≥ 1 1e-10` | SL(2,) reduction step |
| Vertical strip | `|Re(τ)| ≤ 0.5 + 1e-10` | SL(2,) reduction step |
These hold for **any** well-formed genus-1 mesh — they are topology, not geometry.
---
## Check 5 — Möbius arithmetic (complex analysis correctness)
```bash
./build/conformallab_cgal_tests --gtest_filter="MobiusMap.*" -v
```
**Expected:** all 8 tests `[ PASSED ]`
What is verified:
- `T ∘ T⁻¹ = Id` (inverse is correct)
- `(T₁ ∘ T₂)(z) = T₁(T₂(z))` (composition is associative)
- `from_three(z₁, z₂, z₃)` maps z₁→0, z₂→1, z₃→∞ (unique Möbius transformation)
A bug here would corrupt **all** hyperbolic holonomy computation.
---
## Check 6 — End-to-end pipeline (build + solve + layout)
```bash
./build/conformallab_cgal_tests --gtest_filter="Pipeline.*" -v
```
**Expected:** all 5 tests `[ PASSED ]`
What is verified: starting from a mesh file, the full pipeline
(setup → Gauss-Bonnet → Newton → layout → serialise → reload)
produces a consistent result.
---
## Check 7 — Manual torus τ verification
This check requires adding a small program (or modifying an existing test).
It validates that `torus_4x4.off` produces τ in the fundamental domain:
```cpp
#include "conformal_mesh.hpp"
#include "euclidean_functional.hpp"
#include "newton_solver.hpp"
#include "cut_graph.hpp"
#include "layout.hpp"
#include "period_matrix.hpp"
#include "mesh_io.hpp"
#include <iostream>
int main() {
conformallab::ConformalMesh mesh;
conformallab::load_mesh(mesh, "code/data/off/torus_4x4.off");
auto maps = conformallab::setup_euclidean_maps(mesh);
conformallab::compute_euclidean_lambda0_from_mesh(mesh, maps);
conformallab::enforce_gauss_bonnet(mesh, maps);
auto res = conformallab::newton_euclidean(mesh, std::vector<double>(maps.n_dof, 0.0), maps);
std::cout << "Converged: " << res.converged
<< " iterations: " << res.iterations
<< " |G|∞: " << res.grad_inf_norm << "\n";
auto cg = conformallab::compute_cut_graph(mesh);
conformallab::HolonomyData hol;
conformallab::euclidean_layout(mesh, res.x, maps, &cg, &hol, true);
auto pd = conformallab::compute_period_matrix(hol);
std::cout << "τ = " << pd.tau_reduced.real()
<< " + " << pd.tau_reduced.imag() << "i\n";
std::cout << "|τ| = " << std::abs(pd.tau_reduced) << "\n";
std::cout << "|Re(τ)| = " << std::abs(pd.tau_reduced.real()) << "\n";
}
```
**Expected output (torus_4x4.off, R=2, r=1 torus of revolution):**
```
Converged: 1 iterations: <30 |G|∞: <1e-8
τ = [small] + [positive]i (Re close to 0 by 4-fold symmetry)
|τ| ≥ 1.0 (fundamental domain)
|Re(τ)| ≤ 0.5 (fundamental domain)
```
The exact value of Im(τ) depends on the 3D embedding (R=2, r=1 gives unequal
inner/outer edge lengths). Use `torus_8x8.off` for a finer approximation.
---
## Summary checklist
```
[ ] Check 0: 170 tests pass, 1 skip
[ ] Check 1: GaussBonnet exact (1e-10)
[ ] Check 2: FD gradient < 1e-6 for all 3 geometries
[ ] Check 3: Newton convergence < 50 iterations
[ ] Check 4: τ in SL(2,) fundamental domain
[ ] Check 5: Möbius arithmetic (inverse, compose, from_three)
[ ] Check 6: End-to-end pipeline
[ ] Check 7: Torus τ in upper half-plane with correct symmetry
```
All checks are deterministic and do not depend on random initialization or
floating-point non-determinism beyond standard IEEE-754.

271
doc/math/validation.md Normal file
View File

@@ -0,0 +1,271 @@
# Mathematical Validation
This document lists analytically known results and explains how to verify
them against conformallab++ output. It is the primary tool for an independent
mathematician to check the correctness of the implementation.
---
## How to run the examples
```bash
cmake -S code -B build -DWITH_CGAL=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --target conformallab_cgal_tests
ctest --test-dir build -R cgal --output-on-failure
```
All 173 tests pass (1 skipped by design — see `doc/api/tests.md`).
---
## 1 — GaussBonnet (topology)
**Theorem.** For any closed triangulated surface M,
```
Σᵥ (2π Θᵥ) = 2π · χ(M)
```
where χ(M) = 2 2g is the Euler characteristic.
| Surface | g | χ | Σ(2π Θᵥ) |
|---|---|---|---|
| Sphere (tetrahedron, cube, …) | 0 | 2 | 4π |
| Torus | 1 | 0 | 0 |
| Double torus | 2 | 2 | 4π |
**How to check:**
```cpp
#include "gauss_bonnet.hpp"
auto defect = gauss_bonnet_sum(mesh, maps); // Σ(2π Θᵥ)
auto chi = mesh.euler_characteristic();
EXPECT_NEAR(defect, 2.0 * M_PI * chi, 1e-10);
```
Covered by: `cgal.GaussBonnet.*` tests in `test_phase6.cpp`.
---
## 2 — Period matrix: fundamental domain invariants
**Theorem (SL(2,)-reduction).** Every lattice τ ∈ has a unique representative
in the standard fundamental domain
```
F = { τ ∈ : |τ| ≥ 1, |Re(τ)| ≤ 1/2, Im(τ) > 0 }
```
**After calling `compute_period_matrix(hol)`, the returned τ must satisfy:**
| Condition | Invariant |
|---|---|
| `pd.tau_reduced.imag() > 0` | τ lies in the upper half-plane |
| `std::abs(pd.tau_reduced) >= 1.0 - 1e-10` | τ outside unit disk |
| `std::abs(pd.tau_reduced.real()) <= 0.5 + 1e-10` | τ in vertical strip |
These three conditions hold for **any** closed genus-1 triangulated surface
processed through Euclidean uniformization — they are topology, not geometry.
Covered by: `cgal.PeriodMatrix.TauInFundamentalDomain_*` tests in `test_phase7.cpp`.
---
## 3 — Square-symmetric torus
**Setup.** Take a torus mesh with 4-fold rotational symmetry around the z-axis
(e.g. `code/data/off/torus_4x4.off`, which has M=4 columns of vertices).
**Expected.** The symmetry group Z₄ acts conformally. Conformal automorphisms
of the torus correspond to SL(2,) symmetries of τ. The unique fixed point of
a rotation of order 4 in the modular group is τ = i. Therefore:
```
For a mesh with exact 4-fold symmetry and uniform edge lengths:
Re(τ) = 0 (to machine precision, by symmetry)
Im(τ) ≈ 1 (approaches 1 as mesh is refined)
```
The coarse 4×4 mesh (`torus_4x4.off`) gives Im(τ) in (0.7, 1.3) depending on
the 3D embedding (R=2, r=1 torus of revolution has unequal inner/outer edge lengths).
The uniformization algorithm finds the conformal class of the *abstract* metric
encoded in the edge lengths.
**Manual verification** (run from the build directory after adding a small
program or reading from the test output):
```cpp
ConformalMesh mesh; load_mesh(mesh, "code/data/off/torus_4x4.off");
EuclideanMaps maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps);
enforce_gauss_bonnet(mesh, maps);
auto res = newton_euclidean(mesh, maps);
CutGraph cg = compute_cut_graph(mesh);
HolonomyData hol;
euclidean_layout(mesh, res.x, maps, &cg, &hol, true);
PeriodData pd = compute_period_matrix(hol);
// pd.tau_reduced satisfies the fundamental domain invariants above
```
---
## 4 — Hexagonal-symmetric torus
**Setup.** Take a torus mesh with 6-fold rotational symmetry
(`code/data/off/torus_hex_6x6.off`, M=6).
**Expected.** The unique τ fixed under a rotation of order 6 in SL(2,) is
τ = e^{iπ/3} = ½ + i√3/2. So:
```
Re(τ) = 0.5 (to machine precision, by symmetry)
Im(τ) = √3/2 ≈ 0.8660
```
The coarse 6×6 torus of revolution approximates this: Re(τ) ≈ 0.5 by symmetry,
Im(τ) approaches √3/2 as the mesh is refined toward a flat hexagonal lattice.
---
## 5 — Newton convergence rate
**Theorem.** Because the Euclidean and hyper-ideal energies are strictly convex
(after gauge-fixing), Newton's method converges quadratically near the optimum.
**Expected:** for any mesh with up to a few hundred faces, Newton converges in
**fewer than 30 iterations** starting from u = 0.
```cpp
auto res = newton_euclidean(mesh, maps);
EXPECT_LT(res.iterations, 30);
EXPECT_LT(res.gradient_norm, 1e-10);
```
Covered by: `cgal.EuclideanPipeline.ConvRates_*` and similar tests.
---
## 6 — Gradient check (finite differences)
For each functional F(u), the gradient G = ∂F/∂u is verified by:
```
|G(u)ᵢ (F(u + εeᵢ) F(u εeᵢ)) / (2ε)| < 1e-6
```
with ε = 1e-5. This check is run **inside the test suite** for all three
geometries (Euclidean, Spherical, HyperIdeal) at u = 0 and at random u.
Relevant test suites:
```
cgal.EuclideanFunctional.GradientCheck_*
cgal.SphericalFunctional.GradientCheck_*
cgal.HyperIdealFunctional.GradientCheck_*
```
A failing gradient check means the energy and its derivative are inconsistent —
the Newton solver will converge to the wrong point.
---
## 7 — Holonomy composition (Möbius maps)
For a closed surface, the composition of holonomies around any contractible cycle
must be the identity. In genus 1 with a single handle:
```
T₁ · T₂ · T₁⁻¹ · T₂⁻¹ = Id (commutator = Id for a torus)
```
because π₁(T²) = × is abelian.
For genus g ≥ 2, the fundamental group is non-abelian and this check does not hold,
but the representation ρ: π₁(Σ_g) → SU(1,1) must still satisfy the relation
```
[T₁, T₂] · [T₃, T₄] · … = Id (product of g commutators = Id)
```
These are the **holonomy consistency** checks implemented in `test_phase7.cpp`
(`cgal.HolonomyData.*`).
---
## 9 — Cross-validation with geometry-central *(optional / hypothetical)*
> **Hinweis:** Dieser Abschnitt beschreibt eine mögliche externe Kreuz-Validierung,
> die keine Voraussetzung für die Korrektheit der Implementierung ist.
> Sie ist interessant, weil geometry-central denselben mathematischen Kern
> implementiert (Gillespie, Springborn, Crane — SIGGRAPH 2021, aufbauend auf
> Springborn 2020), aber mit einer anderen algorithmischen Strategie
> (Ptolemäische Flips + intrinsische Triangulierungen statt Newton auf der
> Original-Triangulierung).
### Welche Outputs sind vergleichbar?
| Output | conformallab++ | geometry-central | Vergleichbar? |
|---|---|---|---|
| u-Vektor (Skalierungsparameter) | `res.x` | `u` nach Yamabe flow | ✓ nach Normalisierung |
| UV-Koordinaten | `layout.uv[v]` | konforme Parametrisierung | ✓ bis auf Möbius-Transformation |
| Gauss-Bonnet Defekt | `gauss_bonnet_sum()` | implizit via Krümmungsfluss | ✓ (analytisch identisch) |
| Anzahl Newton-Iterationen | `res.iterations` | Yamabe-Schritte | ~ (anderer Algorithmus) |
| Period-Matrix τ | `pd.tau_reduced` | **nicht vorhanden** | ✗ |
| Möbius-Holonomie | `hol.T_a, T_b` | **nicht vorhanden** | ✗ |
### Normalisierungsabgleich
Der u-Vektor in conformallab++ hat einen Freiheitsgrad (globale additive Konstante —
Eichfreiheit nach Pin-Fixierung). geometry-central kann eine andere Konvention nutzen.
Vor dem Vergleich normalisieren:
```cpp
// conformallab++: u zentrieren
double mean_u = std::accumulate(x.begin(), x.end(), 0.0) / x.size();
std::vector<double> x_norm(x.size());
for (int i = 0; i < x.size(); ++i) x_norm[i] = x[i] - mean_u;
// Dann mit geometry-central u-Vektor (ebenfalls zentriert) vergleichen:
// max|x_norm[i] - gc_u[i]| < 1e-8 → identischer Konvergenzpunkt
```
### Wann ist der Vergleich sinnvoll?
| Zeitpunkt | Was ist möglich |
|---|---|
| **Jetzt (Phase 7)** | Manueller Vergleich mit denselben `.off`/`.obj` Testnetzen |
| **Nach Phase 8** | Automatisiertes Vergleichsskript (Python oder separates C++-Binary) |
| **Phase 10 (Forschung)** | Algorithmus-Vergleich: Newton vs. Ptolemäische Flips auf schwierigen Netzen |
### Voraussetzungen für einen fairen Vergleich
1. Identische Eingabenetze (OFF/OBJ, gleiche Vertex-Orientierung)
2. Gleiche Gauss-Bonnet-Zielkrümmungen (Θᵥ = 2π für alle v, geschlossene Fläche)
3. u-Normalisierung abgeglichen (zentriert, gleiche Eichfixierung)
4. Konvergenztoleranz synchronisiert (max. Gradientnorm < 1e-8)
### Verbindung zur Literatur
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete Uniformization")
ist **in conformallab++ bereits implementiert** es ist die mathematische Grundlage
für den HyperIdeal-Geometriemodus (Phase 2/3). Die geometry-central Implementierung
basiert auf der Weiterentwicklung von Gillespie, Springborn & Crane (2021), die
denselben Variationsprinzip von BobenkoSpringborn 2004 verwendet, aber zusätzlich
Ptolemäische Flips einsetzt, um die Triangulierung während der Optimierung zu
verbessern eine Idee, die in conformallab++ noch nicht implementiert ist (→ GC-2
im Phasen-Roadmap).
---
## 8 — Checklist for an independent reviewer
Run these in order to validate the implementation:
- [ ] `ctest --test-dir build -R cgal --output-on-failure` 173 tests pass, 1 skip
- [ ] `cgal.GaussBonnet.*` all pass topology is correctly read from mesh
- [ ] `cgal.EuclideanFunctional.GradientCheck_*` pass energy = integral of gradient
- [ ] `cgal.PeriodMatrix.TauInFundamentalDomain_*` pass SL(2,) reduction correct
- [ ] `cgal.MobiusMap.Compose_*` and `Inverse_*` pass Möbius arithmetic correct
- [ ] `cgal.HolonomyData.*` pass holonomy loops close up
All of the above are **deterministic, analytic tests** no mesh loading, no
file I/O, no floating-point non-determinism beyond standard IEEE-754.

View File

@@ -0,0 +1,86 @@
# Java ConformalLab vs. conformallab++ — Feature Parity
Reference: [github.com/varylab/conformallab](https://github.com/varylab/conformallab)
Java package root: `de.varylab.discreteconformal`
When porting a Java class, locate the original in the Java repository and use it
as the reference implementation for expected behaviour, edge cases, and test cases.
---
## Algorithm parity
| Mathematical layer | Java ConformalLab | conformallab++ | Notes |
|---|---|---|---|
| Euclidean functional — energy, gradient | ✅ | ✅ | |
| Spherical functional — energy, gradient, gauge-fix | ✅ | ✅ | |
| HyperIdeal functional — energy, gradient | ✅ | ✅ | |
| Inversive-distance functional (Luo 2004) | ✅ | ❌ Phase 9a | `InversiveDistanceFunctional.java` |
| Euclidean Hessian — cotangent Laplacian | ✅ analytic | ✅ analytic | PinkallPolthier (1993) |
| Spherical Hessian — ∂α/∂u via law of cosines | ✅ analytic | ✅ analytic | |
| HyperIdeal Hessian — ζ → lᵢⱼ → β/α chain | ✅ analytic | ⚠️ symmetric FD | Phase 9b |
| Newton solver | ✅ | ✅ | |
| SparseQR fallback for gauge modes | unknown | ✅ | New in C++ |
| Cone metrics — prescribed Θᵥ ≠ 2π | ✅ fully | ⚠️ data structure only | |
| Layout / embedding — ℝ² / H² / S² | ✅ | ✅ priority-BFS all three | |
| Exact hyperbolic trilateration | ✅ Möbius | ✅ Möbius + law of cosines | |
| halfedge_uv — seam-aware UV (texture atlas) | ✅ | ✅ | |
| GaussBonnet consistency check | ✅ | ✅ | |
| Tree-cotree cut graph (2g edges) | ✅ | ✅ EricksonWhittlesey (2005) | |
| Holonomy — Euclidean (translations) | ✅ | ✅ | |
| Holonomy — Hyperbolic (SU(1,1) Möbius maps) | ✅ | ✅ | |
| Period matrix τ — genus 1, SL(2,)-reduced | ✅ | ✅ | |
| Fundamental domain — genus 1 | ✅ | ✅ CCW parallelogram | |
| 4g-polygon boundary walk — genus g > 1 | ✅ | ❌ Phase 9c | `FundamentalDomainUtility.java` |
| Siegel period matrix Ω — genus g ≥ 2 | ✅ | ❌ Phase 10b | |
| Global uniformization — genus g ≥ 2 | ✅ | ❌ Phase 10c | |
| Clausen / Lobachevsky / ImLi₂ | ✅ | ✅ | |
| Poincaré disk / Lorentz boost visualisation | ✅ | ✅ | |
| Mesh I/O + serialisation | ✅ XML/CoHDS | ✅ OFF/OBJ/PLY + JSON/XML | |
| Interactive viewer | ✅ jReality | ✅ libigl/GLFW | |
---
## Java utility classes not yet ported
These exist in `de.varylab.discreteconformal.util` in the Java library.
They are candidates for Phase 9 or Phase 10.
| Java class | Description | Phase |
|---|---|---|
| `InversiveDistanceFunctional` | Inversive-distance conformal energy | 9a |
| `DiscreteHarmonicFormUtility` | Discrete harmonic 1-forms | 10a prerequisite |
| `DiscreteHolomorphicFormUtility` | Holomorphic differentials on discrete surfaces | 10a |
| `DiscreteRiemannUtility` | Discrete Riemann surfaces | 10 |
| `CanonicalBasisUtility` | Canonical homology basis for genus g | 9c / 10 |
| `HomologyUtility` | Homology computation | 9c |
| `HomotopyUtility` | Homotopy generators | 9c |
| `SpanningTreeUtility` | Spanning tree algorithms | 8 / infrastructure |
| `SurgeryUtility` | Mesh surgery (cut/glue) | — |
| `StitchingUtility` | Seam stitching | — |
| `CuttingUtility` | Advanced cutting (beyond tree-cotree) | 9c |
| `HyperellipticUtility` | Hyperelliptic surfaces | 10 |
| `LaplaceUtility` | Discrete Laplace operators | 9 / infrastructure |
| `ConformalStructureUtility` | Conformal structure extraction | 10 |
---
## HyperIdeal Hessian: FD vs. analytic
The Java library computes the HyperIdeal Hessian analytically through the chain:
```
(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ / βᵢ
```
conformallab++ uses a **symmetric finite-difference approximation**:
```
H[i,j] = ( G(x + ε·eⱼ)[i] G(x ε·eⱼ)[i] ) / (2ε), ε = 1e-5
```
Accuracy: O(ε²) ≈ 10⁻¹⁰ relative error. PSD guaranteed by strict convexity (Springborn 2020).
Cost: n extra gradient evaluations per Newton step.
Impact: negligible for meshes < 500 DOFs; measurable for larger meshes.
The analytic Hessian is deferred to Phase 9b.

173
doc/roadmap/phases.md Normal file
View File

@@ -0,0 +1,173 @@
# Development Roadmap
> **Legend:** ✅ complete · 🔲 planned
>
> **Porting / research boundary:**
> Phases 17 are direct ports of the Java original and its dissertation.
> From Phase 8 onwards the work goes beyond the scope of the Java library.
> Phase 8 (CGAL package) is infrastructure. Phase 9 is porting of remaining Java features.
> Phase 10+ is independent research with no direct Java reference implementation.
---
## ◼ Porting complete — Phases 17
```
Phase 1 Clausen / Lobachevsky / ImLi₂ special functions ✅
Phase 2 Hyper-ideal geometry (ζ, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ) ✅
Phase 3 CGAL Surface_mesh infrastructure + all three functionals
(Euclidean, Spherical, HyperIdeal) + analytical Hessians ✅
Phase 4 Newton solver (SimplicialLDLT + SparseQR fallback)
+ Mesh I/O (OFF/OBJ/PLY) + example programs ✅ 68 tests
Phase 5 Priority-BFS layout + CLI app + JSON/XML serialisation ✅ 95 tests
Phase 6 GaussBonnet check/enforce, tree-cotree cut graph (2g),
exact hyperbolic trilateration, layout normalisation ✅ 121 tests
Phase 7 MobiusMap, halfedge_uv, Möbius holonomy (SU(1,1)),
period matrix τ∈ℍ + SL(2,) reduction,
fundamental domain parallelogram + tiling ✅ 158 tests
```
---
## ◼ Infrastructure — Phase 8: CGAL Package
Goal: conformallab++ as a standalone CGAL package, submission-ready, fulfilling all
CGAL package conventions with a traits-class design compatible with any CGAL-conforming
mesh type.
```
8a Traits class & concepts
→ include/CGAL/Conformal_map_traits.h
Separates MeshType, KernelType, ScalarType from the algorithm.
Enables use with any CGAL-compatible mesh, not just Surface_mesh.
→ Concept checks via static_assert / CGAL_concept_check
8b Public CGAL header hierarchy
→ include/CGAL/Discrete_conformal_map.h (user-facing entry header)
→ include/CGAL/Conformal_newton_solver.h
→ include/CGAL/Conformal_layout.h
→ include/CGAL/Conformal_cut_graph.h
All existing include/conformallab/*.hpp remain as implementation details.
8c CGAL-style documentation
→ doc/Conformal_map/PackageDescription.txt
→ doc/Conformal_map/fig/ (pipeline diagrams)
→ Doxygen comments on all public concepts and functions
→ User_manual.md + Reference_manual.md
8d CGAL test format
→ test/Conformal_map/CMakeLists.txt (CGAL-style CMake)
Existing GTest tests remain; CGAL-format tests are added alongside.
8e Declarative YAML pipeline
→ Lightweight YAML format for reproducible experiments
(specification in doc/api/cgal-package.md)
→ Validator: checks require/provide tokens before execution
→ CLI integration: conformallab_core --pipeline experiment.yml
```
---
## ◼ Remaining porting — Phase 9
Java features from `de.varylab.discreteconformal` not yet in C++:
```
9a Inversive-distance functional (Luo 2004 / BowersStephenson)
→ inversive_distance_functional.hpp
Follows the exact same pattern as the three existing functionals.
→ newton_inversive_distance()
→ New test suite: test_inversive_distance.cpp
9b Analytic HyperIdeal Hessian
→ Replace FD Hessian in hyper_ideal_hessian.hpp
Direct differentiation through the chain:
(bᵢ, aₑ) → lᵢⱼ → ζ₁₃/ζ₁₄/ζ₁₅ → αᵢⱼ / βᵢ
Relevant for meshes > 500 DOFs (current FD Hessian is slow there).
9c 4g-polygon boundary walk (genus g > 1)
→ Extend compute_fundamental_domain() beyond genus 1
Algorithm outline already in fundamental_domain.hpp as TODO(Phase 9).
Java reference: FundamentalDomainUtility.java
```
---
## ◼ Optional / Hypothetical — geometry-central Cross-Comparison
> **Status: keine geplante Phase — rein explorativ.**
> Diese Punkte sind keine Voraussetzung für Phase 810. Sie sind
> interessant, weil geometry-central (Keenan Crane, CMU) auf denselben
> mathematischen Grundlagen wie conformallab++ aufbaut — insbesondere auf
> **Springborn 2020** und der direkten Weiterentwicklung durch
> **Gillespie, Springborn & Crane (SIGGRAPH 2021)**.
> Der entscheidende Unterschied: geometry-central löst dasselbe Problem
> (diskrete konforme Äquivalenz) mit **intrinsischen Triangulierungen +
> Ptolemäischen Flips**, während conformallab++ **Newton auf der
> Original-Triangulierung** anwendet.
```
GC-1 [optional, jetzt möglich]
Mathematischer Output-Vergleich
→ gleiche Testnetze (cathead.obj, brezel.obj, torus_4x4.off) in
beide Bibliotheken laden
→ UV-Koordinaten, u-Vektor, Residualnorm vergleichen
→ Normalisierungskonventionen abgleichen (u-Mittelwert, Skalierung)
Ziel: unabhängige Kreuz-Validierung der Konvergenzpunkte.
Aufwand: kleines Python/C++ Vergleichsskript, kein Bibliotheks-Umbau.
GC-2 [optional, sinnvoll nach Phase 8]
Intrinsic Delaunay Pre-Conditioning
→ Vor dem Newton-Solver: geometry-central SignpostIntrinsicTriangulation
auf die Eingabe anwenden
→ Ptolemäische Flips konditionieren die Hessian-Matrix vor
→ Hypothese: weniger Newton-Iterationen auf nicht-Delaunay-Eingaben
→ Implementierbar als optionaler cmake-Flag: -DWITH_GC_PRECOND=ON
Abhängigkeit: geometry-central als optionale externe Abhängigkeit
(header-only Teile genügen für den Flip-Algorithmus).
GC-3 [hypothetisch, Phase 10+ Forschung]
Ptolemäische Flip-basierter Solver als alternativer Backend
→ Statt Newton: Ptolemäische Flips + penultimate-step Normalisierung
(GillespieSpringbornCrane 2021 Algorithmus)
→ Vergleich: Konvergenzradius, Robustheit auf pathologischen Netzen,
numerische Stabilität auf hohen Genus-Flächen
→ Für conformallab++ interessant, weil der Newton-Ansatz auf
stark nicht-Delaunay Netzen (z.B. nach Remeshing) instabil
werden kann.
Keine Implementierung geplant — Konzeptnotiz für Phase 10-Forschung.
```
**Verbindung zur Literatur:**
Das Springborn 2020-Papier ("Ideal Hyperbolic Polyhedra and Discrete
Uniformization") ist in conformallab++ als HyperIdeal-Geometriemodus
bereits implementiert (Phase 2/3). Die GillespieSpringbornCrane
2021-Erweiterung — die geometry-central implementiert — ergänzt dies um
intrinsische Triangulierungen und macht den Algorithmus robust gegen
schlechte Eingangs-Triangulierungen. Beide teilen denselben
mathematischen Kern (diskrete konforme Äquivalenz, GaussBonnet,
Variationsprinzip von BobenkoSpringborn 2004).
---
## ◼ New research — Phase 10+
No direct Java reference implementation exists for these items.
```
Phase 10 Global uniformization for genus g ≥ 2
10a Discrete holomorphic differentials
Integrate basis 1-forms ωᵢ along b-cycles of the cut graph.
Mathematical basis: BobenkoSpringborn (2004), §6.
Java partial reference: DiscreteHolomorphicFormUtility.java
10b Siegel period matrix Ω ∈ H_g (g×g complex symmetric, Im(Ω) > 0)
Ωᵢⱼ = ∫_{bⱼ} ωᵢ
Reduction to Siegel fundamental domain via Sp(2g,).
Requires: 10a
10c Full uniformization for genus g ≥ 2
Embedding as H²/Γ with Γ ⊂ PSL(2,) a Fuchsian group.
Requires: 10a + 10b + stable cut graph for g ≥ 2 (Phase 9c)
```

View File

@@ -0,0 +1,202 @@
# Tutorial: Porting the Inversive-Distance Functional (Phase 9a)
This is a complete, step-by-step example of how to add a new functional
to conformallab++. It ports `InversiveDistanceFunctional.java` from the
Java reference implementation (Luo 2004).
**Prerequisite:** Read [doc/api/extending.md](../api/extending.md) first
for the general pattern. This tutorial fills in every detail for one
specific case.
---
## Mathematical background
Inversive distance (Luo 2004) uses a different edge-length update formula:
```
Given inversive distances I_{ij} ∈ for each edge {i,j}:
cosh(l̃_{ij}) = I_{ij} · cosh((u_i + u_j) / 2)
+ (cosh²((u_i - u_j) / 2) - 1) · ...
```
For the Euclidean version the angle formula is the same law of cosines,
but the log-lengths Λ̃ are computed differently from the u-vector.
**Reference:** Luo, F. (2004). *Combinatorial Yamabe Flow on Surfaces*.
Communications in Contemporary Mathematics, 6(5), 765780.
**Java source:** `de.varylab.discreteconformal.functional.InversiveDistanceFunctional`
---
## Step 1 — Create the header
```bash
cp code/include/euclidean_functional.hpp \
code/include/inversive_distance_functional.hpp
```
Edit the new file:
```cpp
#pragma once
// inversive_distance_functional.hpp
//
// Phase 9a — Inversive-distance discrete conformal functional (Luo 2004).
// Ported from de.varylab.discreteconformal.functional.InversiveDistanceFunctional.
//
// Usage: identical to euclidean_functional.hpp — replace lambda0 with
// inversive_distance0 (the initial inversive distances per edge).
#include "conformal_mesh.hpp"
#include <Eigen/Dense>
#include <cmath>
namespace conformallab {
struct InversiveDistanceMaps {
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Edge_index, double> inv_dist0; ///< I_{ij} per edge
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Vertex_index, double> theta_v; ///< target corner angles Θ_v
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Vertex_index, int> v_idx; ///< DOF index (-1 = pinned)
};
// ... (follow euclidean_functional.hpp exactly, replacing lambda0 with inv_dist0
// and the angle formula with the Luo (2004) version)
```
---
## Step 2 — Implement the energy, gradient, and angle formula
The Euclidean angle formula uses the law of cosines on edge lengths
derived from log-lengths. For inversive distance, the edge lengths
are derived from inversive distances I_{ij} and the u-vector:
```cpp
// Euclidean (reference):
// lambda_ij = lambda0_ij + u_i + u_j
// l_ij = exp(lambda_ij / 2)
// Inversive distance (Luo 2004):
// cosh(l̃_ij) = I_ij * cosh((u_i + u_j) / 2) [simplified form]
// l̃_ij = acosh(I_ij * cosh((u_i + u_j) / 2))
```
Then the **corner angle** at vertex k opposite edge {i,j} in triangle {i,j,k}
is computed by the standard law of cosines from l̃_{ij}, l̃_{jk}, l̃_{ki}.
The **gradient** is the same as Euclidean:
```cpp
G_v = Σ_{faces containing v} alpha_v(face, u) Theta_v
```
---
## Step 3 — Write the gradient-check test
Create `code/tests/cgal/test_inversive_distance.cpp`:
```cpp
#include "inversive_distance_functional.hpp"
#include "mesh_factory.hpp"
#include <gtest/gtest.h>
// Finite-difference gradient check: copy the pattern from
// test_euclidean_functional.cpp :: EuclideanFunctional.GradientCheck_Triangle
TEST(InversiveDistance, GradientCheck_Triangle)
{
// Build a single equilateral triangle (open mesh, no boundary issues).
ConformalMesh mesh = MeshFactory::make_open_mesh(MeshFactory::Kind::triangle);
InversiveDistanceMaps maps = setup_inversive_distance_maps(mesh);
compute_inversive_distance0_from_mesh(mesh, maps); // I_ij from 3D edge lengths
const int n = /* count free DOFs */;
std::vector<double> x0(n, 0.0);
constexpr double eps = 1e-5;
auto G = inversive_distance_gradient(mesh, x0, maps);
for (int i = 0; i < n; ++i) {
auto xp = x0; xp[i] += eps;
auto xm = x0; xm[i] -= eps;
double Ep = inversive_distance_energy(mesh, xp, maps);
double Em = inversive_distance_energy(mesh, xm, maps);
double fd = (Ep - Em) / (2.0 * eps);
EXPECT_NEAR(G[i], fd, 1e-6) << "gradient mismatch at DOF " << i;
}
}
```
Run with:
```bash
./build/conformallab_cgal_tests --gtest_filter="InversiveDistance.*"
```
---
## Step 4 — Register in CMakeLists
Add to `code/tests/cgal/CMakeLists.txt` inside the `add_executable` block:
```cmake
# ── Phase 9a: Inversive-distance functional ────────────────────────────────
test_inversive_distance.cpp
```
---
## Step 5 — Add a Newton wrapper
In `code/include/newton_solver.hpp` add:
```cpp
/// Solve the inversive-distance conformal problem.
/// \see newton_euclidean() — identical structure.
inline NewtonResult newton_inversive_distance(
ConformalMesh& mesh,
std::vector<double> x0,
const InversiveDistanceMaps& m,
double tol = 1e-8,
int max_iter = 200)
{
// Copy newton_euclidean() exactly, replacing euclidean_* with
// inversive_distance_*. The Hessian is PSD (Luo 2004, Thm. 1),
// so SimplicialLDLT with SparseQR fallback applies directly.
}
```
---
## Step 6 — Verify against Java output
The Java library outputs text results for a triangulated torus. To compare:
1. Run Java ConformalLab on the same OFF mesh with Inversive-Distance mode.
2. Record the final gradient norm and angle sums.
3. Run the C++ equivalent and check:
```cpp
EXPECT_LT(res.grad_inf_norm, 1e-8);
EXPECT_LT(res.iterations, 50); // inversive-distance converges faster than hyper-ideal
```
**Java reference:** `InversiveDistanceFunctionalTest.java` in `de.varylab.discreteconformal.test`
---
## Checklist
- [ ] `code/include/inversive_distance_functional.hpp` compiles
- [ ] `GradientCheck_Triangle` passes
- [ ] `GradientCheck_OpenMesh` passes (copy from Euclidean tests)
- [ ] Newton converges on `torus_4x4.off`
- [ ] Result matches Java output (gradient norm < 1e-8 on same mesh)
- [ ] Registered in `CMakeLists.txt`
- [ ] `doc/roadmap/java-parity.md` updated: Phase 9a

76
scripts/try_it.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# try_it.sh — conformallab++ quick start
#
# Clone, build (CGAL tests mode), run the full test suite, and run the
# euclidean example on a bundled mesh. No system dependencies beyond a
# C++17 compiler, CMake ≥ 3.20, and Boost headers.
#
# Usage:
# cd ConformalLabpp
# bash scripts/try_it.sh
#
# Expected output (last lines):
# [PASS] 173 CGAL tests pass, 1 skipped
# [PASS] 36 non-CGAL tests pass
# [EXAMPLE] Converged in N iterations. ||G||_inf < 1e-9
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUILD_DIR="${REPO_ROOT}/build-try"
NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)
echo "========================================"
echo " conformallab++ — quick start"
echo " repo: ${REPO_ROOT}"
echo " build: ${BUILD_DIR}"
echo "========================================"
echo ""
# ── 1. Configure ──────────────────────────────────────────────────────────────
echo ">>> [1/4] CMake configure (CGAL tests, headless) …"
cmake -S "${REPO_ROOT}/code" -B "${BUILD_DIR}" \
-DWITH_CGAL_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-Wno-dev \
2>&1 | tail -5
echo ""
# ── 2. Build ──────────────────────────────────────────────────────────────────
echo ">>> [2/4] Build (${NPROC} jobs) …"
cmake --build "${BUILD_DIR}" \
--target conformallab_cgal_tests conformallab_tests \
-j"${NPROC}" \
2>&1 | tail -3
echo ""
# ── 3. Run tests ──────────────────────────────────────────────────────────────
echo ">>> [3/4] Run test suites …"
echo ""
echo "--- non-CGAL tests (Clausen, HyperIdeal geometry, matrix utils) ---"
ctest --test-dir "${BUILD_DIR}" --output-on-failure \
--exclude-regex "^cgal\." 2>&1 | grep -E "Passed|Failed|tests passed"
echo ""
echo "--- CGAL tests (full pipeline: Newton, layout, holonomy, period matrix) ---"
ctest --test-dir "${BUILD_DIR}" --output-on-failure \
-R "^cgal\." 2>&1 | grep -E "Passed|Failed|tests passed|Skipped"
echo ""
# ── 4. Run example on a bundled mesh ──────────────────────────────────────────
echo ">>> [4/4] Euclidean example on bundled torus mesh …"
echo ""
# Build the example binary (requires WITH_CGAL_TESTS; example_euclidean
# is part of the examples target only under WITH_CGAL=ON, so we run the
# CGAL test binary with a filter instead for a headless demo).
"${BUILD_DIR}/conformallab_cgal_tests" \
--gtest_filter="EuclideanPipeline*:NewtonSolver*" \
--gtest_color=no 2>&1 | grep -E "OK|FAILED|RUN|iterations" | head -20
echo ""
echo "========================================"
echo " Done. See doc/getting-started.md for next steps."
echo " Extend: doc/tutorials/add-inversive-distance.md"
echo " Validate: doc/math/validation-protocol.md"
echo "========================================"