Replaces the "Springborn / Bobenko alumnus" placeholder in the reviewer materials (briefing, questions, agenda, README) and in locked-vs-flexible.md with a research-profile description: active researcher in the decorated-DCE / Penner-coordinates / canonical-tessellations / hyperideal-polyhedra line, treated as a peer most likely to USE conformallab++ as numerical infrastructure for their own future experiments — not merely to evaluate it. Citations to the published literature (Springborn 2020 paper, etc.) remain untouched. Only personal references to the prospective reviewer were anonymised. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
311 lines
20 KiB
Markdown
311 lines
20 KiB
Markdown
# 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 snapshot + external review by
|
||
> an active researcher in the decorated-DCE / canonical-tessellation /
|
||
> hyperideal-polyhedra line 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`. Pipe-operator `|` chaining shipped (in lieu of `.member()` chaining which would require CGAL upstream modifications). |
|
||
| Cost to extend with `.member()` chaining | ~2 days IF CGAL upstream is forked / patched; otherwise the pipe-operator workaround is the maintainable path. |
|
||
| 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:** the pipe-operator `|` is already shipped and
|
||
sufficient. Add `.member()` chaining only if a concrete user pushes for
|
||
the CGAL-canonical syntax AND we are willing to fork CGAL upstream.
|
||
|
||
---
|
||
|
||
## 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 §4 "Five DCE models on the same mesh" above. |
|
||
|
||
---
|
||
|
||
## 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 | pipe ✅; .member() ~2 days |
|
||
| 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).
|
||
|
||
---
|
||
|
||
## Known limitations (state at the time of the reviewer meeting)
|
||
|
||
These are deliberate, honestly-flagged gaps in the v0.9.0 snapshot the
|
||
reviewer will see. None of them are load-bearing — each is a small,
|
||
mechanical next step rather than a missing piece of theory.
|
||
|
||
| Limitation | Status | Effort to close |
|
||
|---|---|---|
|
||
| **`output_uv_map` covers 4 of 5 entries** — Euclidean, Spherical, HyperIdeal, **and Inversive-Distance** (new on the structural-tests branch) are wired to call the appropriate `*_layout()` after Newton. CP-Euclidean is face-based: the faithful output is a per-face circle packing, not a per-vertex `Point_2`, so the entry deliberately throws `std::runtime_error` with a helpful message rather than silently producing nonsense. Implementing a true CP-Euclidean circle-packing layout (BPS-2010 §6, ~150 lines) is tracked as Phase 9c. | Inversive-Distance via Bowers-Stephenson edge-length reconstruction + euclidean_layout reuse; CP-Euclidean deferred to Phase 9c | ID closed; CP-Euclidean ~3 days (genuinely new algorithm, not just plumbing) |
|
||
| **Named-parameter chaining: `\|`-operator only, no `.a().b().c()`.** Member-style chaining would need a patch to CGAL's upstream `parameters_interface.h`, which we treat as a read-only vendored dependency. The pipe operator is documented, ADL-discoverable, and equivalent in expressive power. | pipe shipped, member-chain deferred until upstream extension point exists | ~2 days (only if upstream PR is accepted) |
|
||
| **Phase 9b-analytic: derivation complete, code uses block-FD.** The Schläfli-based analytic HyperIdeal Hessian is fully derived in [`hyperideal-hessian-derivation.md`](../math/hyperideal-hessian-derivation.md) (805 lines, all sign pitfalls covered). The shipped code still uses per-face block-FD (already 96× faster than the legacy full-FD path). | research-ready writeup; implementation gated on the reviewer's view of whether the additional ~6× is worth it | ~2 weeks |
|
||
| **Doxygen `WARN_IF_UNDOCUMENTED = NO`.** With `EXTRACT_ALL = YES`, every symbol is in the generated HTML — live at <https://tmoussa.codeberg.page/ConformalLabpp/> (auto-published from `main` by `.gitea/workflows/doxygen-pages.yml`) — but symbols without explicit doc comments show only their signature. Public API surface (entry functions, named-parameter helpers, traits typedefs) has hand-written Doxygen; internal helpers vary. | clean (0 warnings) under current policy; not yet enforced "no undocumented symbol"; pursued on a separate branch | ~3 days to drive `WARN_IF_UNDOCUMENTED = YES` to zero |
|
||
| **`check-test-counts.sh` not wired into CI.** ✅ Closed by branch `ci/structural-tests`. Step is now part of `.gitea/workflows/cpp-tests.yml` (re-uses the just-built `build/` dir; ~5 s overhead). | gate active on every PR | done |
|
||
| **End-to-end smoke (`try_it.sh`) not in CI.** ✅ Closed by `ci/structural-tests`. Added as a step after the CGAL job. | gate active on every PR | done |
|
||
| **Internal markdown link checker.** ✅ Closed by `ci/structural-tests`. New `.gitea/workflows/markdown-links.yml` runs on every PR that touches a `*.md` file, plus a weekly cron for external link rot. | gate active on every PR + weekly | done |
|
||
| **Local quality gates (sanitizers, coverage, clang-tidy, multi-compiler, CGAL-version-matrix, reproducible-build, license-headers).** Eight scripts under `scripts/quality/`, driven by `run-all.sh`. Documented in `scripts/quality/README.md` with promotion-to-CI checklist. | local-only by design; promotion gated on policy text in release-policy.md | done as scripts; 60 SPDX headers missing in `code/include/` will be a follow-up |
|
||
| **Code-style / convention gates (clang-format + CGAL-conventions checker).** ✅ Closed by `ci/structural-tests`. Adds `.clang-format` (project style mechanically captured) + `clang-format.sh` (drift detector, `--fix` mode); `cgal-conventions.py` enforces 6 CGAL-specific idioms (include-guard format, `\file` brief, namespace nesting, named-parameter tag `_t` suffix, no `using namespace`, no stray `#define`). Both are intentionally local-only — promotion to CI once the existing tree passes `--strict` (today CGAL-conventions does pass: 0/6 violations across 6 CGAL public headers; clang-format drift TBD pending toolchain install). | done as scripts; promotion deferred | done |
|
||
| **CP-Euclidean and Inversive-Distance research-track entries.** Both ship a working DCE solver, but lack the auxiliary utilities the Euclidean / HyperIdeal entries have (curvature inspection helpers, edge-flip Delaunay maintenance for ID). Out of port scope; listed in [`research-track.md`](../roadmap/research-track.md). | research-track, not blocking | per-utility |
|
||
| **`StereographicUnwrapper`, `CircleDomainUnwrapper`, `CuttingUtility`, `KoebePolyhedron`.** Mentioned in roadmap + research-track docs but not yet ported. Java versions still authoritative. | documented as Phase 11+ / optional | weeks each — explicit "out of port scope unless requested" |
|
||
|
||
The honest framing for the meeting: **the porting layer hits its target
|
||
for the 5 Phase-8b-Lite DCE entries; the hackability layer is
|
||
demonstrably in place (3 tutorials, named-parameter chaining,
|
||
Doxygen-HTML); the research-track items are scoped but not built.**
|
||
|
||
---
|
||
|
||
## 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?
|