perf(inv-dist): B1 port — block-FD Hessian for newton_inversive_distance
The Inversive-Distance solver built its Hessian inline by full finite
differences: n perturbations × a full O(F) gradient eval = O(n·F) per Newton
iteration (quadratic in mesh size), with no fast path at all (api-performance
audit B1, second half).
Port the per-face block-FD scheme already used by HyperIdeal (Phase 9b):
the gradient decomposes by face (G_v = Θ_v − Σ_{f∋v} α_v, and each face's
angles depend only on its 3 vertex DOFs), so the Hessian decomposes into
per-face 3×3 blocks. Cost drops to O(F) face evaluations, a ≈ n/6 speed-up.
- inversive_distance_functional.hpp: add the pure 3→3 kernel
inversive_distance_face_grad_contribs (returns the per-face contribution
−α to G; mirrors the gradient's face-skip on ℓ²≤0 exactly).
- inversive_distance_hessian.hpp (new): full-FD baseline + block-FD + sym
variants, mirroring hyper_ideal_hessian.hpp.
- newton_solver.hpp: drop the inline full-FD lambda; call
inversive_distance_hessian_block_fd_sym.
- test: InversiveDistance_BlockFDHessianMatchesFullFD cross-validates the
two Hessians entry-wise on a perturbed (off-equilibrium) config.
291/291 CGAL tests pass; all Inversive-Distance convergence tests unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -315,6 +315,43 @@ inline std::vector<double> inversive_distance_gradient(
|
||||
return G;
|
||||
}
|
||||
|
||||
/// Per-face contribution to the Inversive-Distance gradient, as a pure
|
||||
/// 3→3 kernel of the three local vertex DOFs `(u1,u2,u3)` and the three
|
||||
/// edge inversive distances `(I12,I23,I31)`. No mesh, no property maps —
|
||||
/// used by the per-face block-FD Hessian in `inversive_distance_hessian.hpp`.
|
||||
///
|
||||
/// Returns the three values this face *adds* to the global gradient at
|
||||
/// `(v1,v2,v3)`. Since `G_v = Θ_v − Σ_faces α_v`, the per-face contribution
|
||||
/// is the **negative** corner angles `(−α₁,−α₂,−α₃)`. A non-real circle
|
||||
/// configuration (any `ℓ² ≤ 0`) contributes nothing — exactly mirroring the
|
||||
/// face-skip (`continue`) in `inversive_distance_gradient`, so the block-FD
|
||||
/// Hessian and the full-FD Hessian see the same per-face support.
|
||||
struct IDFaceGradContribs {
|
||||
double g1; ///< contribution to G at v₁ (= −α₁)
|
||||
double g2; ///< contribution to G at v₂ (= −α₂)
|
||||
double g3; ///< contribution to G at v₃ (= −α₃)
|
||||
};
|
||||
|
||||
inline IDFaceGradContribs inversive_distance_face_grad_contribs(
|
||||
double u1, double u2, double u3,
|
||||
double I12, double I23, double I31)
|
||||
{
|
||||
double l12sq = id_detail::edge_length_squared(u1, u2, I12);
|
||||
double l23sq = id_detail::edge_length_squared(u2, u3, I23);
|
||||
double l31sq = id_detail::edge_length_squared(u3, u1, I31);
|
||||
|
||||
// Same face-skip as inversive_distance_gradient: a non-real circle
|
||||
// configuration has no limiting angle, so the face contributes 0.
|
||||
if (l12sq <= 0.0 || l23sq <= 0.0 || l31sq <= 0.0)
|
||||
return {0.0, 0.0, 0.0};
|
||||
|
||||
// euclidean_angles deliberately keeps its limiting (valid=false) angles
|
||||
// here — the convex C¹ extension — matching the gradient's choice not to
|
||||
// skip on !fa.valid. Corner at v_k = fa.alpha_k (see gradient Pass-2 trace).
|
||||
auto fa = euclidean_angles(std::log(l12sq), std::log(l23sq), std::log(l31sq));
|
||||
return {-fa.alpha1, -fa.alpha2, -fa.alpha3};
|
||||
}
|
||||
|
||||
/// Inversive-Distance energy `E(u) = ∫₀¹ ⟨G(t·u), u⟩ dt`, evaluated
|
||||
/// with 10-point Gauss-Legendre (constants shared with `euclidean_energy`).
|
||||
inline double inversive_distance_energy(
|
||||
|
||||
Reference in New Issue
Block a user