fix+test: Euclidean holonomy/τ end-to-end + spherical edge-DOF oracle (2026-05-29 audit)
All checks were successful
C++ Tests / test-fast (pull_request) Successful in 1m57s
API Docs / doc-build (pull_request) Successful in 1m3s
Markdown link check / check (pull_request) Successful in 44s
C++ Tests / test-cgal (pull_request) Has been skipped
C++ Tests / quality-gates (pull_request) Successful in 2m11s

Bundles the 2026-05-29 Java↔C++ math-correctness audit (doc/reviewer/
java-port-audit.md, 11 findings) with two follow-up fixes.

Audit code changes:
- Finding 3 (spherical_functional): edge-DOF replacement parameterization via
  spher_eff_lambda; edge gradient α_opp⁺+α_opp⁻−θ_e (drops additive −(S⁺+S⁻)/2)
- Finding 4 (spherical_hessian): always-compiled edge-DOF throw guard
- Finding 6 (period_matrix): faithful normalizeModulus (0≤Re≤½, Im≥0, |τ|≥1)
- Finding 9 (inversive_distance): degenerate-face limiting angles, no skip
- Findings 1/2 (euclidean): degenerate gradient limiting angles + Hessian guard

Euclidean holonomy/τ fix: develop the cut surface across the dual spanning tree
only (cut_graph now exposes is_dual_tree), so genus-1 cut edges yield
non-degenerate lattice generators. Previously τ came out 0 / NaN / 1e13 on the
bundled tori; now matches the analytic revolution modulus i·√(R²−r²)/r. Re-enabled
τ reporting in the Euclidean CLI; rewrote validation.md §3/§4 accordingly.

Tests (240 CGAL, 0 skipped):
- HolonomyEndToEnd ×3 — tori of revolution (4×4, hex 6×6, 8×8) vs analytic modulus
- SphericalFunctional.EdgeGradient_RegularTetClosedForm — independent closed-form
  π/3 oracle locking the Finding-3 edge formula (the path-integral FD check cannot
  detect a wrong-but-conservative gradient)

Also documents the latent spherical/hyperbolic holonomy-extraction bug (same
single-development pattern, dead code today) in research-track.md (Phase 9c/10),
and adds favour/normalisations to the codespell ignore list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-29 12:50:16 +02:00
parent ca936b7652
commit a3ee9576d4
23 changed files with 1065 additions and 165 deletions

View File

@@ -164,8 +164,12 @@ TEST(SphericalFunctional, GradientCheck_SpherTetAllDofs)
compute_lambda0_from_mesh(mesh, maps);
int n = assign_all_spherical_dof_indices(mesh, maps);
// Small but non-zero values; vertex DOFs negative, edge DOFs zero.
// Edge DOF adjusts the effective log-length Λ_ij = λ°_ij + u_i + u_j + λ_e.
// Replacement parameterization (Finding 3): when an edge carries a DOF its
// value *replaces* λ°_ij + u_i + u_j entirely, so Λ_ij = λ_e. Here the edge
// DOFs stay at 0 and only the vertex DOFs are perturbed; this checks that the
// gradient is curl-free (energy = Schläfli path integral), not Java-faithfulness
// of the edge formula — that is locked separately by
// EdgeGradient_RegularTetClosedForm below.
std::vector<double> x(static_cast<std::size_t>(n), 0.0);
// Set vertex DOFs (indices 0..3) to -0.2 to keep triangle well-formed.
for (int i = 0; i < 4; ++i) x[static_cast<std::size_t>(i)] = -0.2;
@@ -174,6 +178,59 @@ TEST(SphericalFunctional, GradientCheck_SpherTetAllDofs)
<< "Gradient check failed on spherical tetrahedron (all DOFs)";
}
// ════════════════════════════════════════════════════════════════════════════
// Closed-form oracle for the edge-DOF gradient (Finding 3, missing-test item 4)
//
// The FD gradient check above can only confirm that G is conservative — the
// spherical energy is *defined* as the path integral of G, so the energy↔gradient
// FD agreement is automatic and CANNOT detect a wrong-but-conservative edge
// formula. This test instead pins the edge gradient against an independent,
// closed-form geometric value, so it would fail if the Finding-3 formula
// (G_e = α_opp⁺ + α_opp⁻ θ_e, dropping the additive (S⁺+S⁻)/2 term) ever
// regressed.
//
// Geometry: the regular spherical tetrahedron has all edges a = arccos(1/3),
// so by the spherical law of cosines every interior corner angle is
// cos α = (cos a cos²a)/sin²a = cos a/(1+cos a) = (1/3)/(2/3) = 1/2
// ⇒ α = 2π/3.
// Each edge is shared by two faces, so both opposite angles equal 2π/3 and
// G_e = 2π/3 + 2π/3 θ_e with θ_e = π (default) = π/3.
//
// Setup: all edges carry DOFs, set to their λ⁰ (the replacement convention then
// reproduces the original tetrahedron metric exactly), vertex DOFs left at 0.
// ════════════════════════════════════════════════════════════════════════════
TEST(SphericalFunctional, EdgeGradient_RegularTetClosedForm)
{
const double PI_ = std::acos(-1.0);
auto mesh = make_spherical_tetrahedron();
auto maps = setup_spherical_maps(mesh);
compute_lambda0_from_mesh(mesh, maps);
int n = assign_all_spherical_dof_indices(mesh, maps);
// Edge DOF = λ⁰ → Λ_ij = λ⁰ → reproduces the arccos(1/3) tetrahedron.
// Vertex DOFs stay at 0 (ignored by the replacement convention for DOF edges).
std::vector<double> x(static_cast<std::size_t>(n), 0.0);
int n_edge_dofs = 0;
for (auto e : mesh.edges()) {
int ie = maps.e_idx[e];
if (ie >= 0) { x[static_cast<std::size_t>(ie)] = maps.lambda0[e]; ++n_edge_dofs; }
}
ASSERT_EQ(n_edge_dofs, 6) << "regular tetrahedron must have 6 edge DOFs";
auto G = spherical_gradient(mesh, x, maps);
const double expected = PI_ / 3.0; // 2·(2π/3) π
for (auto e : mesh.edges()) {
int ie = maps.e_idx[e];
if (ie < 0) continue;
EXPECT_NEAR(G[static_cast<std::size_t>(ie)], expected, 1e-9)
<< "edge gradient at DOF " << ie
<< " must equal the closed-form value π/3 (Finding 3)";
}
}
// ════════════════════════════════════════════════════════════════════════════
// Angles are finite at a known interior point
//