Two complementary improvements aimed at reducing recurring maintenance
overhead:
1. **Test-count centralisation** — `doc/api/tests.md` is now the
single source of truth for the test counts. All other docs
(README, CLAUDE.md, doc/contributing.md, doc/getting-started.md,
doc/math/validation.md, doc/math/validation-protocol.md,
scripts/try_it.sh) use qualitative phrasing + a link instead of
hardcoded numbers. The previous regime had eight places with
"227 CGAL tests, 23 non-CGAL tests" that drifted apart across
releases (the v0.9.0 release-prep needed to touch nine files).
2. **Versioning policy** — `doc/release-policy.md` (new, ~250 lines)
formalises:
* SemVer rules for the pre-1.0 and post-1.0 phases.
* Phase-milestone → MINOR-bump mapping (v0.10.0 → Phase 9c, …).
* Single-source-of-truth table for moving numbers (test counts,
version, date).
* Step-by-step release process (the recipe that worked for v0.9.0
after the false-start with PR #11/#12).
* Hotfix policy + post-1.0 deprecation policy.
* Known failure modes and how to recover from them.
Plus a small CI gate:
3. **scripts/check-test-counts.sh** — verifies the totals in
doc/api/tests.md match `ctest` output. Re-uses existing build-cgal/
if present. Exit 0 on match, 1 on divergence with recovery hints.
Cheap enough (~30 s) to run on every PR.
Other cleanups
──────────────
* code/tests/cgal/CMakeLists.txt — stale "Test 7 (genus-2 homology)
as GTEST_SKIP stub until Phase 8" comment removed; that test landed
as HomologyGenerators.Genus2_FourCutEdges in Phase 7.
* CLAUDE.md — "test-fast also runs stubs" Known Quirks entry updated
to reflect the v0.9.0 stub cleanup (no GTEST_SKIPs remain).
* CLAUDE.md doc map — new entry for doc/release-policy.md.
Stubs audit
───────────
Zero GTEST_SKIP() calls remain in the codebase as of this commit.
The only references to stubs are in historical documentation
(CHANGELOG.md v0.7.0 entry, doc/roadmap/* "deferred to research-track"
notes) — those are intended.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
214 lines
6.1 KiB
Markdown
214 lines
6.1 KiB
Markdown
# Validation Protocol
|
||
|
||
Concrete, reproducible steps to verify the mathematical correctness of
|
||
conformallab++. Every check below has a deterministic expected outcome.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
```bash
|
||
cmake -S code -B build -DWITH_CGAL=ON -DCMAKE_BUILD_TYPE=Release
|
||
cmake --build build --target conformallab_cgal_tests -j$(nproc)
|
||
```
|
||
|
||
---
|
||
|
||
## Check 0 — All tests pass
|
||
|
||
```bash
|
||
ctest --test-dir build -R "^cgal\." --output-on-failure
|
||
```
|
||
|
||
**Expected output (last lines):**
|
||
```
|
||
100% tests passed, 0 tests failed out of 170
|
||
|
||
The following tests did not run:
|
||
206 - cgal.HomologyGenerators.Genus2_FourGeneratorPaths_BLOCKED (Skipped)
|
||
```
|
||
|
||
If any test fails, stop — the implementation is broken.
|
||
|
||
---
|
||
|
||
## Check 1 — Gauss–Bonnet (topological identity, error < 1e-10)
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="GaussBonnet.*" -v
|
||
```
|
||
|
||
**Expected:** all 8 tests `[ PASSED ]`
|
||
|
||
What is verified: for each test mesh (tetrahedron χ=2, torus χ=0, open mesh χ=1):
|
||
|
||
```
|
||
Σᵥ (2π − Θᵥ) = 2π · χ(M) ± 1e-10
|
||
```
|
||
|
||
This is a **pure topology check** — it fails only if vertex/face counts or
|
||
property-map assignments are wrong. It does not depend on the Newton solver.
|
||
|
||
---
|
||
|
||
## Check 2 — Euclidean gradient consistency (FD vs. analytic, error < 1e-6)
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="EuclideanFunctional.GradientCheck*" -v
|
||
```
|
||
|
||
**Expected:** all `GradientCheck_*` tests `[ PASSED ]`
|
||
|
||
What is verified: for ε = 1e-5,
|
||
|
||
```
|
||
|G(u)ᵢ − (E(u + εeᵢ) − E(u − εeᵢ)) / (2ε)| < 1e-6
|
||
```
|
||
|
||
This check **proves that the energy and its gradient are mathematically consistent**.
|
||
A failing FD check means Newton will converge to the wrong point — it is the most
|
||
important correctness check for any new functional.
|
||
|
||
Also run for Spherical and HyperIdeal:
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="SphericalFunctional.GradientCheck*" -v
|
||
./build/conformallab_cgal_tests --gtest_filter="HyperIdealFunctional.GradientCheck*" -v
|
||
```
|
||
|
||
---
|
||
|
||
## Check 3 — Newton convergence on canonical test meshes
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="NewtonSolver.*" -v
|
||
```
|
||
|
||
**Expected:** all 11 tests `[ PASSED ]`
|
||
|
||
Each test verifies:
|
||
- `res.converged == true`
|
||
- `res.grad_inf_norm < 1e-8`
|
||
- `res.iterations < 50` (typically 5–20 for the small test meshes)
|
||
|
||
---
|
||
|
||
## Check 4 — Period matrix: SL(2,ℤ)-reduction invariants
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="PeriodMatrix.*" -v
|
||
```
|
||
|
||
**Expected:** all 7 tests `[ PASSED ]`
|
||
|
||
The three mathematical invariants checked for **any** genus-1 output τ:
|
||
|
||
| Property | Condition | Why |
|
||
|---|---|---|
|
||
| Upper half-plane | `Im(τ) > 0` | τ encodes a positive-area lattice |
|
||
| Outside unit disk | `|τ| ≥ 1 − 1e-10` | SL(2,ℤ) reduction step |
|
||
| Vertical strip | `|Re(τ)| ≤ 0.5 + 1e-10` | SL(2,ℤ) reduction step |
|
||
|
||
These hold for **any** well-formed genus-1 mesh — they are topology, not geometry.
|
||
|
||
---
|
||
|
||
## Check 5 — Möbius arithmetic (complex analysis correctness)
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="MobiusMap.*" -v
|
||
```
|
||
|
||
**Expected:** all 8 tests `[ PASSED ]`
|
||
|
||
What is verified:
|
||
- `T ∘ T⁻¹ = Id` (inverse is correct)
|
||
- `(T₁ ∘ T₂)(z) = T₁(T₂(z))` (composition is associative)
|
||
- `from_three(z₁, z₂, z₃)` maps z₁→0, z₂→1, z₃→∞ (unique Möbius transformation)
|
||
|
||
A bug here would corrupt **all** hyperbolic holonomy computation.
|
||
|
||
---
|
||
|
||
## Check 6 — End-to-end pipeline (build + solve + layout)
|
||
|
||
```bash
|
||
./build/conformallab_cgal_tests --gtest_filter="Pipeline.*" -v
|
||
```
|
||
|
||
**Expected:** all 5 tests `[ PASSED ]`
|
||
|
||
What is verified: starting from a mesh file, the full pipeline
|
||
(setup → Gauss-Bonnet → Newton → layout → serialise → reload)
|
||
produces a consistent result.
|
||
|
||
---
|
||
|
||
## Check 7 — Manual torus τ verification
|
||
|
||
This check requires adding a small program (or modifying an existing test).
|
||
It validates that `torus_4x4.off` produces τ in the fundamental domain:
|
||
|
||
```cpp
|
||
#include "conformal_mesh.hpp"
|
||
#include "euclidean_functional.hpp"
|
||
#include "newton_solver.hpp"
|
||
#include "cut_graph.hpp"
|
||
#include "layout.hpp"
|
||
#include "period_matrix.hpp"
|
||
#include "mesh_io.hpp"
|
||
#include <iostream>
|
||
|
||
int main() {
|
||
conformallab::ConformalMesh mesh;
|
||
conformallab::load_mesh(mesh, "code/data/off/torus_4x4.off");
|
||
|
||
auto maps = conformallab::setup_euclidean_maps(mesh);
|
||
conformallab::compute_euclidean_lambda0_from_mesh(mesh, maps);
|
||
conformallab::enforce_gauss_bonnet(mesh, maps);
|
||
|
||
auto res = conformallab::newton_euclidean(mesh, std::vector<double>(maps.n_dof, 0.0), maps);
|
||
std::cout << "Converged: " << res.converged
|
||
<< " iterations: " << res.iterations
|
||
<< " |G|∞: " << res.grad_inf_norm << "\n";
|
||
|
||
auto cg = conformallab::compute_cut_graph(mesh);
|
||
conformallab::HolonomyData hol;
|
||
conformallab::euclidean_layout(mesh, res.x, maps, &cg, &hol, true);
|
||
auto pd = conformallab::compute_period_matrix(hol);
|
||
|
||
std::cout << "τ = " << pd.tau_reduced.real()
|
||
<< " + " << pd.tau_reduced.imag() << "i\n";
|
||
std::cout << "|τ| = " << std::abs(pd.tau_reduced) << "\n";
|
||
std::cout << "|Re(τ)| = " << std::abs(pd.tau_reduced.real()) << "\n";
|
||
}
|
||
```
|
||
|
||
**Expected output (torus_4x4.off, R=2, r=1 torus of revolution):**
|
||
```
|
||
Converged: 1 iterations: <30 |G|∞: <1e-8
|
||
τ = [small] + [positive]i (Re close to 0 by 4-fold symmetry)
|
||
|τ| ≥ 1.0 (fundamental domain)
|
||
|Re(τ)| ≤ 0.5 (fundamental domain)
|
||
```
|
||
|
||
The exact value of Im(τ) depends on the 3D embedding (R=2, r=1 gives unequal
|
||
inner/outer edge lengths). Use `torus_8x8.off` for a finer approximation.
|
||
|
||
---
|
||
|
||
## Summary checklist
|
||
|
||
```
|
||
[ ] Check 0: full test suite passes, 0 skipped (counts: `doc/api/tests.md`)
|
||
[ ] Check 1: Gauss–Bonnet exact (1e-10)
|
||
[ ] Check 2: FD gradient < 1e-6 for all 3 geometries
|
||
[ ] Check 3: Newton convergence < 50 iterations
|
||
[ ] Check 4: τ in SL(2,ℤ) fundamental domain
|
||
[ ] Check 5: Möbius arithmetic (inverse, compose, from_three)
|
||
[ ] Check 6: End-to-end pipeline
|
||
[ ] Check 7: Torus τ in upper half-plane with correct symmetry
|
||
```
|
||
|
||
All checks are deterministic and do not depend on random initialization or
|
||
floating-point non-determinism beyond standard IEEE-754.
|