From 52604d8544124ceb60ca1989eb4808f70f0a99f9 Mon Sep 17 00:00:00 2001 From: Tarik Moussa Date: Mon, 18 May 2026 01:39:14 +0200 Subject: [PATCH] docs: Konzeptdokument Declarative YAML Pipeline (Phase 8e) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit doc/concepts/declarative-pipeline.md — vollständige Design-Spezifikation: 1. Kernidee: Processing Units mit expliziten require/provide-Contracts 2. Token-Vokabular: 30 Tokens in 7 Kategorien input, setup, Gauss-Bonnet, solver, topology, layout, period/domain/output 3. YAML-Schema: Vollständige Syntax inkl. Parameterdefaults aller Units 4. Validierungsalgorithmus: monoton wachsendes provided-Set, Pre-Execution-Check 5. 5 vollständige Beispiele: A — Euklidische Uniformisierung Torus (τ-Ausgabe) B — Sphärische Uniformisierung (cathead.obj) C — Hyperbolische Uniformisierung Torus (Poincaré-Disk) D — Volle Pipeline mit Periodenmatrix + 5×5-Kachelung E — Absichtlich fehlerhaftes Beispiel mit Validator-Fehlermeldungen 6. C++-Mapping: alle YAML-Unit-Namen → C++-Funktionen + Header 7. Implementierungsplan (Phase 8e): pipeline.hpp + CLI-App + YAML-Abhängigkeit 8. Design-Entscheidungen: YAML vs. JSON/TOML, explizit vs. auto-inference, linear vs. DAG, eine Geometrie pro Datei doc/api/cgal-package.md: Link zum Konzeptdokument ergänzt. README.md: Link in Dokumentationstabelle ergänzt. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 1 + doc/api/cgal-package.md | 5 +- doc/concepts/declarative-pipeline.md | 571 +++++++++++++++++++++++++++ 3 files changed, 576 insertions(+), 1 deletion(-) create mode 100644 doc/concepts/declarative-pipeline.md diff --git a/README.md b/README.md index d2d3292..c6ac008 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Layout2D layout = euclidean_layout(mesh, res.x, maps); | **Validation** — known analytic results + how to verify them | [doc/math/validation.md](doc/math/validation.md) | | **Validation protocol** — concrete commands with expected outputs | [doc/math/validation-protocol.md](doc/math/validation-protocol.md) | | **Tutorial: add a new functional** — step-by-step Inversive-Distance port | [doc/tutorials/add-inversive-distance.md](doc/tutorials/add-inversive-distance.md) | +| **Declarative YAML pipeline** — concept, token vocabulary, 5 examples | [doc/concepts/declarative-pipeline.md](doc/concepts/declarative-pipeline.md) | | **Geometry modes** — Euclidean / Spherical / HyperIdeal comparison | [doc/math/geometry-modes.md](doc/math/geometry-modes.md) | | **References** — all papers by module | [doc/math/references.md](doc/math/references.md) | | **Roadmap** — Phases 1–10 | [doc/roadmap/phases.md](doc/roadmap/phases.md) | diff --git a/doc/api/cgal-package.md b/doc/api/cgal-package.md index b48c350..e10a471 100644 --- a/doc/api/cgal-package.md +++ b/doc/api/cgal-package.md @@ -96,7 +96,10 @@ A lightweight YAML format for reproducible experiments. The CLI accepts `--pipeline experiment.yml`; the validator checks `require`/`provide` tokens before execution. -Example: +**Full concept & design specification:** [doc/concepts/declarative-pipeline.md](../concepts/declarative-pipeline.md) +— token vocabulary, validation algorithm, 5 complete examples, implementation plan. + +Abbreviated example: ```yaml pipeline: diff --git a/doc/concepts/declarative-pipeline.md b/doc/concepts/declarative-pipeline.md new file mode 100644 index 0000000..d57035f --- /dev/null +++ b/doc/concepts/declarative-pipeline.md @@ -0,0 +1,571 @@ +# Declarative YAML Pipeline — Concept & Design + +> **Status: Phase 8e — designed, not yet implemented.** +> This document is the authoritative design specification. +> Implementation target: `code/include/pipeline.hpp` + CLI flag `--pipeline`. + +--- + +## 1 — Core idea + +Every algorithm in conformallab++ is a **Processing Unit**: a function with +explicit preconditions (*require*) and guarantees (*provide*). +The contract table in [doc/api/contracts.md](../api/contracts.md) lists them all. + +A **declarative pipeline** is a YAML file that: +1. Lists the units to execute and their parameters. +2. Annotates each step with the tokens it `require`s and `provide`s. +3. Is validated **before any code runs** — the validator walks the dependency + graph and rejects the file if any `require` token is not yet provided by + a preceding step. + +The result is a **self-documenting, reproducible experiment** that can be +version-controlled, shared, and re-run identically. + +--- + +## 2 — Token vocabulary + +Tokens are short strings. Each Processing Unit consumes and produces a fixed +set of tokens. The validator treats them as a monotonically growing *provided set*. + +### Input tokens (provided by the `input:` block) +| Token | Meaning | +|---|---| +| `mesh` | A loaded, triangulated, oriented `ConformalMesh` | +| `mesh_closed` | Mesh has no boundary (required for cut graph) | +| `mesh_open` | Mesh has at least one boundary component | + +### Setup tokens +| Token | Produced by | Required by | +|---|---|---| +| `maps_euclidean` | `setup_euclidean_maps` | `lambda0_euclidean`, `gauss_bonnet`, `newton_euclidean` | +| `maps_spherical` | `setup_spherical_maps` | `lambda0_spherical`, `gauss_bonnet`, `newton_spherical` | +| `maps_hyper_ideal` | `setup_hyper_ideal_maps` | `lambda0_hyper_ideal`, `gauss_bonnet`, `newton_hyper_ideal` | +| `lambda0_euclidean` | `compute_euclidean_lambda0_from_mesh` | `gauss_bonnet_euclidean`, `newton_euclidean` | +| `lambda0_spherical` | `compute_spherical_lambda0_from_mesh` | `gauss_bonnet_spherical`, `newton_spherical` | +| `lambda0_hyper_ideal` | `compute_hyper_ideal_lambda0_from_mesh` | `gauss_bonnet_hyper_ideal`, `newton_hyper_ideal` | +| `dof_indices` | DOF assignment step | `newton_*` | + +### Gauss–Bonnet tokens +| Token | Produced by | +|---|---| +| `gauss_bonnet_euclidean` | `check_gauss_bonnet` or `enforce_gauss_bonnet` (Euclidean maps) | +| `gauss_bonnet_spherical` | same, Spherical maps | +| `gauss_bonnet_hyper_ideal` | same, HyperIdeal maps | + +### Solver tokens +| Token | Produced by | +|---|---| +| `x_euclidean` | `newton_euclidean` (converged) | +| `x_spherical` | `newton_spherical` (converged) | +| `x_hyper_ideal` | `newton_hyper_ideal` (converged) | + +### Topology tokens +| Token | Produced by | Required by | +|---|---|---| +| `cut_graph` | `compute_cut_graph` | `euclidean_layout` (closed mesh), `hyper_ideal_layout` | + +### Layout tokens +| Token | Produced by | +|---|---| +| `layout_euclidean` | `euclidean_layout` | +| `layout_spherical` | `spherical_layout` | +| `layout_hyper_ideal` | `hyper_ideal_layout` | +| `holonomy_euclidean` | `euclidean_layout` (with cut graph) | +| `holonomy_hyper_ideal` | `hyper_ideal_layout` (with cut graph) | + +### Period / domain tokens +| Token | Produced by | Required by | +|---|---|---| +| `period_matrix` | `compute_period_matrix` | `fundamental_domain` | +| `fundamental_domain` | `compute_fundamental_domain` | `tiling` | +| `tiling` | `tiling_neighbourhood` | output steps | + +### Output tokens +| Token | Produced by | +|---|---| +| `saved_layout` | `save_layout_off` | +| `saved_result` | `save_result_json` / `save_result_xml` | + +--- + +## 3 — YAML schema + +```yaml +pipeline: + name: # human-readable experiment name + geometry: euclidean # euclidean | spherical | hyper_ideal + description: | # optional multi-line description + ... + + input: + source: # mesh file (.off / .obj / .ply) + # Optional overrides: + theta_v: flat # flat (2π everywhere) | cone: | custom: + + steps: + - id: # unique step identifier + unit: # C++ function name (see token table) + require: [, ...] # tokens that must be in the provided set + provide: [, ...] # tokens added to provided set after this step + params: # optional parameter overrides + : + + output: + layout: # optional: save Layout2D as .off with UVs + json: # optional: save NewtonResult + Layout as JSON + xml: # optional: save NewtonResult + Layout as XML + tau: # optional: write τ (period matrix) as text + report: # optional: write human-readable summary +``` + +### Parameter defaults by unit + +| Unit | Parameter | Default | +|---|---|---| +| `newton_euclidean` | `tol` | `1e-8` | +| `newton_euclidean` | `max_iter` | `200` | +| `newton_spherical` | `tol` | `1e-8` | +| `newton_spherical` | `max_iter` | `200` | +| `newton_hyper_ideal` | `tol` | `1e-8` | +| `newton_hyper_ideal` | `max_iter` | `200` | +| `newton_hyper_ideal` | `hess_eps` | `1e-5` | +| `euclidean_layout` | `normalise` | `false` | +| `hyper_ideal_layout` | `normalise` | `false` | +| `spherical_layout` | `normalise` | `false` | +| `compute_period_matrix` | `reduce` | `true` (SL(2,ℤ)) | +| `tiling_neighbourhood` | `m` | `1` | +| `tiling_neighbourhood` | `n` | `1` | + +--- + +## 4 — Validation algorithm + +``` +provided = { "mesh" } ← always available after input is loaded + +if input.source has no boundary: + provided ← provided ∪ { "mesh_closed" } +else: + provided ← provided ∪ { "mesh_open" } + +for each step in pipeline.steps: + for token in step.require: + if token ∉ provided: + ERROR: "Step '' requires '' which is not yet provided. + Add a step that provides it before step ''." + provided ← provided ∪ step.provide + +for each output key in pipeline.output: + check that its required token is in provided + (e.g. 'tau' requires 'period_matrix') +``` + +The validator runs **before any C++ code executes**. If validation passes, +the steps are executed in order. There is no parallelism — steps are sequential. + +--- + +## 5 — Complete examples + +### Example A — Euclidean uniformization of a flat torus (genus 1) + +```yaml +pipeline: + name: flat_torus_euclidean + geometry: euclidean + description: | + Euclidean uniformization of the 4×4 torus of revolution. + Computes the period matrix τ and the fundamental domain parallelogram. + + input: + source: code/data/off/torus_4x4.off + + steps: + - id: setup + unit: setup_euclidean_maps + require: [mesh] + provide: [maps_euclidean] + + - id: lambda0 + unit: compute_euclidean_lambda0_from_mesh + require: [mesh, maps_euclidean] + provide: [lambda0_euclidean] + + - id: dofs + unit: assign_dof_indices_euclidean + require: [maps_euclidean] + provide: [dof_indices] + params: + pin_strategy: first_vertex # pin v₀, assign sequential to rest + + - id: gauss_bonnet + unit: enforce_gauss_bonnet + require: [maps_euclidean, lambda0_euclidean] + provide: [gauss_bonnet_euclidean] + + - id: solve + unit: newton_euclidean + require: [gauss_bonnet_euclidean, dof_indices] + provide: [x_euclidean] + params: + tol: 1.0e-10 + max_iter: 200 + + - id: cut + unit: compute_cut_graph + require: [mesh_closed] + provide: [cut_graph] + + - id: layout + unit: euclidean_layout + require: [x_euclidean, cut_graph] + provide: [layout_euclidean, holonomy_euclidean] + params: + normalise: true + + - id: period + unit: compute_period_matrix + require: [holonomy_euclidean] + provide: [period_matrix] + params: + reduce: true + + - id: domain + unit: compute_fundamental_domain + require: [holonomy_euclidean] + provide: [fundamental_domain] + + output: + layout: out/torus_layout.off + json: out/torus_result.json + tau: out/torus_tau.txt +``` + +**Expected output (`torus_tau.txt`):** +``` +tau = 0.000... + 0.9...i # Re(τ) ≈ 0 (4-fold symmetry), Im(τ) ≈ 1 +|tau| = 0.9... # ≥ 1 after SL(2,ℤ) reduction +``` + +--- + +### Example B — Spherical uniformization (genus 0) + +```yaml +pipeline: + name: cathead_spherical + geometry: spherical + description: | + Map the open cathead mesh to the sphere. + + input: + source: code/data/obj/cathead.obj + + steps: + - id: setup + unit: setup_spherical_maps + require: [mesh] + provide: [maps_spherical] + + - id: lambda0 + unit: compute_spherical_lambda0_from_mesh + require: [mesh, maps_spherical] + provide: [lambda0_spherical] + + - id: dofs + unit: assign_dof_indices_spherical + require: [maps_spherical] + provide: [dof_indices] + + - id: gauss_bonnet + unit: enforce_gauss_bonnet + require: [maps_spherical, lambda0_spherical] + provide: [gauss_bonnet_spherical] + + - id: solve + unit: newton_spherical + require: [gauss_bonnet_spherical, dof_indices] + provide: [x_spherical] + + - id: layout + unit: spherical_layout + require: [x_spherical] + provide: [layout_spherical] + params: + normalise: true # rotate centroid to north pole + + output: + json: out/cathead_spherical.json +``` + +--- + +### Example C — Hyperbolic uniformization (genus 1, Poincaré disk) + +```yaml +pipeline: + name: torus_hyperbolic + geometry: hyper_ideal + description: | + Hyperbolic (hyper-ideal) uniformization of the 8×8 torus. + Lays out the mesh in the Poincaré disk with correct Möbius holonomy. + + input: + source: code/data/off/torus_8x8.off + + steps: + - id: setup + unit: setup_hyper_ideal_maps + require: [mesh] + provide: [maps_hyper_ideal] + + - id: lambda0 + unit: compute_hyper_ideal_lambda0_from_mesh + require: [mesh, maps_hyper_ideal] + provide: [lambda0_hyper_ideal] + + - id: dofs + unit: assign_all_dof_indices + require: [maps_hyper_ideal] + provide: [dof_indices] + # HyperIdeal: all vertices AND edges are free DOFs — no vertex pinned. + + - id: gauss_bonnet + unit: enforce_gauss_bonnet + require: [maps_hyper_ideal, lambda0_hyper_ideal] + provide: [gauss_bonnet_hyper_ideal] + + - id: solve + unit: newton_hyper_ideal + require: [gauss_bonnet_hyper_ideal, dof_indices] + provide: [x_hyper_ideal] + params: + tol: 1.0e-10 + + - id: cut + unit: compute_cut_graph + require: [mesh_closed] + provide: [cut_graph] + + - id: layout + unit: hyper_ideal_layout + require: [x_hyper_ideal, cut_graph] + provide: [layout_hyper_ideal, holonomy_hyper_ideal] + params: + normalise: true # Möbius-centre to disk origin + + output: + layout: out/torus_disk.off + json: out/torus_disk.json +``` + +--- + +### Example D — Full pipeline with period matrix and tiling + +```yaml +pipeline: + name: torus_full + geometry: euclidean + + input: + source: code/data/off/torus_hex_6x6.off + + steps: + - id: setup + unit: setup_euclidean_maps + require: [mesh] + provide: [maps_euclidean] + + - id: lambda0 + unit: compute_euclidean_lambda0_from_mesh + require: [mesh, maps_euclidean] + provide: [lambda0_euclidean] + + - id: dofs + unit: assign_dof_indices_euclidean + require: [maps_euclidean] + provide: [dof_indices] + + - id: gauss_bonnet + unit: enforce_gauss_bonnet + require: [maps_euclidean, lambda0_euclidean] + provide: [gauss_bonnet_euclidean] + + - id: solve + unit: newton_euclidean + require: [gauss_bonnet_euclidean, dof_indices] + provide: [x_euclidean] + + - id: cut + unit: compute_cut_graph + require: [mesh_closed] + provide: [cut_graph] + + - id: layout + unit: euclidean_layout + require: [x_euclidean, cut_graph] + provide: [layout_euclidean, holonomy_euclidean] + params: + normalise: true + + - id: period + unit: compute_period_matrix + require: [holonomy_euclidean] + provide: [period_matrix] + + - id: domain + unit: compute_fundamental_domain + require: [holonomy_euclidean] + provide: [fundamental_domain] + + - id: tiling + unit: tiling_neighbourhood + require: [layout_euclidean, holonomy_euclidean] + provide: [tiling] + params: + m: 2 # 5×5 tile grid around origin + n: 2 + + output: + layout: out/hex_torus_layout.off + tau: out/hex_torus_tau.txt + json: out/hex_torus_full.json + report: out/hex_torus_summary.txt + +# Expected tau for 6-fold symmetric torus: +# Re(τ) ≈ 0.5, Im(τ) ≈ 0.866 (approaches e^{iπ/3} as mesh is refined) +``` + +--- + +### Example E — Validation error (intentional mistake) + +```yaml +pipeline: + name: broken_example + geometry: euclidean + + input: + source: code/data/off/torus_4x4.off + + steps: + - id: solve # ← WRONG: skipped setup and lambda0 + unit: newton_euclidean + require: [gauss_bonnet_euclidean, dof_indices] + provide: [x_euclidean] + + - id: layout + unit: euclidean_layout + require: [x_euclidean, cut_graph] # ← WRONG: cut_graph never provided + provide: [layout_euclidean] +``` + +**Validator output:** +``` +ERROR [step 'solve']: requires 'gauss_bonnet_euclidean' which is not yet provided. + Hint: add setup_euclidean_maps → compute_euclidean_lambda0_from_mesh + → enforce_gauss_bonnet before 'solve'. + +ERROR [step 'layout']: requires 'cut_graph' which is not yet provided. + Hint: add compute_cut_graph (requires: mesh_closed) before 'layout'. + +Pipeline rejected. 2 contract violations. +``` + +--- + +## 6 — Mapping to C++ code + +Each `unit:` name in the YAML maps directly to a C++ function: + +| YAML unit | C++ function | Header | +|---|---|---| +| `setup_euclidean_maps` | `conformallab::setup_euclidean_maps()` | `euclidean_functional.hpp` | +| `compute_euclidean_lambda0_from_mesh` | `conformallab::compute_euclidean_lambda0_from_mesh()` | `euclidean_functional.hpp` | +| `enforce_gauss_bonnet` | `conformallab::enforce_gauss_bonnet()` | `gauss_bonnet.hpp` | +| `assign_dof_indices_euclidean` | manual pin + sequential loop | `euclidean_functional.hpp` | +| `assign_all_dof_indices` | `conformallab::assign_all_dof_indices()` | `hyper_ideal_functional.hpp` | +| `newton_euclidean` | `conformallab::newton_euclidean()` | `newton_solver.hpp` | +| `newton_spherical` | `conformallab::newton_spherical()` | `newton_solver.hpp` | +| `newton_hyper_ideal` | `conformallab::newton_hyper_ideal()` | `newton_solver.hpp` | +| `compute_cut_graph` | `conformallab::compute_cut_graph()` | `cut_graph.hpp` | +| `euclidean_layout` | `conformallab::euclidean_layout()` | `layout.hpp` | +| `spherical_layout` | `conformallab::spherical_layout()` | `layout.hpp` | +| `hyper_ideal_layout` | `conformallab::hyper_ideal_layout()` | `layout.hpp` | +| `compute_period_matrix` | `conformallab::compute_period_matrix()` | `period_matrix.hpp` | +| `compute_fundamental_domain` | `conformallab::compute_fundamental_domain()` | `fundamental_domain.hpp` | +| `tiling_neighbourhood` | `conformallab::tiling_neighbourhood()` | `fundamental_domain.hpp` | +| `save_layout_off` | `conformallab::save_layout_off()` | `mesh_io.hpp` | +| `save_result_json` | `conformallab::save_result_json()` | `serialization.hpp` | + +--- + +## 7 — Implementation plan (Phase 8e) + +``` +code/include/pipeline.hpp ← YAML parser + validator + executor +code/apps/conformallab_pipeline.cpp ← CLI: conformallab_pipeline --pipeline foo.yml + +External YAML dependency (header-only, already bundled): + code/deps/single_includes/yaml-cpp/yaml.h ← or single-include yaml.hpp + Alternative: use the bundled single_includes/nlohmann/json.hpp for a + JSON-based pipeline format (simpler, no new dependency). +``` + +### Validator pseudocode + +```cpp +struct PipelineValidator { + std::set provided; + + void load_input(const YAML::Node& input) { + provided.insert("mesh"); + auto mesh = load_mesh(input["source"].as()); + if (is_closed(mesh)) provided.insert("mesh_closed"); + else provided.insert("mesh_open"); + } + + void validate_step(const YAML::Node& step) { + for (auto& tok : step["require"]) + if (!provided.count(tok.as())) + throw PipelineError("Step '" + step["id"].as() + + "' requires '" + tok.as() + "' not yet provided."); + for (auto& tok : step["provide"]) + provided.insert(tok.as()); + } + + void validate(const YAML::Node& pipeline) { + load_input(pipeline["input"]); + for (auto& step : pipeline["steps"]) + validate_step(step); + } +}; +``` + +--- + +## 8 — Design decisions + +**Why YAML and not JSON or TOML?** +YAML supports multi-line strings (for `description:`), comments (`#`), and +anchors/aliases — useful for parametric experiments. JSON lacks comments. +TOML lacks the list syntax needed for `require:` / `provide:`. + +**Why explicit require/provide instead of auto-inference?** +Auto-inference would require the validator to know all function signatures +at parse time — this couples the validator tightly to the C++ code. +Explicit tokens make the contract visible in the YAML, making it +self-documenting and readable without the source code. + +**Why sequential steps and not a DAG?** +The pipeline is a linear sequence for now (Phase 8e target). A DAG-based +executor (parallel steps where contracts allow) is a natural Phase 10+ extension +but adds significant complexity. Linear execution is correct and debuggable. + +**Why one YAML per geometry mode?** +A single YAML could support multiple geometries with conditional blocks, but +this adds syntactic complexity. The `geometry:` field at the top locks the mode +and keeps the YAML readable. Cross-geometry experiments can chain two pipelines.