// test_hyper_ideal_functional.cpp // // Phase 3b — HyperIdealFunctional ported to ConformalMesh. // // Corresponds to de.varylab.discreteconformal.functional.HyperIdealFunctionalTest. // // The Java tests use CoHDS + HyperIdealGenerator to build complex meshes and // then verify correctness via a finite-difference gradient check (FunctionalTest). // Here we apply the same gradient-check strategy on simple hand-crafted meshes // (triangle, tetrahedron) to validate the port independently of the generator. // // Test map (Java → C++) // ────────────────────── // testHessian (Ignored) → GradientCheck_Hessian (SKIPPED, not implemented) // testGradientWithHyperIdeal… → GradientCheck_AllHyperIdealTriangle (ported) // testGradientInExtendedDomain → GradientCheck_ExtendedDomain (ported) // testGradientWithHyperelliptic → GradientCheck_TetrahedronAllVariable (ported) // testFunctionalAtNaNValue → EnergyFiniteAtTestPoint (ported) #include "conformal_mesh.hpp" #include "mesh_builder.hpp" #include "hyper_ideal_functional.hpp" #include #include #include using namespace conformallab; // ════════════════════════════════════════════════════════════════════════════ // Helpers // ════════════════════════════════════════════════════════════════════════════ // Build a mesh with all vertices and edges variable, and set reasonable // DOF values: b_i = b_val, a_e = a_val. static std::vector make_x_all_variable( ConformalMesh& mesh, HyperIdealMaps& maps, double b_val, double a_val) { int n = assign_all_dof_indices(mesh, maps); std::vector x(static_cast(n)); // Vertices first, then edges (matching assign_all_dof_indices order) for (auto v : mesh.vertices()) x[static_cast(maps.v_idx[v])] = b_val; for (auto e : mesh.edges()) x[static_cast(maps.e_idx[e])] = a_val; return x; } // ════════════════════════════════════════════════════════════════════════════ // @Ignore in Java: no Hessian implemented // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_Hessian) { GTEST_SKIP() << "@Ignore in Java – Hessian not implemented in the functional"; } // ════════════════════════════════════════════════════════════════════════════ // Gradient check: single triangle, all vertices hyper-ideal // // Mirrors testGradientWithHyperIdealAndIdealPoints (simplified to single face). // DOFs: 3 vertex b-values + 3 edge a-values = 6 total. // Point: b_i = 1.0, a_e = 0.5. // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_AllHyperIdealTriangle) { auto mesh = make_triangle(); auto maps = setup_hyper_ideal_maps(mesh); auto x = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5); EXPECT_TRUE(gradient_check(mesh, x, maps)) << "Finite-difference gradient check failed on all-hyper-ideal triangle"; } // ════════════════════════════════════════════════════════════════════════════ // Gradient check in the "extended domain" // // Mirrors testGradientInTheExtendedDomain: larger DOF values // (Java: x_i = 1.2 + |rnd|, so typically > 1.2). // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_ExtendedDomain) { auto mesh = make_triangle(); auto maps = setup_hyper_ideal_maps(mesh); auto x = make_x_all_variable(mesh, maps, /*b=*/2.0, /*a=*/1.5); EXPECT_TRUE(gradient_check(mesh, x, maps)) << "Finite-difference gradient check failed in extended domain"; } // ════════════════════════════════════════════════════════════════════════════ // Gradient check: tetrahedron (4 faces), all vertices and edges variable // // Mirrors testGradientWithHyperellipticCurve — larger mesh, closed surface. // DOFs: 4 vertex b-values + 6 edge a-values = 10 total. // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_TetrahedronAllVariable) { auto mesh = make_tetrahedron(); auto maps = setup_hyper_ideal_maps(mesh); auto x = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5); EXPECT_TRUE(gradient_check(mesh, x, maps)) << "Finite-difference gradient check failed on all-variable tetrahedron"; } // ════════════════════════════════════════════════════════════════════════════ // Energy is finite (not NaN / Inf) // // Mirrors testFunctionalAtNaNValue: evaluates at a test point and checks // the energy is a real number. Uses a quad-strip to exercise the interior // edge path (adjacent faces sharing one non-border edge). // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, EnergyFiniteAtTestPoint) { auto mesh = make_quad_strip(); auto maps = setup_hyper_ideal_maps(mesh); int n = assign_all_dof_indices(mesh, maps); // Values taken from the Java testFunctionalAtNaNValue spirit: // large-ish but positive DOF values that could expose degenerate paths. std::vector x(static_cast(n), 1.5); auto res = evaluate_hyper_ideal(mesh, x, maps, true, false); EXPECT_FALSE(std::isnan(res.energy)) << "Energy must not be NaN"; EXPECT_FALSE(std::isinf(res.energy)) << "Energy must not be Inf"; } // ════════════════════════════════════════════════════════════════════════════ // Gradient check: mixed vertices (some ideal = pinned, some hyper-ideal) // // One vertex is ideal (v_idx = -1, b = 0), the other two are hyper-ideal. // This exercises the lij / αij branches for the ideal-vertex case. // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_MixedIdealHyperIdeal) { auto mesh = make_triangle(); auto maps = setup_hyper_ideal_maps(mesh); // Make v0 ideal (pinned), v1 and v2 hyper-ideal. auto vit = mesh.vertices().begin(); Vertex_index v0 = *vit++; Vertex_index v1 = *vit++; Vertex_index v2 = *vit; maps.v_idx[v0] = -1; // ideal: b_0 = 0 (fixed) maps.v_idx[v1] = 0; maps.v_idx[v2] = 1; // All edges variable: indices 2, 3, 4 int eidx = 2; for (auto e : mesh.edges()) maps.e_idx[e] = eidx++; // DOF vector: [b1, b2, a_e0, a_e1, a_e2] std::vector x = {1.0, 1.0, 0.5, 0.5, 0.5}; EXPECT_TRUE(gradient_check(mesh, x, maps)) << "Finite-difference gradient check failed for mixed ideal / hyper-ideal"; } // ════════════════════════════════════════════════════════════════════════════ // Gradient check: fan mesh (n=6), all variable // // Exercises the full halfedge-around-vertex traversal for a high-valence // interior vertex. // ════════════════════════════════════════════════════════════════════════════ TEST(HyperIdealFunctional, GradientCheck_Fan6AllVariable) { auto mesh = make_fan(6); auto maps = setup_hyper_ideal_maps(mesh); auto x = make_x_all_variable(mesh, maps, /*b=*/1.0, /*a=*/0.5); EXPECT_TRUE(gradient_check(mesh, x, maps)) << "Finite-difference gradient check failed on fan-6 mesh"; }