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:
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));
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user