# 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

` | | | |--|--| | 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` 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` | | | |--|--| | 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`. 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` 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 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 (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?