docs: 5-document meeting prep — tutorials + research note + status + architecture

External-reviewer-visit prep package (Springborn-Bobenko PhD alumnus,
2026-05-26).  All five documents target the same audience: a
mathematician who wants to evaluate, extend, or contribute to
conformallab++.  Goal: make the project maximally hackable BEFORE the
meeting.  Code unchanged in this commit — pure documentation.

Files added
───────────

1. **doc/tutorials/block-fd-hessian.md** (460 lines)
   Step-by-step tutorial on the per-face block-FD Hessian pattern
   shipped in Phase 9b (96× speed-up).  Matches the style of
   add-inversive-distance.md.  Covers:
   * The per-face locality lemma (mathematical justification).
   * Cost analysis (full-FD vs block-FD vs analytic).
   * Implementation walkthrough through face_angles_from_local_dofs +
     hyper_ideal_hessian_block_fd.
   * Porting checklist for applying the same pattern to a new
     functional.
   * The four cross-validation criteria.
   * When NOT to use block-FD + upgrade path to Phase 9b-analytic.

2. **doc/tutorials/add-output-uv-map.md** (477 lines)
   Tutorial for the `output_uv_map` named-parameter pattern shipped in
   PR #14.  Covers:
   * The UX problem (two-step pipeline → one-call wrapper).
   * The CGAL named-parameter mechanism + how the entry functions
     wire it (get_parameter + constexpr if).
   * Step-by-step recipe for adding a new named parameter (worked
     example: hypothetical `output_holonomy_map`).
   * The five test patterns for verification.
   * Why CP-Euclidean (face-DOF) and Inversive-Distance (Luo-edge-length)
     do not yet support output_uv_map — what is needed to add them.

3. **doc/math/hyperideal-hessian-derivation.md** (805 lines)
   Research-quality LaTeX-formatted derivation of the analytic
   HyperIdeal Hessian via the Schläfli identity (Phase 9b-analytic
   preparation).  Covers:
   * Schläfli identity (1858/60) — gradient and second-order form.
   * Derivatives of ζ, ζ₁₃, ζ₁₄, ζ₁₅ (all hyper-ideal-to-fully-ideal cases).
   * Chain rule for ∂β_i/∂(b,a) and ∂α_ij/∂(b,a) — case-split on the
     four α_ij branches.
   * Per-face 6×6 block formulas.
   * Acceptance criteria for the future implementation.
   * Implementation outline (Conformal_map header sketch).
   * Appendix A: sign / argument-order pitfalls reading the code.
   * References: Schläfli 1858, Milnor 1982, Vinberg 1993, Cho-Kim 1999,
     Rivin, Glickenstein 2011, Springborn 2020, BPS 2015.

4. **doc/roadmap/porting-status.md** (~250 lines)
   Operational snapshot of "where is each piece of Java math today"
   at v0.9.0.  Sections:
   * 25 000 lines of Java in one table (ported / worth porting /
     intentionally skipped breakdown).
   * Five DCE models — full status matrix with Java port status,
     Hessian type, Newton support, CGAL entry, UV-output capability.
   * Topology + solver infrastructure status.
   * CGAL public API map + known limitations (no chaining, Surface_mesh
     only, submission-readiness gaps).
   * Reverse cross-reference: Java class → C++ port location (or
     "skipped: replaced by …" / "in roadmap: phase X").
   * Things in C++ that the Java original does NOT have (research
     extensions track).
   * "How to use the library today" quickstart.

5. **doc/architecture/locked-vs-flexible.md** (~270 lines)
   12-item architecture-decision review with tier classification
   (🔴 load-bearing / 🟡 semi-fixed / 🟢 opportunistic).  Each item
   includes: locked-since date, cost to change, when to revisit,
   recommended posture for new contributors.  Key insight stated up
   front: "the load-bearing decisions are all good in 2026".  Closes
   with five open questions for the external reviewer — items where
   a second opinion would genuinely help (Phase 9c algorithm choice,
   Phase 10a priorities, analytic-Hessian payoff justification,
   CGAL upstream vs independent distribution, geometry-central
   cross-validation).

Total: ~2 250 lines across five new docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-22 13:38:53 +02:00
parent ff9c9ec11b
commit eb393537f3
5 changed files with 2270 additions and 0 deletions

View File

