#pragma once // hyper_ideal_hessian.hpp // // Phase 4a — Hessian of the hyper-ideal discrete conformal functional. // // ┌──────────────────────────────────────────────────────────────────────────┐ // │ Implementation strategy │ // │ │ // │ The hyper-ideal functional involves angle functions (ζ, σ, α, β) │ // │ composed through several nested layers (lij → ζ13/14/15 → β/α). │ // │ Deriving closed-form Hessian entries analytically through all these │ // │ layers is feasible but lengthy; an analytical Hessian is left for a │ // │ future phase. │ // │ │ // │ Here we compute the Hessian by symmetric finite differences of the │ // │ gradient, which is exact to O(ε²) and sufficient for Newton's method │ // │ at the meshes typical in Phase 4 (< 500 DOFs): │ // │ │ // │ H[i,j] = (G(x + ε·eⱼ)[i] − G(x − ε·eⱼ)[i]) / (2ε) │ // │ │ // │ The hyper-ideal energy is strictly convex (Springborn 2020), so H is │ // │ positive semi-definite everywhere and Eigen::SimplicialLDLT applies │ // │ directly. │ // └──────────────────────────────────────────────────────────────────────────┘ #include "hyper_ideal_functional.hpp" #include #include #include namespace conformallab { // ── Numerical Hessian via symmetric finite differences ──────────────────────── // // Returns the n×n sparse Hessian, where n = hyper_ideal_dimension(mesh, m). // eps: finite-difference step size (default 1e-5 gives ~1e-10 relative error). inline Eigen::SparseMatrix hyper_ideal_hessian( ConformalMesh& mesh, const std::vector& x, const HyperIdealMaps& m, double eps = 1e-5) { const int n = hyper_ideal_dimension(mesh, m); std::vector> trips; trips.reserve(static_cast(n * n)); // dense upper bound std::vector xp = x, xm = x; for (int j = 0; j < n; ++j) { const std::size_t sj = static_cast(j); xp[sj] = x[sj] + eps; xm[sj] = x[sj] - eps; auto Gp = evaluate_hyper_ideal(mesh, xp, m, /*energy=*/false).gradient; auto Gm = evaluate_hyper_ideal(mesh, xm, m, /*energy=*/false).gradient; xp[sj] = xm[sj] = x[sj]; // restore for (int i = 0; i < n; ++i) { double val = (Gp[static_cast(i)] - Gm[static_cast(i)]) / (2.0 * eps); if (std::abs(val) > 1e-15) trips.emplace_back(i, j, val); } } Eigen::SparseMatrix H(n, n); H.setFromTriplets(trips.begin(), trips.end()); return H; } // ── Symmetrised Hessian ─────────────────────────────────────────────────────── // // The FD Hessian is symmetric in exact arithmetic; floating-point rounding // can introduce tiny asymmetries. This helper returns (H + Hᵀ)/2. inline Eigen::SparseMatrix hyper_ideal_hessian_sym( ConformalMesh& mesh, const std::vector& x, const HyperIdealMaps& m, double eps = 1e-5) { auto H = hyper_ideal_hessian(mesh, x, m, eps); Eigen::SparseMatrix Ht = H.transpose(); return (H + Ht) * 0.5; } } // namespace conformallab