feat(phase4): HyperIdeal Newton solver, SparseQR fallback, examples, docs
Phase 4 complete — 87 CGAL tests pass, 2 skipped. Newton solver (phase4a): - hyper_ideal_hessian.hpp: symmetric FD Hessian (O(ε²), PSD by convexity) - newton_hyper_ideal(): Newton + backtracking for the HyperIdeal functional - detail::solve_with_fallback(): optional bool* fallback_used parameter - solve_linear_system(): public API exposing LDLT→SparseQR fallback SparseQR fallback tests (SparseQRFallback.*): - FullRankSystem_CorrectSolution: LDLT path, fallback_used=false - SingularMatrix_FallbackActivated: zero-pivot → QR activated, fallback_used=true - Euclidean_ClosedMeshNoPinConverges: gauge-mode null space handled via QR HyperIdeal Newton tests (NewtonSolver.HyperIdeal_*): - ConvergesTriangleAllVariable, ResultFieldsConsistent, ConvergesTetrahedron, SparseQRFallbackNoCrash - Natural-target base point (b=1.0, a=0.5) — x=0 is degenerate in log-space Pipeline tests (test_pipeline.cpp): - End-to-end: all three geometries, mesh I/O round-trip, solve+export Example programs (code/examples/): - example_euclidean.cpp: headless Euclidean pipeline - example_hyper_ideal.cpp: headless HyperIdeal pipeline - example_viewer.cpp: interactive libigl viewer with jet colour map README: - Mathematical scope table: C++ vs Java original (18 rows) - "For mathematicians" section: mental model, step-by-step new-functional guide, half-edge traversal snippets, recommended reading Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
87
code/include/hyper_ideal_hessian.hpp
Normal file
87
code/include/hyper_ideal_hessian.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#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 <Eigen/Sparse>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
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<double> hyper_ideal_hessian(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
{
|
||||
const int n = hyper_ideal_dimension(mesh, m);
|
||||
std::vector<Eigen::Triplet<double>> trips;
|
||||
trips.reserve(static_cast<std::size_t>(n * n)); // dense upper bound
|
||||
|
||||
std::vector<double> xp = x, xm = x;
|
||||
|
||||
for (int j = 0; j < n; ++j) {
|
||||
const std::size_t sj = static_cast<std::size_t>(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<std::size_t>(i)]
|
||||
- Gm[static_cast<std::size_t>(i)]) / (2.0 * eps);
|
||||
if (std::abs(val) > 1e-15)
|
||||
trips.emplace_back(i, j, val);
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::SparseMatrix<double> 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<double> hyper_ideal_hessian_sym(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
{
|
||||
auto H = hyper_ideal_hessian(mesh, x, m, eps);
|
||||
Eigen::SparseMatrix<double> Ht = H.transpose();
|
||||
return (H + Ht) * 0.5;
|
||||
}
|
||||
|
||||
} // namespace conformallab
|
||||
Reference in New Issue
Block a user