feat(euclidean): block-FD edge-DOF Hessian → cyclic Newton + Java convergence oracle

Implements the edge-DOF (cyclic) Euclidean Hessian, unblocking the full cyclic
Newton solve, and enables the Java EuclideanCyclicConvergenceTest cross-validation.

- euclidean_hessian.hpp: `euclidean_hessian_block_fd` / `_sym` — per-face 6×6
  block FD over (u1,u2,u3,λ12,λ23,λ31), mirroring hyper_ideal_hessian_block_fd.
  Per-face outputs carry the gradient signs (−α vertex, +α_opp edge), so the
  result equals ∂G/∂x by construction (locality lemma). Analytic vertex-only
  cotangent Hessian unchanged (still used for vertex-only layouts).
- newton_solver.hpp: newton_euclidean routes cyclic layouts (edge DOFs present)
  through the block-FD Hessian; vertex-only path unchanged.
- tests:
  * CyclicCircularEdge_CatHead_JavaXVal (now GREEN) — prescribe φ=π−0.1 on one
    interior edge, solve, assert realised α_opp+α_opp = π−0.1 @1e-9.
  * CyclicCircularEdge_PhiEntersGradient_CatHead — solver-free φ-wiring check.
  * CyclicHessian_BlockFD_MatchesGradientFD_Tetrahedron — Hessian correctness.

243/243 cgal tests pass; vertex-only Euclidean Newton unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-30 00:09:37 +02:00
committed by Tarik Moussa
parent 5d74a94b78
commit ea5f01d73d
4 changed files with 210 additions and 52 deletions

View File

@@ -258,7 +258,14 @@ inline NewtonResult newton_euclidean(
}
// ── Hessian + solve H·Δx = G (SparseQR fallback for singular H) ──
auto H = euclidean_hessian(mesh, x, m);
// Cyclic layout (edge DOFs present) → block-FD Hessian, which covers the
// vertex-edge / edge-edge blocks the analytic cotangent Laplacian omits.
// Vertex-only layout → analytic cotangent Laplacian (cheaper, exact).
bool has_edge_dof = false;
for (auto e : mesh.edges())
if (m.e_idx[e] >= 0) { has_edge_dof = true; break; }
auto H = has_edge_dof ? euclidean_hessian_block_fd_sym(mesh, x, m)
: euclidean_hessian(mesh, x, m);
bool ok = false;
Eigen::VectorXd dx = detail::solve_with_fallback(H, -G, ok);
if (!ok) break;