test: Java golden-value oracles for the five DCE math cores + P1-2/P1-3 fixes
All checks were successful
C++ Tests / test-fast (pull_request) Successful in 1m57s
API Docs / doc-build (pull_request) Successful in 59s
Markdown link check / check (pull_request) Successful in 51s
C++ Tests / test-cgal (pull_request) Has been skipped
C++ Tests / quality-gates (pull_request) Successful in 2m19s
All checks were successful
C++ Tests / test-fast (pull_request) Successful in 1m57s
API Docs / doc-build (pull_request) Successful in 59s
Markdown link check / check (pull_request) Successful in 51s
C++ Tests / test-cgal (pull_request) Has been skipped
C++ Tests / quality-gates (pull_request) Successful in 2m19s
Add bit-for-bit (1e-12) golden-value oracle tests pinning the C++ pure-math and functional cores against the compiled upstream Java library (openjdk 17): - HyperIdealGoldenJava: Clausen/Л/ImLi2, ζ13/14/15/ζ, both tetrahedron-volume formulas (real de.varylab…Clausen / HyperIdealUtility). - EuclideanGoldenJava / SphericalGoldenJava: angle formulas + β relations + Л energy terms, plus FULL-MESH oracles driving the real EuclideanCyclicFunctional / SphericalFunctional on a shared tetrahedron — per-vertex gradient (Θ−Σα) and ΔE = E(x)−E(0) (C++ Gauss-Legendre path integral vs Java closed form). - SphericalGoldenJava.FullMeshEdgeDofGradient: edge-DOF gradient (vertex + edge components, α_opp⁺+α_opp⁻−θ_e) vs raw conformalEnergyAndGradient — locks Finding 3 at the solution level (audit items 4 & 5). - PeriodMatrix.NormalizeModulus_GoldenJava: τ-reduction fold convention vs the real DiscreteEllipticUtility.normalizeModulus (audit items 7 & 8). Subtlety documented: the spherical oracles call Java's raw conformalEnergyAndGradient, not evaluate() (which pre-runs a Brent gauge maximization that C++ factors into the Newton solver's spherical_gauge_shift). Also: - P1-2 (layout.hpp): Euclidean holonomy now uses a per-cut-edge rigid-motion fit g(z)=a·z+b, exposing residual_rotation = |arg(a)| as a diagnostic; non- regressive (flat case a=1 reduces to the old midpoint formula). - P1-3 (period_matrix.hpp): is_in_fundamental_domain fixed to the correct half-open SL(2,ℤ) domain (−½ ≤ Re < ½). Updated the now-exposed ComputePeriodMatrix_ReducedTau_InFD to assert the normalizeModulus domain (closed +½ edge) instead. Test counts (single source of truth = doc/api/tests.md): 272/272 pass, 0 skipped (26 non-CGAL + 246 CGAL). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#include "euclidean_geometry.hpp"
|
||||
#include "euclidean_functional.hpp"
|
||||
#include "euclidean_hessian.hpp"
|
||||
#include "clausen.hpp"
|
||||
#include <gtest/gtest.h>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
@@ -290,3 +291,117 @@ TEST(EuclideanFunctional, GradientCheck_MixedPinnedVertices)
|
||||
EXPECT_TRUE(gradient_check_euclidean(mesh, x, maps))
|
||||
<< "Gradient check failed for mixed pinned/variable vertices";
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Golden-value oracle — pin the Euclidean angle formula and the 2·Л(α) energy
|
||||
// term bit-for-bit against the upstream Java reference (EuclideanCyclicFunctional
|
||||
// .triangleEnergyAndAlphas, lines 341-361), captured by running the compiled Java
|
||||
// library (openjdk 17) with the real de.varylab…Clausen.Л on these exact edge
|
||||
// lengths. Companion to HyperIdealGoldenJava and SphericalGoldenJava: locks the
|
||||
// absolute angle/energy values against an independent implementation, catching
|
||||
// silent index/sign drift the curl-free path-integral gradient check cannot see.
|
||||
//
|
||||
// To regenerate: /tmp/oracle/EucOracle.java. Values are Java printf %.17g.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
TEST(EuclideanGoldenJava, AngleAndLobachevskyEnergyFromLengths)
|
||||
{
|
||||
auto check = [](double l12, double l23, double l31,
|
||||
double a1_g, double a2_g, double a3_g, double L_g) {
|
||||
auto fa = euclidean_angles_from_lengths(l12, l23, l31);
|
||||
EXPECT_TRUE(fa.valid);
|
||||
EXPECT_NEAR(fa.alpha1, a1_g, 1e-12);
|
||||
EXPECT_NEAR(fa.alpha2, a2_g, 1e-12);
|
||||
EXPECT_NEAR(fa.alpha3, a3_g, 1e-12);
|
||||
const double Lterm = 2.0 * Lobachevsky(fa.alpha1)
|
||||
+ 2.0 * Lobachevsky(fa.alpha2)
|
||||
+ 2.0 * Lobachevsky(fa.alpha3);
|
||||
EXPECT_NEAR(Lterm, L_g, 1e-12);
|
||||
};
|
||||
check(1.0, 1.2, 0.9,
|
||||
1.3637649752769678, 0.82416964552030680, 0.95365803279251860,
|
||||
1.9456273836230942);
|
||||
check(1.0, 1.0, 1.0,
|
||||
1.0471975511965979, 1.0471975511965979, 1.0471975511965979,
|
||||
2.0298832128193070);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// FULL-MESH golden oracle — the strongest cross-check: drives the REAL upstream
|
||||
// EuclideanCyclicFunctional (openjdk 17) on a tetrahedron loaded from a shared
|
||||
// OBJ (identical topology + geometry to make_tetrahedron()), and pins BOTH the
|
||||
// per-vertex gradient G_v = Θ_v − Σα AND the energy difference ΔE = E(x) − E(0)
|
||||
// bit-for-bit against it.
|
||||
//
|
||||
// This closes audit missing-test item 5 (full-mesh energy + gradient at a known
|
||||
// x). It is genuinely independent of the C++ implementation in two ways:
|
||||
// • the gradient is the upstream library's own analytic gradient (not an FD
|
||||
// check, which only proves curl-freeness of the C++ self-consistent energy);
|
||||
// • the energy is Java's CLOSED-FORM functional value, whereas C++ computes it
|
||||
// as a Gauss-Legendre PATH INTEGRAL of its gradient — two different methods
|
||||
// that must agree on ΔE (the initialEnergy constant and the φ·λ⁰ term cancel
|
||||
// in the difference, and there are no edge DOFs here).
|
||||
//
|
||||
// Setup parity (verified against UnwrapUtility.prepareInvariantDataEuclidean):
|
||||
// closed mesh, ALL 4 vertices variable (no pin), Θ_v = 2π, no edge DOFs,
|
||||
// λ°_e = 2·log(|p_i − p_j|), per-vertex u(P) = 0.10·X − 0.07·Y + 0.13·Z.
|
||||
//
|
||||
// To regenerate: /tmp/oracle/{tet.obj,EucMeshOracle.java}. Values are Java %.17g.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
TEST(EuclideanGoldenJava, FullMeshGradientAndEnergy_Tetrahedron)
|
||||
{
|
||||
constexpr double TWO_PI = 2.0 * 3.14159265358979323846264338328;
|
||||
|
||||
auto mesh = make_tetrahedron(); // same 4 vertices as /tmp/oracle/tet.obj
|
||||
auto maps = setup_euclidean_maps(mesh);
|
||||
compute_euclidean_lambda0_from_mesh(mesh, maps);
|
||||
|
||||
// All four vertices are DOFs (Java prepareInvariantData makes every interior
|
||||
// vertex variable on a closed mesh); Θ_v = 2π; no edge DOFs.
|
||||
int idx = 0;
|
||||
for (auto v : mesh.vertices()) {
|
||||
maps.v_idx[v] = idx++;
|
||||
maps.theta_v[v] = TWO_PI;
|
||||
}
|
||||
|
||||
auto u_of = [](const Point3& p) {
|
||||
return 0.10 * p.x() - 0.07 * p.y() + 0.13 * p.z();
|
||||
};
|
||||
|
||||
std::vector<double> x(static_cast<std::size_t>(idx), 0.0);
|
||||
for (auto v : mesh.vertices())
|
||||
x[static_cast<std::size_t>(maps.v_idx[v])] = u_of(mesh.point(v));
|
||||
|
||||
auto G = euclidean_gradient(mesh, x, maps);
|
||||
|
||||
// Java golden gradients keyed by vertex position (order-independent lookup).
|
||||
struct GoldRow { double X, Y, Z, G; };
|
||||
const GoldRow gold[4] = {
|
||||
{ 1, 1, 1, 3.5277511803984396},
|
||||
{ 1, -1, -1, 3.2837611905358440},
|
||||
{-1, 1, -1, 2.3437485291237100},
|
||||
{-1, -1, 1, 3.4111097143011780},
|
||||
};
|
||||
for (auto v : mesh.vertices()) {
|
||||
const auto& p = mesh.point(v);
|
||||
const double g = G[static_cast<std::size_t>(maps.v_idx[v])];
|
||||
bool matched = false;
|
||||
for (const auto& row : gold) {
|
||||
if (std::abs(p.x() - row.X) < 1e-9 &&
|
||||
std::abs(p.y() - row.Y) < 1e-9 &&
|
||||
std::abs(p.z() - row.Z) < 1e-9) {
|
||||
EXPECT_NEAR(g, row.G, 1e-12)
|
||||
<< "gradient mismatch at (" << p.x() << "," << p.y()
|
||||
<< "," << p.z() << ")";
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(matched) << "unexpected vertex position";
|
||||
}
|
||||
|
||||
// ΔE = E(x) − E(0). C++ path integral vs Java closed-form functional value.
|
||||
std::vector<double> x0(static_cast<std::size_t>(idx), 0.0);
|
||||
const double dE = euclidean_energy(mesh, x, maps)
|
||||
- euclidean_energy(mesh, x0, maps);
|
||||
EXPECT_NEAR(dE, 0.15962619236187336, 1e-12);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user