Files
ConformalLabpp/doc/api/pipeline.md
Tarik Moussa 14134b99ce
Some checks failed
C++ Tests / test-fast (push) Successful in 2m25s
C++ Tests / test-cgal (push) Failing after 1m58s
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>
2026-05-17 21:17:15 +02:00

5.9 KiB
Raw Permalink Blame History

Pipeline API Reference

The full pipeline from mesh loading to layout and serialisation. See doc/architecture/overall_pipeline.md for the Mermaid diagram and stage descriptions.


Minimal Euclidean pipeline

#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)

#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

#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

#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

#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

#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

// 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

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));
}