docs: restructure documentation into focused files
README.md: reduced from 703 to ~75 lines — what/why, status, quick start, minimal usage example, navigation table to doc/ files. doc/architecture/overall_pipeline.md: trimmed — roadmap, extension points, declarative pipeline YAML, and references sections removed (each now has its own dedicated file). Replaced with a link table. New files: doc/getting-started.md — build modes, single-test invocation, CLI doc/api/pipeline.md — full pipeline API with code for all 3 geometries doc/api/extending.md — new functionals, geometry modes, Java porting guide doc/api/contracts.md — processing unit preconditions/provides table doc/api/cgal-package.md — Phase 8 CGAL package design + YAML pipeline (TODO) doc/math/geometry-modes.md — Euclidean/Spherical/HyperIdeal comparison doc/math/references.md — all papers by module doc/roadmap/phases.md — Phases 1–10 with porting/research boundary doc/roadmap/java-parity.md — Java vs C++ feature parity table doc/contributing.md — language policy, test standards, release flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
163
doc/api/cgal-package.md
Normal file
163
doc/api/cgal-package.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# 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.
|
||||
|
||||
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
69
doc/api/contracts.md
Normal 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.
|
||||
|
||||
---
|
||||
|
||||
## Gauss–Bonnet — the most common source of failure
|
||||
|
||||
Prescribing angles that violate Gauss–Bonnet 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
141
doc/api/extending.md
Normal 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.
|
||||
214
doc/api/pipeline.md
Normal file
214
doc/api/pipeline.md
Normal 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 Gauss–Bonnet (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 v1–v2)
|
||||
// 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));
|
||||
}
|
||||
```
|
||||
@@ -388,177 +388,14 @@ 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 1–7 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 1–7
|
||||
|
||||
```
|
||||
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 Gauss–Bonnet, 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` |
|
||||
| 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) |
|
||||
| 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 1–10) | [roadmap/phases.md](../roadmap/phases.md) |
|
||||
| Java vs. C++ feature parity table | [roadmap/java-parity.md](../roadmap/java-parity.md) |
|
||||
|
||||
88
doc/contributing.md
Normal file
88
doc/contributing.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 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` | 158 CGAL tests + 2 skips | `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 + 158 tests pass, exactly 2 skipped**.
|
||||
The 2 skips are intentional `GTEST_SKIP()` stubs for Phase 9b (analytic HyperIdeal Hessian)
|
||||
and Phase 9c (genus-2 homology). Do not remove them.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
142
doc/getting-started.md
Normal file
142
doc/getting-started.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# 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: **158 tests pass, 2 skipped** (intentional stubs for Phase 9 items).
|
||||
|
||||
### 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.
|
||||
|
||||
---
|
||||
|
||||
## 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/
|
||||
```
|
||||
112
doc/math/geometry-modes.md
Normal file
112
doc/math/geometry-modes.md
Normal 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.
|
||||
|
||||
---
|
||||
|
||||
## Gauss–Bonnet constraint
|
||||
|
||||
Before calling any Newton solver, the target angles must satisfy the Gauss–Bonnet equation:
|
||||
|
||||
```
|
||||
Σᵥ (2π − Θᵥ) = 2π · χ(M) (Euclidean / flat)
|
||||
Σᵥ (2π − Θᵥ) > 0 (spherical, χ > 0)
|
||||
Σᵥ (2π − Θᵥ) < 0 (hyperbolic, χ < 0)
|
||||
```
|
||||
|
||||
If this is violated, no conformal factor can realise the target angles and Newton will
|
||||
fail to converge without a visible error.
|
||||
|
||||
```cpp
|
||||
check_gauss_bonnet(mesh, maps); // throws if violated
|
||||
enforce_gauss_bonnet(mesh, maps); // redistributes residual uniformly across all vertices
|
||||
```
|
||||
|
||||
**This is the most common source of silent non-convergence.** Always call one of these before solving.
|
||||
35
doc/math/references.md
Normal file
35
doc/math/references.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 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 Laplace–Beltrami 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 |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
86
doc/roadmap/java-parity.md
Normal file
86
doc/roadmap/java-parity.md
Normal 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 | Pinkall–Polthier (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) | ✅ | ✅ | |
|
||||
| Gauss–Bonnet consistency check | ✅ | ✅ | |
|
||||
| Tree-cotree cut graph (2g edges) | ✅ | ✅ Erickson–Whittlesey (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.
|
||||
116
doc/roadmap/phases.md
Normal file
116
doc/roadmap/phases.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Development Roadmap
|
||||
|
||||
> **Legend:** ✅ complete · 🔲 planned
|
||||
>
|
||||
> **Porting / research boundary:**
|
||||
> Phases 1–7 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 1–7
|
||||
|
||||
```
|
||||
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 Gauss–Bonnet 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 / Bowers–Stephenson)
|
||||
→ 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ◼ 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: Bobenko–Springborn (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)
|
||||
```
|
||||
Reference in New Issue
Block a user