add GTest infrastructure and port first mathematical tests from Java

- Add Google Test via CMake FetchContent (v1.14.0)
- Add clausen.hpp: Clausen integral, Lobachevsky function, Im(Li2)
- Add hyper_ideal_utility.hpp: generalized and ideal-vertex hyperbolic
  tetrahedron volume formulas using Eigen for the 4x4 Gram determinant
- Port ClausenTest (5 tests) and HyperIdealUtilityTest (8 tests) from
  Java/JUnit — all 13 pass with same tolerances as the Java originals
- Fix pre-existing VIEWER/viewer case mismatch in CMakeLists.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-09 01:07:19 +02:00
parent 94533514fc
commit c30d540521
8 changed files with 468 additions and 15 deletions

View File

@@ -0,0 +1,108 @@
#pragma once
// Hyperbolic tetrahedron volume formulas.
// Ported from de.varylab.discreteconformal.functional.HyperIdealUtility (Java).
#include "clausen.hpp"
#include <Eigen/Dense>
#include <cmath>
#include <complex>
namespace conformallab {
// Volume of a generalized hyperbolic tetrahedron with dihedral angles A..F.
// Formula: Meyerhoff / Ushijima (Springer 2006).
// Corresponds to Java HyperIdealUtility.calculateTetrahedronVolume().
inline double calculateTetrahedronVolume(double A, double B, double C,
double D, double E, double F) {
constexpr double pi = 3.14159265358979323846264338328;
// Degenerate if any angle equals pi.
if (A == pi || B == pi || C == pi || D == pi || E == pi || F == pi)
return 0.0;
const double sA = std::sin(A), sB = std::sin(B), sC = std::sin(C);
const double sD = std::sin(D), sE = std::sin(E), sF = std::sin(F);
const double cA = std::cos(A), cB = std::cos(B), cC = std::cos(C);
const double cD = std::cos(D), cE = std::cos(E), cF = std::cos(F);
// Unit complex numbers e^(i*angle).
using Cx = std::complex<double>;
auto polar = [](double angle) { return std::polar(1.0, angle); };
Cx ad = polar(A + D), be = polar(B + E), cf = polar(C + F);
Cx abc = polar(A + B + C), abf = polar(A + B + F);
Cx ace = polar(A + C + E), aef = polar(A + E + F);
Cx bcd = polar(B + C + D), bdf = polar(B + D + F);
Cx def = polar(D + E + F), cde = polar(C + D + E);
Cx abde = ad * be, acdf = ad * cf, bcef = be * cf;
Cx abcdef = abc * def;
Cx z = ad + be + cf + abf + ace + bcd + def + abcdef;
// Gram matrix of the tetrahedron.
Eigen::Matrix4d G;
G << 1.0, -cA, -cB, -cF,
-cA, 1.0, -cC, -cE,
-cB, -cC, 1.0, -cD,
-cF, -cE, -cD, 1.0;
Cx sqrtG = std::sqrt(Cx(G.determinant(), 0.0));
Cx f = Cx(sA*sD + sB*sE + sC*sF, 0.0);
Cx f1 = f - sqrtG;
Cx f2 = f + sqrtG;
Cx z1 = -2.0 * f1 / z;
Cx z2 = -2.0 * f2 / z;
auto U = [&](Cx zi) {
return 0.5 * (
+ ImLi2(zi)
+ ImLi2(abde * zi)
+ ImLi2(acdf * zi)
+ ImLi2(bcef * zi)
- ImLi2(-abc * zi)
- ImLi2(-aef * zi)
- ImLi2(-bdf * zi)
- ImLi2(-cde * zi)
);
};
return (U(z1) - U(z2)) / 2.0;
}
// Volume of a hyperideal tetrahedron with one ideal vertex (at gamma).
// Dihedral angles at the ideal vertex: gamma1, gamma2, gamma3.
// Dihedral angles at opposite edges: alpha23, alpha31, alpha12.
// Formula: KolpakovMednykh (arxiv math/0603097).
// Corresponds to Java HyperIdealUtility.calculateTetrahedronVolumeWithIdealVertexAtGamma().
inline double calculateTetrahedronVolumeWithIdealVertexAtGamma(
double gamma1, double gamma2, double gamma3,
double alpha23, double alpha31, double alpha12)
{
constexpr double pi = 3.14159265358979323846264338328;
auto L = [](double x) { return Lobachevsky(x); };
double result = L(gamma1) + L(gamma2) + L(gamma3);
result += L((pi + alpha31 - alpha12 - gamma1) / 2.0);
result += L((pi + alpha12 - alpha23 - gamma2) / 2.0);
result += L((pi + alpha23 - alpha31 - gamma3) / 2.0);
result += L((pi - alpha31 + alpha12 - gamma1) / 2.0);
result += L((pi - alpha12 + alpha23 - gamma2) / 2.0);
result += L((pi - alpha23 + alpha31 - gamma3) / 2.0);
result += L((pi + alpha31 + alpha12 - gamma1) / 2.0);
result += L((pi + alpha12 + alpha23 - gamma2) / 2.0);
result += L((pi + alpha23 + alpha31 - gamma3) / 2.0);
result += L((pi - alpha31 - alpha12 - gamma1) / 2.0);
result += L((pi - alpha12 - alpha23 - gamma2) / 2.0);
result += L((pi - alpha23 - alpha31 - gamma3) / 2.0);
return result / 2.0;
}
} // namespace conformallab