feat(num): N3 selectable scale-floor clamp mode (HardJava | SmoothBarrier)
The hyper-ideal vertex scale b is floored to keep the geometry valid. The original clamp `b<0 → 0.01` mirrors the Java oracle but is only C⁰ (in fact value-discontinuous at b=0): a Newton step crossing the feasibility boundary hits a kink that can stall convergence (numerical-stability audit N3). Rather than replace the Java-faithful behaviour (which would break the golden parity tests), make the floor a selectable mode so BOTH the Java standpoint and the clean mathematics are available: - HyperIdealScaleClamp::HardJava (DEFAULT) — the original snap, bit-for-bit faithful to HyperIdealFunctional.java → all parity tests unchanged. - HyperIdealScaleClamp::SmoothBarrier — C¹ softplus floor b ↦ floor + softplus_β(b−floor), β = HYPER_IDEAL_SCALE_SHARPNESS (=100); ≈ identity away from the floor, smooth across b=0. Opt-in. clamp_hyper_ideal_scale centralises the logic (also folds in the N4 nachzügler: compute_face_angles used a bare 0.01). The mode threads with a defaulted trailing parameter through compute_face_angles, face_angles_from_local_dofs, evaluate_hyper_ideal, the four hyper_ideal_hessian* variants and newton_hyper_ideal — so every existing call site keeps HardJava behaviour. Tests (+4): clamp-function C¹/floor/identity contract, mode-equivalence away from the boundary, and end-to-end SmoothBarrier convergence to the same Java golden vector (LawsonHyperIdeal). 296/296 CGAL tests pass. Documented in doc/math/geometry-modes.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -400,6 +400,12 @@ inline NewtonResult newton_spherical(
|
||||
/// \param max_iter Maximum Newton iterations. Default: 200.
|
||||
/// \param hess_eps Finite-difference step for Hessian approximation. Default: 1e-5.
|
||||
/// (Phase 9b will replace this with an analytic Hessian.)
|
||||
/// \param clamp Vertex-scale floor mode (N3 audit). Default `HardJava`
|
||||
/// reproduces the Java oracle's hard `b<0 → floor` snap
|
||||
/// (C⁰, parity-faithful). `SmoothBarrier` uses the C¹
|
||||
/// softplus floor — smoother near the feasibility boundary
|
||||
/// but deviates from the Java golden values. See
|
||||
/// `HyperIdealScaleClamp` in hyper_ideal_functional.hpp.
|
||||
/// \return NewtonResult{x*, iterations, grad_inf_norm, converged}.
|
||||
///
|
||||
/// \see Springborn (2020), Theorem 1.3 for the strict convexity proof.
|
||||
@@ -410,7 +416,8 @@ inline NewtonResult newton_hyper_ideal(
|
||||
const HyperIdealMaps& m,
|
||||
double tol = 1e-8,
|
||||
int max_iter = 200,
|
||||
double hess_eps = 1e-5)
|
||||
double hess_eps = 1e-5,
|
||||
HyperIdealScaleClamp clamp = HyperIdealScaleClamp::HardJava)
|
||||
{
|
||||
std::vector<double> x = x0;
|
||||
const int n = static_cast<int>(x.size());
|
||||
@@ -422,7 +429,7 @@ inline NewtonResult newton_hyper_ideal(
|
||||
|
||||
for (int iter = 0; iter < max_iter; ++iter) {
|
||||
// ── Gradient ──────────────────────────────────────────────────────────
|
||||
auto G_std = evaluate_hyper_ideal(mesh, x, m, /*energy=*/false).gradient;
|
||||
auto G_std = evaluate_hyper_ideal(mesh, x, m, /*energy=*/false, /*grad=*/true, clamp).gradient;
|
||||
Eigen::Map<const Eigen::VectorXd> G(G_std.data(), n);
|
||||
|
||||
double inf_norm = G.cwiseAbs().maxCoeff();
|
||||
@@ -435,7 +442,7 @@ inline NewtonResult newton_hyper_ideal(
|
||||
}
|
||||
|
||||
// ── Hessian (block-FD, ~33–1166× faster than full-FD) + solve H·Δx = −G
|
||||
auto H = hyper_ideal_hessian_block_fd_sym(mesh, x, m, hess_eps);
|
||||
auto H = hyper_ideal_hessian_block_fd_sym(mesh, x, m, hess_eps, clamp);
|
||||
bool ok = false;
|
||||
Eigen::VectorXd dx = detail::solve_with_fallback(H, -G, ok);
|
||||
if (!ok) break;
|
||||
@@ -446,14 +453,14 @@ inline NewtonResult newton_hyper_ideal(
|
||||
bool improved = true;
|
||||
x = detail::line_search(x, dx, d_sd, norm0,
|
||||
[&](const std::vector<double>& xnew) {
|
||||
return evaluate_hyper_ideal(mesh, xnew, m, false).gradient;
|
||||
return evaluate_hyper_ideal(mesh, xnew, m, false, true, clamp).gradient;
|
||||
}, &improved);
|
||||
if (!improved) break;
|
||||
|
||||
res.iterations = iter + 1;
|
||||
}
|
||||
|
||||
auto G_final = evaluate_hyper_ideal(mesh, x, m, false).gradient;
|
||||
auto G_final = evaluate_hyper_ideal(mesh, x, m, false, true, clamp).gradient;
|
||||
double inf_final = 0.0;
|
||||
for (double v : G_final) inf_final = std::max(inf_final, std::abs(v));
|
||||
res.grad_inf_norm = inf_final;
|
||||
|
||||
Reference in New Issue
Block a user