diff --git a/code/include/cp_euclidean_functional.hpp b/code/include/cp_euclidean_functional.hpp index 758a9c4..8267591 100644 --- a/code/include/cp_euclidean_functional.hpp +++ b/code/include/cp_euclidean_functional.hpp @@ -324,16 +324,19 @@ inline Eigen::SparseMatrix cp_euclidean_hessian(const ConformalMesh& return H; } -/// FD gradient check for the CP-Euclidean functional. Mirrors the -/// Java `FunctionalTest`; default `eps = 1e-5`, `tol = 1e-6`. +/// FD gradient check for the CP-Euclidean functional (central differences). +/// Uses the same **relative** error criterion as every other gradient check in +/// this library: `|analytic − fd| / max(1, |analytic|) < tol`. +/// Default `eps = 1e-5`, `tol = 1e-4` (matches Java `FunctionalTest`). inline bool gradient_check_cp_euclidean(const ConformalMesh& mesh, const std::vector& x, const CPEuclideanMaps& m, double eps = 1e-5, - double tol = 1e-6) + double tol = 1e-4) { auto G = cp_euclidean_gradient(mesh, x, m); const std::size_t n = G.size(); + bool ok = true; for (std::size_t i = 0; i < n; ++i) { std::vector xp = x, xm = x; @@ -341,28 +344,33 @@ inline bool gradient_check_cp_euclidean(const ConformalMesh& mesh, xm[i] -= eps; const double Ep = cp_euclidean_energy(mesh, xp, m); const double Em = cp_euclidean_energy(mesh, xm, m); - const double fd = (Ep - Em) / (2.0 * eps); - if (std::abs(G[i] - fd) > tol) { + const double fd = (Ep - Em) / (2.0 * eps); + const double err = std::abs(G[i] - fd); + const double scale = std::max(1.0, std::abs(G[i])); + if (err / scale > tol) { std::cerr << "[cp-euclidean] FD gradient mismatch at DOF " << i << ": analytic=" << G[i] << " FD=" << fd - << " diff=" << (G[i] - fd) << "\n"; - return false; + << " rel-err=" << (err / scale) << "\n"; + ok = false; } } - return true; + return ok; } /// FD Hessian check for the CP-Euclidean functional. Verifies analytic /// `H` column-by-column against `(G(x+εe_j) − G(x−εe_j)) / (2ε)`. +/// Uses the same **relative** error criterion as `hessian_check_euclidean`: +/// `|analytic − fd| / max(1, |analytic|) < tol`. inline bool hessian_check_cp_euclidean(const ConformalMesh& mesh, const std::vector& x, const CPEuclideanMaps& m, double eps = 1e-5, - double tol = 1e-5) + double tol = 1e-4) { const auto H = cp_euclidean_hessian(mesh, x, m); const int n = static_cast(H.rows()); + bool ok = true; for (int j = 0; j < n; ++j) { std::vector xp = x, xm = x; @@ -372,19 +380,21 @@ inline bool hessian_check_cp_euclidean(const ConformalMesh& mesh, auto Gm = cp_euclidean_gradient(mesh, xm, m); for (int i = 0; i < n; ++i) { - double fd = (Gp[static_cast(i)] - Gm[static_cast(i)]) - / (2.0 * eps); - double an = H.coeff(i, j); - if (std::abs(an - fd) > tol) { + double fd = (Gp[static_cast(i)] - Gm[static_cast(i)]) + / (2.0 * eps); + double an = H.coeff(i, j); + double err = std::abs(an - fd); + double scale = std::max(1.0, std::abs(an)); + if (err / scale > tol) { std::cerr << "[cp-euclidean] FD Hessian mismatch at (" << i << "," << j << "): analytic=" << an << " FD=" << fd - << " diff=" << (an - fd) << "\n"; - return false; + << " rel-err=" << (err / scale) << "\n"; + ok = false; } } } - return true; + return ok; } } // namespace conformallab diff --git a/code/include/inversive_distance_functional.hpp b/code/include/inversive_distance_functional.hpp index 2a5377b..ef8da4d 100644 --- a/code/include/inversive_distance_functional.hpp +++ b/code/include/inversive_distance_functional.hpp @@ -354,32 +354,37 @@ inline double inversive_distance_energy( } /// FD gradient check for the Inversive-Distance functional (central diff). +/// Uses the same **relative** error criterion as every other gradient check: +/// `|analytic − fd| / max(1, |analytic|) < tol`. inline bool gradient_check_inversive_distance( const ConformalMesh& mesh, const std::vector& x, const InversiveDistanceMaps& m, double eps = 1e-5, - double tol = 1e-6) + double tol = 1e-4) { auto G = inversive_distance_gradient(mesh, x, m); const std::size_t n = G.size(); + bool ok = true; for (std::size_t i = 0; i < n; ++i) { std::vector xp = x, xm = x; xp[i] += eps; xm[i] -= eps; - double Ep = inversive_distance_energy(mesh, xp, m); - double Em = inversive_distance_energy(mesh, xm, m); - double fd = (Ep - Em) / (2.0 * eps); - if (std::abs(G[i] - fd) > tol) { + double Ep = inversive_distance_energy(mesh, xp, m); + double Em = inversive_distance_energy(mesh, xm, m); + double fd = (Ep - Em) / (2.0 * eps); + double err = std::abs(G[i] - fd); + double scale = std::max(1.0, std::abs(G[i])); + if (err / scale > tol) { std::cerr << "[inversive-distance] FD gradient mismatch at DOF " << i << ": analytic=" << G[i] << " FD=" << fd - << " diff=" << (G[i] - fd) << "\n"; - return false; + << " rel-err=" << (err / scale) << "\n"; + ok = false; } } - return true; + return ok; } /// Newton equilibrium check: returns `true` iff the gradient at `x` diff --git a/doc/reviewer/external-audit-2026-05-30.md b/doc/reviewer/external-audit-2026-05-30.md index f208918..e42688b 100644 --- a/doc/reviewer/external-audit-2026-05-30.md +++ b/doc/reviewer/external-audit-2026-05-30.md @@ -703,7 +703,7 @@ index. Add a comment: | B | `gauss_bonnet.hpp` | 87–88, 128–134 | API error | Medium | ✅ Fixed 2026-05-31 | | C | `euclidean_hessian.hpp` | 26–27, 57–58 | Doc error | Medium | ✅ Fixed 2026-05-31 | | D | `euclidean_functional.hpp` + 2 others | 97–107 | Doc error | Medium | ✅ Fixed 2026-05-31 | -| E | `cp_euclidean_functional.hpp` | 338–349, 373–387 | Inconsistency | Medium | 🟡 Open | +| E | `cp_euclidean_functional.hpp` | 338–349, 373–387 | Inconsistency | Medium | ✅ Fixed 2026-05-31 | | F | test files | — | Test gap | Medium | 🟠 Open | | G | test files | — | Test gap | Medium | 🟠 Open | | H | test files | — | Test gap | Medium | 🟠 Open |