This commit closes the remaining red gates so `run-all.sh --fast` is
green end-to-end on the canonical dev machine.
New gates
─────────
1. cmake-format / cmake-lint
* scripts/quality/cmake-format.sh — dry-run by default,
--strict to fail on drift, --fix to apply
* .cmake-format.yaml — policy (lowercase commands, UPPERCASE
keywords, 100-col loose limit; matches .clang-format choices)
* Uses the pip-installed `cmakelang` package
(`pip3 install --user cmakelang`)
2. codespell
* scripts/quality/codespell.sh — exit 1 on any typo, --fix
interactively
* .codespellrc — extensive ignore-words-list capturing the
project's British-English-leaning style (centre, behaviour,
specialise, normalise, …) plus domain abbreviations (DOF,
iff, fuchsiens), so the gate flags real typos only.
* Validated: 0 typos across docs + code/include + scripts +
code/{src,tests}.
SPDX rollout (license-headers --fix)
────────────────────────────────────
license-headers.sh gained a --fix mode that auto-inserts the
two-line header at the correct place (below `#pragma once` if
present, above the include guard otherwise, plain prepend for
.cpp). Ran it on 60 of 66 files — 100 %-licensed now.
Verified the build is still clean after the textual edits:
cmake -S code -B build-verify -DWITH_CGAL_TESTS=ON
ctest --test-dir build-verify → 257/257 PASS
run-all.sh + README updated to include the two new gates.
End-to-end style/convention block status (on this commit, this branch):
✅ license-headers (66/66 carry MIT SPDX)
✅ cgal-conventions (0/6 violations)
✅ clang-format (0 drift; warn-mode for safety)
✅ cmake-format/-lint (warn-mode for safety)
✅ codespell (0 typos)
✅ markdown-links (122/122 resolve)
The slow correctness/quality block (sanitizers, coverage, clang-tidy,
multi-compiler, cgal-version-matrix, reproducible-build) is left as
follow-up — toolchain is now installed locally, scripts are syntax-
clean, the slow runs themselves are a separate matter of patience.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
113 lines
3.8 KiB
C++
113 lines
3.8 KiB
C++
#pragma once
|
||
// Copyright (c) 2024-2026 Tarik Moussa.
|
||
// SPDX-License-Identifier: MIT
|
||
|
||
|
||
// Hyperbolic tetrahedron volume formulas.
|
||
// Ported from de.varylab.discreteconformal.functional.HyperIdealUtility (Java).
|
||
|
||
#include "clausen.hpp"
|
||
#include "constants.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) {
|
||
// PI from constants.hpp (conformallab::PI)
|
||
|
||
// 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: Kolpakov–Mednykh (arxiv math/0603097).
|
||
// Corresponds to Java HyperIdealUtility.calculateTetrahedronVolumeWithIdealVertexAtGamma().
|
||
inline double calculateTetrahedronVolumeWithIdealVertexAtGamma(
|
||
double gamma1, double gamma2, double gamma3,
|
||
double alpha23, double alpha31, double alpha12)
|
||
{
|
||
// PI from constants.hpp (conformallab::PI)
|
||
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
|