Files
ConformalLabpp/CLAUDE.md
Tarik Moussa 26405be149
Some checks failed
C++ Tests / test-fast (push) Successful in 2m29s
C++ Tests / test-cgal (push) Failing after 1m51s
docs: add CLAUDE.md — context file for Claude Code
Covers build commands (all three modes), single-test invocation,
header-only architecture, the three geometry modes, Newton solver
sign conventions, layout BFS design, test patterns, CI structure,
and known quirks (Finder ` 2.hpp` duplicates, CGAL compile flags).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:47:01 +02:00

145 lines
7.3 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.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this project is
conformallab++ is a C++ reimplementation of Stefan Sechelmann's [ConformalLab](https://github.com/varylab/conformallab) Java library (TU Berlin, 2016). It solves one precise problem: given a triangulated surface, find a conformally equivalent metric satisfying prescribed curvature (angle-sum) constraints at each vertex. All code lives in `code/`.
The algorithmic foundation is Sechelmann's dissertation:
> *Variational Methods for Discrete Surface Parameterization: Applications and Implementation*, TU Berlin 2016. DOI: 10.14279/depositonce-5415
## Build commands
All three modes share the same source root `code/`:
```bash
# Mode 1 — fast tests only (no CGAL, no Boost, no display)
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 mode, requires Boost headers, no wayland/display)
# 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, requires Wayland/X11 dev headers)
cmake -S code -B build -DWITH_CGAL=ON
cmake --build build -j$(nproc)
```
**Important:** `-DWITH_CGAL=ON` automatically enables `-DWITH_VIEWER=ON`, which pulls in GLFW and requires `wayland-scanner`. Use `-DWITH_CGAL_TESTS=ON` for headless environments.
### Running a single test
```bash
# Non-CGAL test by name
ctest --test-dir build -R "Clausen" --output-on-failure
# CGAL test by name (prefix is always "cgal.")
ctest --test-dir build -R "cgal.NewtonSolver" --output-on-failure
# Or run the binary directly for full GTest output
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver*"
./build/conformallab_tests --gtest_filter="Clausen*"
```
## Architecture
### Everything is header-only
All algorithms live in `code/include/*.hpp`. There is no compiled library. The CMake targets (`conformallab_tests`, `conformallab_cgal_tests`, `conformallab_core`) all compile the headers directly via their test or app `.cpp` files.
### Central type: `ConformalMesh`
Defined in `conformal_mesh.hpp`:
```cpp
using ConformalMesh = CGAL::Surface_mesh<Point3>; // CGAL::Simple_cartesian<double>
```
Algorithms attach data via CGAL named property maps rather than intrusive vertex/edge types (replacing the Java CoHDS/CoVertex/CoEdge pattern). Property map naming convention:
- `"v:lambda"` — per-vertex log scale factor (the conformal variable uᵢ)
- `"v:theta"` — per-vertex target cone angle
- `"v:idx"` — solver DOF index (`-1` = pinned/boundary)
- `"e:alpha"` — per-edge intersection angle (hyperbolic geometry only)
### The three geometry modes
Each mode has its own Maps struct + functional + Hessian header:
| Mode | Headers | Maps struct | Newton function |
|------|---------|-------------|----------------|
| Euclidean (ℝ²) | `euclidean_functional.hpp`, `euclidean_hessian.hpp` | `EuclideanMaps` | `newton_euclidean()` |
| Spherical (S²) | `spherical_functional.hpp`, `spherical_hessian.hpp` | `SphericalMaps` | `newton_spherical()` |
| Hyper-ideal (H²) | `hyper_ideal_functional.hpp`, `hyper_ideal_hessian.hpp` | `HyperIdealMaps` | `newton_hyper_ideal()` |
Setup follows the same pattern for all three:
```cpp
EuclideanMaps maps = setup_euclidean_maps(mesh);
compute_euclidean_lambda0_from_mesh(mesh, maps); // initialise λ° from 3-D positions
maps.v_idx[*mesh.vertices().begin()] = -1; // pin one vertex (gauge fix)
int idx = 0;
for (auto v : mesh.vertices()) if (maps.v_idx[v] != -1) maps.v_idx[v] = idx++;
```
After `compute_*_lambda0_from_mesh()` the solver works entirely in scale-factor space; original vertex positions are no longer used.
### Pipeline
```
load_mesh() → ConformalMesh
setup_*_maps() → *Maps (property maps attached)
compute_*_lambda0_from_mesh() → λ° initialised
check/enforce_gauss_bonnet() → Σ(2πΘᵥ) = 2π·χ(M) [mandatory for closed meshes]
newton_*() → NewtonResult.x (converged scale factors)
compute_cut_graph() → CutGraph (2g seam edges, closed meshes only)
euclidean/spherical/hyper_ideal_layout() → Layout2D/3D + HolonomyData
normalise_*() → canonical position
compute_period_matrix() → PeriodData τ∈ℍ (genus 1 flat torus)
compute_fundamental_domain() → FundamentalDomain + tiling
save_result_json/xml() → serialised result
```
### Newton solver (`newton_solver.hpp`)
Sign conventions differ between modes — the solver handles this internally:
- Euclidean/HyperIdeal: `G_v = actual target`, H is PSD → `SimplicialLDLT(H)`
- Spherical: `G_v = target actual`, H is NSD → `SimplicialLDLT(H)`
When `SimplicialLDLT` fails (rank-deficient H on closed meshes without pinned vertex), the solver automatically retries with `SparseQR` to find the minimum-norm step. This is the gauge-mode fallback — public API: `solve_linear_system(H, rhs, &used_fallback)`.
### Layout (`layout.hpp`)
BFS-trilateration using a min-heap on BFS depth (priority BFS). Root face = largest 3-D area face. Key output fields:
- `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 for GPU texture atlasing
- `hol.translations[i]` — lattice generators ωᵢ (Euclidean/spherical)
- `hol.mobius_maps[i]` — Möbius isometry Tᵢ ∈ SU(1,1) (hyperbolic)
### Test design pattern
Tests use the **"natural theta"** trick to construct a known equilibrium at `x* = 0`:
```cpp
// Evaluate gradient at x=0, use actual angle sums as targets → x*=0 by construction
evaluate_euclidean_gradient(mesh, x0, maps);
for (auto v : mesh.vertices())
if (maps.v_idx[v] >= 0)
maps.theta_v[v] = /* actual angle sum from gradient evaluation */;
```
Gradient checks use finite differences: perturb `xᵢ ± ε`, verify `G(x) ≈ ∂E/∂x`.
## CI pipeline
Two jobs defined in `.gitea/workflows/cpp-tests.yml`:
- **`test-fast`** — no flags, Eigen+GTest only, runs on all branches
- **`test-cgal`** — `-DWITH_CGAL_TESTS=ON`, requires Boost (installed at runtime until Docker image is rebuilt), runs on `main`/`dev`/PRs only, needs `test-fast` to pass first
Docker image: `git.eulernest.eu/conformallab/ci-cpp:latest` (Ubuntu 22.04 ARM64). Dockerfile at `.gitea/docker/Dockerfile.ci-cpp`.
## Known issues / conventions
- Files ending in ` 2.hpp` (e.g. `clausen 2.hpp`, `hyper_ideal_utility 2.hpp`) are macOS Finder duplicates — ignore them, use the canonical name without ` 2`.
- `CGAL_DISABLE_GMP` and `CGAL_DISABLE_MPFR` are defined for all CGAL targets — CGAL runs in exact-predicates-inexact-constructions mode with `Simple_cartesian<double>`, which is intentional (conformal geometry does not need exact arithmetic).
- All deps are bundled as tarballs in `code/deps/tarballs/` and extracted at CMake configure time — no internet access needed at build time except for GTest (fetched via `FetchContent`).
- The `test-fast` job intentionally includes stubs that call `GTEST_SKIP()` — 2 intentional skips are expected in the CGAL suite, not regressions.