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:
@@ -64,7 +64,8 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
double eps = 1e-5,
|
||||
HyperIdealScaleClamp clamp = HyperIdealScaleClamp::HardJava)
|
||||
{
|
||||
const int n = hyper_ideal_dimension(mesh, m);
|
||||
std::vector<Eigen::Triplet<double>> trips;
|
||||
@@ -77,8 +78,8 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian(
|
||||
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;
|
||||
auto Gp = evaluate_hyper_ideal(mesh, xp, m, /*energy=*/false, /*grad=*/true, clamp).gradient;
|
||||
auto Gm = evaluate_hyper_ideal(mesh, xm, m, /*energy=*/false, /*grad=*/true, clamp).gradient;
|
||||
|
||||
xp[sj] = xm[sj] = x[sj]; // restore
|
||||
|
||||
@@ -101,9 +102,10 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian_sym(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
double eps = 1e-5,
|
||||
HyperIdealScaleClamp clamp = HyperIdealScaleClamp::HardJava)
|
||||
{
|
||||
auto H = hyper_ideal_hessian(mesh, x, m, eps);
|
||||
auto H = hyper_ideal_hessian(mesh, x, m, eps, clamp);
|
||||
Eigen::SparseMatrix<double> Ht = H.transpose();
|
||||
return (H + Ht) * 0.5;
|
||||
}
|
||||
@@ -142,7 +144,8 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian_block_fd(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
double eps = 1e-5,
|
||||
HyperIdealScaleClamp clamp = HyperIdealScaleClamp::HardJava)
|
||||
{
|
||||
const int n = hyper_ideal_dimension(mesh, m);
|
||||
std::vector<Eigen::Triplet<double>> trips;
|
||||
@@ -187,9 +190,9 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian_block_fd(
|
||||
vm[j] -= eps;
|
||||
|
||||
auto Op = face_angles_from_local_dofs(
|
||||
vp[0], vp[1], vp[2], vp[3], vp[4], vp[5], v1b, v2b, v3b);
|
||||
vp[0], vp[1], vp[2], vp[3], vp[4], vp[5], v1b, v2b, v3b, clamp);
|
||||
auto Om = face_angles_from_local_dofs(
|
||||
vm[0], vm[1], vm[2], vm[3], vm[4], vm[5], v1b, v2b, v3b);
|
||||
vm[0], vm[1], vm[2], vm[3], vm[4], vm[5], v1b, v2b, v3b, clamp);
|
||||
|
||||
const double Gp[6] = {
|
||||
Op.beta1, Op.beta2, Op.beta3,
|
||||
@@ -221,9 +224,10 @@ inline Eigen::SparseMatrix<double> hyper_ideal_hessian_block_fd_sym(
|
||||
ConformalMesh& mesh,
|
||||
const std::vector<double>& x,
|
||||
const HyperIdealMaps& m,
|
||||
double eps = 1e-5)
|
||||
double eps = 1e-5,
|
||||
HyperIdealScaleClamp clamp = HyperIdealScaleClamp::HardJava)
|
||||
{
|
||||
auto H = hyper_ideal_hessian_block_fd(mesh, x, m, eps);
|
||||
auto H = hyper_ideal_hessian_block_fd(mesh, x, m, eps, clamp);
|
||||
Eigen::SparseMatrix<double> Ht = H.transpose();
|
||||
return (H + Ht) * 0.5;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user