Files
ConformalLabpp/code/include/hyper_ideal_hessian.hpp
Tarik Moussa 3f124eb071 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>
2026-05-13 00:11:25 +02:00

88 lines
4.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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