@@ -0,0 +1,281 @@
# Locked-in vs flexible architecture decisions
> **Purpose.** External collaborators (especially mathematicians
> evaluating whether to extend the library) need to know which
> design choices are **load-bearing** (changing them is expensive
> across the whole codebase) and which are **opportunistic** (made
> on first principles and easy to revisit).
>
> **Why this matters now.** A v0.9.0 + Springborn-Bobenko-alumnus
> external review (May 2026) is the right moment to surface these
> decisions before they ossify further. If any of the *locked*
> decisions need revisiting, this is the cheapest moment in the
> project's life to do so.
Three tiers:
```
🔴 LOAD-BEARING changing requires repo-wide refactoring
🟡 SEMI-FIXED changing affects multiple subsystems; possible but not casual
🟢 OPPORTUNISTIC changing is one-PR work
```
---
## 1. Mesh data structure → `CGAL::Surface_mesh<P>`
| | |
|--|--|
| Status | 🔴 load-bearing |
| Locked since | Phase 3 (2025) |
| Alternative considered | OpenMesh / pmp-library / custom halfedge / Java `CoHDS` literal port |
| Why locked | Every header `code/include/*.hpp` uses `ConformalMesh = CGAL::Surface_mesh<Point3>` and CGAL property maps explicitly. Phase 8a Traits provide a *concept* abstraction but the Default model is `Surface_mesh`-only. |
| Cost to change | ~3 weeks: rewrite traits, every functional, every test mesh factory. Touched in ~30 headers + ~25 test files. |
| Mitigation | Traits-based design (Phase 8a) means user-supplied mesh types CAN be added as Default-traits specialisations without touching algorithms — Phase 8a.2 plan documents this. But the Default model is Surface_mesh. |
| When to revisit | If a major user wants Polyhedron_3 or OpenMesh as default. Currently no concrete request → keep. |
**Recommended posture for an external contributor:** use the existing
Surface_mesh-based API for any new functional. Adding generic-FaceGraph
support is a single architectural step that doesn't need to be repeated
per functional — wait for one user to need it.
---
## 2. Floating-point kernel → `CGAL::Simple_cartesian<double>`
| | |
|--|--|
| Status | 🟡 semi-fixed |
| Locked since | Phase 3 (deliberate decision: conformal geometry doesn't need exact predicates) |
| Cost to change | Per-functional template parameterisation. The Phase 8 MVP wrapper already deduces the kernel from the mesh point type, so user code can specify any CGAL kernel — but the **legacy** `code/include/*.hpp` headers are hardcoded. |
| When to revisit | If a user reports floating-point catastrophic cancellation in the half-tangent angle formula on extreme meshes. Hasn't happened in 250 tests over 9 phases. |
**Recommended posture:** stay with `Simple_cartesian<double>`. If a
specific algorithm needs `Exact_predicates_inexact_constructions_kernel`
for robustness, parameterise that one algorithm — don't refactor the
whole codebase.
---
## 3. Header-only, no compiled library
| | |
|--|--|
| Status | 🔴 load-bearing |
| Locked since | Phase 1 |
| Cost to change | Significant: would need to introduce `.cpp` files, link order, ABI compatibility decisions. But everything in `code/include/*.hpp` is `inline` or template, so a header-only-to-compiled migration is mechanical. |
| Why locked | CGAL package convention is also header-only. Forking would break that goal. |
| When to revisit | If compilation times become prohibitive (currently < 30 s clean build with all 5 functionals). Or if a future cyclic dependency between functionals forces it. Neither is on the horizon. |
**Recommended posture:** stay header-only. This is also the de-facto
norm for CGAL packages of comparable scope.
---
## 4. Five DCE models on the same mesh
| | |
|--|--|
| Status | 🟡 semi-fixed |
| Locked since | Phase 9a (2026-05) when CP-Euclidean introduced face-DOFs |
| Why semi-fixed | Each model has its own `*Maps` bundle with its own property-map name prefix (`ev:`, `sv:`, `v:`, `cf:`/`ce:`, `iv:`/`ie:`) so all five can coexist on the same `CGAL::Surface_mesh`. Adding a sixth model means picking a new prefix + writing a new `*Maps` struct + Default trait. |
| Cost to add a sixth model | ~1 week (CP-Euclidean took ~3 days, Inversive-Distance ~3 days, Hessian + Newton + CGAL entry add another ~3 days). |
| When to revisit | If a unified base-Maps abstraction would actually win something (currently it would not the property-map sets differ in *kind*, not just in name). |
**Recommended posture for adding a new functional:**
1. Pick a 2-letter prefix not in `{ev, sv, v, cf, ce, iv, ie}`.
2. Define your `*Maps` struct with that prefix.
3. Define your `Default_*_traits<Surface_mesh, K>` class in a new
header `code/include/CGAL/Discrete_*.h`.
4. Wire it into `newton_solver.hpp` (template-copy from
`newton_inversive_distance` is the closest pattern for vertex-DOFs;
`newton_cp_euclidean` for face-DOFs).
5. Add a CGAL entry in the new header.
6. Add tests following [`add-inversive-distance.md`](../tutorials/add-inversive-distance.md).
This recipe has been validated three times now (CP-Euclidean, Inversive
Distance, and the four Phase-8b-Lite wrappers).
---
## 5. Newton solver with line search + SparseQR fallback
| | |
|--|--|
| Status | 🟡 semi-fixed |
| Locked since | Phase 4 |
| Cost to change | Each `newton_*` function is ~50-80 lines; replacing the solver across all five is ~2 days. |
| When to revisit | If a future functional needs trust-region or BFGS. None of the five currently does Newton converges quadratically near the optimum and the line search handles bad initial points. |
**Recommended posture:** Newton with line search is enough for any
strictly-convex variational problem. For non-convex variants, consider
adding a `newton_with_trust_region()` helper alongside, not replacing.
---
## 6. Eigen as the linear-algebra back-end
| | |
|--|--|
| Status | 🔴 load-bearing |
| Locked since | Phase 4 |
| Alternative considered | PETSc/Tao (Java original) / Boost.uBLAS / Blaze |
| Why locked | Eigen is header-only (no external dependency at build time), bundled as a CGAL dependency, fast, and offers `SimplicialLDLT + SparseQR` which the gauge-singular-mesh case needs. |
| Cost to change | Significant every Hessian header (`*_hessian.hpp`) and every Newton solver uses `Eigen::SparseMatrix` and `Eigen::VectorXd` directly. ~2 weeks repo-wide. |
| When to revisit | If a sparse-solver feature (e.g. parallel Cholesky) is needed that Eigen doesn't offer. |
**Recommended posture:** stay with Eigen.
---
## 7. CGAL public-API surface layout
| | |
|--|--|
| Status | 🟡 semi-fixed (Phase 8a-MVP design decision, 2026-05-19) |
| Locked since | PR #6 (v0.9.0) |
| Strategy chosen | "Strategy C" functional-specific Default traits, one entry function per functional, no fat unified trait. |
| Cost to change to unified trait | ~1 week refactor `Default_*_traits<>` into a single `Default_conformal_map_traits<>` with all property-map fields. Existing 8 tests would need updating. |
| When to revisit | When the first cross-functional algorithm (e.g. a hybrid functional that uses both face and vertex DOFs) lands. Speculation today. |
**Recommended posture:** stay with Strategy C. CGAL's own
`Polygon_mesh_processing` package follows the same convention one
default trait per algorithm family.
---
## 8. Named-parameter mechanism
| | |
|--|--|
| Status | 🟢 opportunistic |
| Locked since | Phase 8 MVP (2026-05-19) |
| Current state | Six tags: `vertex_curvature_map`, `fixed_vertex_map`, `gradient_tolerance`, `max_iterations`, `output_uv_map`, `normalise_layout`. No chaining yet. |
| Cost to extend with chaining | ~2 days. Generate CGAL-style member-function chainers via macros (CGAL has a `CGAL_add_named_parameter` macro for this). |
| When to revisit | At any time; this is the lowest-risk change in the codebase. Tutorials [`add-output-uv-map.md`](../tutorials/add-output-uv-map.md) §4 explains the mechanism. |
**Recommended posture:** add chaining when the first user complains. Or
when adding the 8th-10th named parameter (still 6 today manageable).
---
## 9. Property-map name conventions
| | |
|--|--|
| Status | 🟢 opportunistic |
| Locked since | Phase 3 + 9a (prefix `ev:`/`sv:`/`v:`/`cf:`/`ce:`/`iv:`/`ie:` set when each functional was introduced) |
| Cost to change | One sed-replace + recompile. No user-visible effect because the names are an *internal* convention; the CGAL public API never exposes them. |
| When to revisit | If a future functional reuses an existing letter prefix. Already discussed in [`locked-vs-flexible.md`](#4-five-dce-models-on-the-same-mesh). |
---
## 10. Tests: GTest, not CGAL's own test format
| | |
|--|--|
| Status | 🟡 semi-fixed |
| Locked since | Phase 1 |
| Cost to change | ~1 week rewrite test harnesses to CGAL's `test/Conformal_map/` convention. This is Phase 8d (planned for CGAL submission). |
| Why GTest now | Faster development cycle, IDE-friendly (Xcode / VSCode / CLion all have native GTest support). No CGAL submission is in progress yet. |
| When to revisit | When committing to CGAL submission (Phase 8c-d, decided to be a future commitment, see [`release-policy.md`](../release-policy.md)). |
**Recommended posture:** keep GTest as primary. When/if CGAL submission
happens, add a `test/Conformal_map/` shim that calls into the GTest
suite both formats can coexist.
---
## 11. License: MIT
| | |
|--|--|
| Status | 🔴 load-bearing |
| Locked since | Project inception |
| Cost to change | High organisational cost (requires consent of all contributors); ~no code cost. |
| Why locked | MIT was chosen for academic friendliness (citing, modifying, embedding). CGAL upstream requires LGPL for submitted packages. |
| Trade-off | Submitting to CGAL upstream is not possible without re-licensing. The codebase architecture is "CGAL-style" but the project would publish independently. |
| When to revisit | If/when a concrete CGAL upstream submission is decided. See [`release-policy.md`](../release-policy.md) for the formal policy. |
**Recommended posture:** stay with MIT. Build the "CGAL-style package
for external distribution" as the primary deliverable. Re-license only
when the CGAL editorial board commits to accepting the submission.
---
## 12. Documentation pattern: Markdown + Doxygen
| | |
|--|--|
| Status | 🟢 opportunistic |
| Locked since | Phase 7.5 (2026-05) |
| Current state | `code/include/*.hpp` carry Doxygen-style `///` comments (87% coverage); `doc/*.md` for prose; `Doxyfile` generates HTML in `doc/doxygen/`. |
| Cost to add more | Per-file basis; ~1 hour per header for full Doxygen. |
| When to revisit | When chasing CGAL-submission readiness (need `PackageDescription.txt` + `User_manual.md`). |
**Recommended posture:** keep adding `///` comments incrementally with
each new public function.
---
## Summary table
| Decision | Tier | Cost to change |
|-----------------------------------------|----------------|----------------------|
| 1. CGAL::Surface_mesh as default mesh | 🔴 load-bearing | ~3 weeks |
| 2. Simple_cartesian<double> kernel | 🟡 semi-fixed | per-functional |
| 3. Header-only architecture | 🔴 load-bearing | medium (mechanical) |
| 4. Five DCE models, separate Maps | 🟡 semi-fixed | ~1 week per new model |
| 5. Newton + line search + SparseQR | 🟡 semi-fixed | ~2 days |
| 6. Eigen back-end | 🔴 load-bearing | ~2 weeks |
| 7. Strategy C (per-functional traits) | 🟡 semi-fixed | ~1 week |
| 8. Named-parameter mechanism | 🟢 opportunistic | ~2 days (chaining) |
| 9. Property-map name conventions | 🟢 opportunistic | ~1 hour |
| 10. GTest, not CGAL test format | 🟡 semi-fixed | ~1 week |
| 11. MIT license | 🔴 load-bearing | organisational |
| 12. Markdown + Doxygen | 🟢 opportunistic | per-file |
**Key insight:** the **load-bearing decisions are all good in 2026**.
Surface_mesh + Eigen + header-only + MIT are the right defaults for a
research-quality CGAL-style package. The **semi-fixed decisions are
all behind one concrete blocker** (single user request, CGAL submission
commitment, etc.). The **opportunistic decisions are cheap to revisit
any time**.
The architecture is in a good place for the v0.9.0 → v0.10.0 transition.
No "expensive corner" has been painted into; every locked decision
matches the project's three-goal hierarchy in
[`research-track.md`](../roadmap/research-track.md).
---
## Open questions for the external reviewer
Items where the project would benefit from a second opinion:
1. **Phase 9c (4g-polygon) algorithm choice.** Two routes:
* Port the Java `FundamentalPolygonUtility` + `CanonicalFormUtility`
literally (~2 weeks).
* Or: re-derive from Springborn 2020 §5 using the existing
`cut_graph.hpp` + holonomy infrastructure (~3 weeks, cleaner
architecture).
Which is preferred? See [`phases.md`](../roadmap/phases.md) §Phase 9c.
2. **Phase 10a (forms) priorities.** Three sub-items
(`DiscreteHarmonicFormUtility`, `DiscreteHolomorphicFormUtility`,
`CanonicalBasisUtility`) interlock. Which to start with?
3. **Analytic Hessian payoff.** The Schläfli-based analytic HyperIdeal
Hessian (Phase 9b-analytic — derivation already written:
[`hyperideal-hessian-derivation.md`](../math/hyperideal-hessian-derivation.md))
would add another ~6× over block-FD. Is that worth ~2 weeks of
implementation effort for a working-mesh size on which?
4. **CGAL upstream vs independent distribution.** Does the reviewer
know a CGAL editor / has personal opinion on the LGPL-vs-MIT
trade-off?
5. **geometry-central cross-validation (GC-1).** Two libraries solve
the same DCE problem from different algorithmic directions
(Newton-on-mesh vs Ptolemaic-flips-on-intrinsic-triangulation). An
independent comparison would be a nice paper. Interested?