Files
ConformalLabpp/doc/architecture/overall_pipeline.md
Tarik Moussa f8686a073c
All checks were successful
C++ Tests / test-fast (push) Successful in 2m12s
C++ Tests / test-cgal (push) Has been skipped
docs: add phase roadmap with porting/research boundary to README and architecture doc
Structured the development roadmap into four blocks with an explicit
boundary marker separating direct Java ports (Phase 1–7) from
infrastructure (Phase 8), remaining porting (Phase 9), and new
research territory (Phase 10+).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:18:21 +02:00

565 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# conformallab++ — Architecture & Pipeline
## Origin
conformallab++ is a C++ reimplementation of
[ConformalLab](https://github.com/varylab/conformallab),
the Java research library for discrete conformal geometry by
**Stefan Sechelmann** (TU Berlin, Institut für Mathematik,
SFB/Transregio 109 *Discretization in Geometry and Dynamics*).
The algorithmic foundation is his doctoral dissertation:
> 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) · CC BY-SA 4.0
The dissertation develops the variational framework for discrete conformal equivalence:
discrete uniformization of Riemann surfaces, cone metrics, period matrices, and
holonomy — all of which are directly implemented in this library.
Further links:
**Java original:** [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/)
---
## Positioning
conformallab++ is a **specialised research library for discrete conformal geometry** on
triangulated surfaces. It is not a general geometry-processing framework.
The library revives the algorithms of the original ConformalLab by Stefan Sechelmann
in a modern C++ setting. Its core concern is one precise mathematical question:
> Given a triangulated surface, find a conformally equivalent metric that satisfies
> prescribed curvature (angle-sum) constraints at each vertex.
Everything in the library serves this goal:
| What it is | What it is not |
|------------|----------------|
| Discrete conformal maps (Euclidean, spherical, hyperbolic) | General mesh processing |
| Newton solver for angle-sum energy functionals | Remeshing / boolean / smoothing |
| Priority-BFS layout into ℝ², S², Poincaré disk | Point-cloud or implicit-field processing |
| Holonomy, period matrices, fundamental domains | NURBS / parametric modelling |
| Research platform — experiment-first, CLI-ready | Production rendering engine |
**Relation to existing libraries.**
conformallab++ sits *on top of* CGAL, not beside it.
`CGAL::Surface_mesh` is the internal mesh representation; there is no additional
abstraction layer. Eigen handles all linear algebra. libigl provides the optional
interactive viewer. The library adds the conformal-geometry layer that none of these
provide.
---
## The conformal geometry pipeline
The full pipeline runs in three stages. All stages operate on the same
`ConformalMesh` (= `CGAL::Surface_mesh<Point3>` with attached property maps).
```mermaid
graph TD
IN["Input\n(OFF / OBJ / PLY / builder)"]
ADAPTER["Input Adapter\nload_mesh() · make_triangle() · make_quad_strip() …"]
subgraph PRE["① PREPROCESSING"]
MAPS["Maps Setup\nsetup_*_maps() + compute_lambda0_from_mesh()"]
DOF["DOF Assignment\nv_idx[v] · e_idx[e] · pin / free"]
THETA["Target Angles\ntheta_v[v] — cone metric or natural equilibrium"]
GB["GaussBonnet Check\ncheck_gauss_bonnet() / enforce_gauss_bonnet()"]
MAPS --> DOF --> THETA --> GB
end
subgraph CORE["② PROCESSING CORE"]
NEWTON["Newton Solver\nnewton_euclidean/spherical/hyper_ideal()\n→ NewtonResult x* ∈ ℝⁿ"]
CG["Cut Graph [closed surfaces]\ncompute_cut_graph()\n→ CutGraph (2g seam edges)"]
LAYOUT["Layout / Embedding\neuclidean/spherical/hyper_ideal_layout()\n→ Layout2D / Layout3D\n · uv[v] · halfedge_uv[h]\n · HolonomyData"]
NORM["Normalisation\nnormalise_euclidean / _hyperbolic / _spherical()"]
PERIOD["Period Matrix [genus 1]\ncompute_period_matrix()\n→ PeriodData τ ∈ "]
FD["Fundamental Domain\ncompute_fundamental_domain()\n→ FundamentalDomain + tiling"]
NEWTON --> CG --> LAYOUT --> NORM --> PERIOD --> FD
end
subgraph POST["③ POSTPROCESSING"]
SERIAL["Serialisation\nsave_result_json/xml()\nsave_layout_off()"]
VIZ["Visualisation\nexample_viewer (libigl / GLFW)"]
CLI["CLI App\nconformallab_core -i -g -o -j -x -s"]
end
IN --> ADAPTER --> PRE
PRE --> CORE
CORE --> POST
style PRE fill:#dbeafe,stroke:#3b82f6,stroke-width:2px,color:#000
style CORE fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#000
style POST fill:#fef9c3,stroke:#ca8a04,stroke-width:2px,color:#000
```
---
## Stage ① — Preprocessing
### Input adapter
All mesh input flows through a single entry point:
```cpp
ConformalMesh mesh = load_mesh("input.off"); // OFF · OBJ · PLY
ConformalMesh mesh = make_quad_strip(); // built-in test meshes
```
**Precondition guarantee:** the adapter ensures the mesh is a valid, orientable,
triangulated surface with consistent halfedge structure (CGAL validity).
Downstream stages never check for degeneracies — they trust the adapter.
### Maps setup
Each geometry mode has its own maps struct that attaches property maps to the mesh:
| Geometry | Setup function | Key property maps |
|----------|---------------|-------------------|
| Euclidean (ℝ²) | `setup_euclidean_maps()` | `lambda0[e]`, `theta_v[v]`, `v_idx[v]` |
| Spherical (S²) | `setup_spherical_maps()` | `lambda0[e]`, `theta_v[v]`, `v_idx[v]` |
| Hyper-ideal (H²) | `setup_hyper_ideal_maps()` | `beta_v[v]`, `alpha_e[e]`, `v_idx[v]`, `e_idx[e]` |
`compute_*_lambda0_from_mesh()` initialises log-edge-lengths from the 3-D vertex
positions. After this step the solver works entirely in scale-factor space; the
original vertex positions are no longer needed.
### DOF assignment & target angles
```cpp
// Pin first vertex (gauge fix for open meshes)
maps.v_idx[*mesh.vertices().begin()] = -1;
int idx = 0;
for (auto v : rest_of_vertices) maps.v_idx[v] = idx++;
// Set target curvature (cone metric or natural equilibrium)
maps.theta_v[v] = 2 * M_PI; // flat interior vertex
maps.theta_v[v] = M_PI / 3; // 60° cone singularity
```
**Natural equilibrium shortcut:** evaluate the gradient at `x = 0`, subtract it from
`theta_v` — the solver then converges to `x* = 0` identically (useful for tests).
### GaussBonnet check
Before solving, the prescribed angles must satisfy:
$$\sum_{v} (2\pi - \Theta_v) = 2\pi \cdot \chi(M)$$
```cpp
check_gauss_bonnet(mesh, maps); // throws if violated
enforce_gauss_bonnet(mesh, maps); // distributes residual uniformly
```
**This is the most common source of silent Newton non-convergence.**
Prescribing angles that violate GaussBonnet means no conformal factor exists —
the solver will iterate without converging.
---
## Stage ② — Processing core
### The three geometry modes
conformallab++ implements discrete conformal geometry in three model spaces:
| Mode | Space | Curvature | Typical surfaces |
|------|-------|-----------|-----------------|
| **Euclidean** | ℝ² | K = 0 | Flat tori, developable surfaces, open patches |
| **Spherical** | S² | K = +1 | Genus-0 (sphere-like) surfaces |
| **Hyper-ideal** | H² (Poincaré disk) | K = 1 | Genus-g surfaces (g ≥ 1), hyperbolic structures |
All three share the same algorithmic structure — only the angle formula, the
trilateration geometry, and the Hessian sign differ.
### Newton solver
```
NewtonResult newton_euclidean (mesh, x0, maps [, tol, max_iter])
NewtonResult newton_spherical (mesh, x0, maps [, tol, max_iter])
NewtonResult newton_hyper_ideal(mesh, x0, maps [, tol, max_iter, hess_eps])
```
Each iteration:
1. Evaluate gradient **G** (angle-sum defect per vertex)
2. Evaluate Hessian **H** (analytical for Euclidean/Spherical; symmetric FD for HyperIdeal)
3. Solve **H·Δx = G** — try `SimplicialLDLT`, fall back to `SparseQR` on rank deficiency
4. Backtracking line search (up to 20 halvings)
**Preconditions:** mesh triangulated + manifold · GaussBonnet satisfied · DOFs assigned
**Provides:** `NewtonResult.x` — converged scale factors; `converged`, `iterations`, `grad_inf_norm`
**SparseQR fallback** handles gauge modes on closed meshes without a pinned vertex.
The fallback is public API: `solve_linear_system(H, rhs, &used_fallback)`.
### Cut graph (closed surfaces)
For a closed genus-g surface, cutting along 2g independent homology cycles
turns it into a topological disk — a prerequisite for globally consistent BFS layout.
```cpp
CutGraph cg = compute_cut_graph(mesh);
// cg.cut_edge_flags[e.idx()] — true for seam edges
// cg.cut_edge_indices — ordered list of 2g seam edges
// cg.genus — g
```
**Algorithm:** tree-cotree decomposition (EricksonWhittlesey 2005).
**Preconditions:** closed, orientable, triangulated mesh
**Provides:** exactly `2g` seam edges whose removal makes the surface simply connected
### Layout / Embedding
BFS-trilateration unfolds the mesh into the target geometry.
The root face (largest 3-D area, 1.5× interior bonus) is placed first;
all other faces are placed in **priority order by BFS depth** (min-heap),
minimising trilateration error accumulation.
```cpp
HolonomyData hol;
Layout2D layout = euclidean_layout(mesh, result.x, maps, &cg, &hol, /*normalise=*/true);
Layout3D slayout = spherical_layout(mesh, result.x, smaps);
Layout2D hlayout = hyper_ideal_layout(mesh, result.x, hmaps, &cg, &hol);
```
**Key outputs:**
| Field | Content |
|-------|---------|
| `layout.uv[v.idx()]` | Primary UV — first / shallowest-BFS visit |
| `layout.halfedge_uv[h.idx()]` | UV of `source(h)` as seen from `face(h)` — seam-aware |
| `layout.has_seam` | True when a vertex was reached via two different paths |
| `hol.translations[i]` | Translation ω_i across cut edge i (Euclidean / spherical) |
| `hol.mobius_maps[i]` | Möbius isometry T_i ∈ SU(1,1) across cut edge i (hyperbolic) |
**`halfedge_uv` — texture atlas semantics:**
At a seam edge the two opposite halfedges carry *different* UV values.
This gives each face its own UV copy of a seam vertex, enabling
a proper GPU texture atlas without vertex duplication.
**Trilateration — geometry by mode:**
| Mode | Method | Accuracy |
|------|--------|---------|
| Euclidean | Analytic formula in ℝ² | Exact |
| Spherical | Spherical law of cosines on S² | Exact |
| Hyper-ideal | Möbius + hyperbolic law of cosines in Poincaré disk | Exact |
**Preconditions:** `NewtonResult.converged` · mesh · maps · optional `CutGraph`
**Provides:** `Layout2D/3D` with `uv`, `halfedge_uv` · `HolonomyData` with translations / Möbius maps
### Möbius maps
`MobiusMap` (T(z) = (az+b)/(cz+d)) is the central algebraic object for hyperbolic geometry:
```cpp
MobiusMap T = MobiusMap::from_three(z1,w1, z2,w2, z3,w3); // fit to 3 correspondences
MobiusMap S = T.inverse().compose(U); // group operations
Eigen::Vector2d p2 = T.apply(p); // apply to 2-D point
```
Used for: hyperbolic trilateration · holonomy tracking · normalisation centering.
### Normalisation
After layout, a canonical post-processing step brings the result into a standard position:
| Mode | Method | Effect |
|------|--------|--------|
| Euclidean | PCA — centroid → origin, major axis → x-axis | Translation + rotation |
| Hyperbolic | Weighted Möbius centering (Fréchet mean, 30 iterations) | Maps centroid to disk origin |
| Spherical | Rodrigues rotation | Maps centroid to north pole |
Both `uv` and `halfedge_uv` are transformed identically.
### Period matrix (genus 1)
From the two holonomy translations ω₁, ω₂ ∈ read off from the cut graph,
the conformal type of a flat torus is the SL(2,)-orbit of:
$$\tau = \omega_2 / \omega_1 \in \mathbb{H}$$
```cpp
PeriodData pd = compute_period_matrix(hol);
// pd.tau — complex period ratio
// pd.omega[i] — lattice generators as complex numbers
// pd.in_fundamental_domain — after SL(2,) reduction
// pd.genus() — g = omega.size() / 2
```
SL(2,) reduction alternates T: τ↦τ+1 and S: τ↦1/τ steps until
τ ∈ F = {|τ| ≥ 1, −½ ≤ Re(τ) < ½}.
**Note:** The Siegel period matrix Ω ∈ H_g for genus g ≥ 2 requires integrating
holomorphic differentials — deferred to Phase 8.
### Fundamental domain
```cpp
FundamentalDomain fd = compute_fundamental_domain(hol);
// genus 1: CCW parallelogram {0, ω₁, ω₁+ω₂, ω₂}
// genus g > 1: empty — 4g-polygon boundary walk deferred to Phase 8
auto tiles = tiling_neighbourhood(layout, hol, /*m_max=*/2, /*n_max=*/2);
// returns (2·m_max+1)·(2·n_max+1) translated copies of the layout
// for visualising the universal cover
```
---
## Stage ③ — Postprocessing
### Serialisation
```cpp
// Layout as mesh file
save_layout_off("layout.off", mesh, layout);
// Full result: DOF vector + metadata + layout UVs
save_result_json("result.json", result, "euclidean", V, F, &layout);
save_result_xml ("result.xml", result, "euclidean", V, F, &layout);
// Round-trip load
NewtonResult res2; std::string geom; Layout2D uv2;
load_result_json("result.json", &res2, &geom, &uv2);
```
### CLI app
```bash
conformallab_core \
-i input.off # input mesh
-g euclidean # geometry: euclidean | spherical | hyper_ideal
-o layout.off # layout output
-j result.json # JSON serialisation
-x result.xml # XML serialisation
-s # show input in viewer
-v # verbose solver output
```
### Interactive viewer
`example_viewer` (libigl / GLFW) shows the 3-D mesh and the 2-D layout
side-by-side. Built automatically with `-DWITH_CGAL=ON`.
---
## Processing unit contracts
Each stage has explicit preconditions and guarantees.
A pipeline is valid if every unit's preconditions are satisfied
by the outputs of all preceding units.
| Unit | Preconditions | Provides |
|------|--------------|---------|
| `load_mesh` | Valid file path, supported format | Manifold, oriented, triangulated `ConformalMesh` |
| `setup_*_maps` | Triangulated mesh | Initialised property maps; `lambda0` from 3-D positions |
| `check_gauss_bonnet` | `theta_v` set | Throws if Σ(2πΘ_v) ≠ 2π·χ |
| `enforce_gauss_bonnet` | `theta_v` set | Σ(2πΘ_v) = 2π·χ guaranteed |
| `newton_*` | GB satisfied · DOFs assigned | `NewtonResult.converged` · `x*` · gradient norm |
| `compute_cut_graph` | Closed, orientable mesh | `2g` seam edges · `CutGraph.genus` |
| `euclidean_layout` | `newton_euclidean` converged | `uv[v]` · `halfedge_uv[h]` · `HolonomyData` |
| `normalise_euclidean` | `layout.success == true` | Centroid at origin · major axis = x-axis |
| `compute_period_matrix` | `hol.translations.size() >= 2` | `τ ∈ ` · optionally reduced to F |
| `compute_fundamental_domain` | `HolonomyData` (genus 1) | CCW parallelogram · edge identifications |
---
## The three geometry modes in detail
```
Euclidean Spherical Hyper-ideal
─────────────────────────────────────────────────────
Space ℝ² S² H² (Poincaré disk)
Curvature K = 0 K = +1 K = 1
Genus any (cone metrics) 0 ≥ 1
Angle sum Σα_v = Θ_v Σα_v = Θ_v Σβ_v = Θ_v
Hessian PSD (cotangent-Lap.) NSD (sign-flip) PSD (FD, strict conv.)
Holonomy translations ω_i rotations (2-D) Möbius maps T_i ∈ SU(1,1)
Period τ = ω₂/ω₁ ∈ — axis of T_i
Normalise PCA centring Rodrigues to N pole weighted Möbius centring
```
---
## Extension points
### 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
| | |
|---|---|
| **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` |