fix(gauss-bonnet): delete HyperIdeal overloads — wrong identity for hyperbolic metrics
Finding-B from doc/reviewer/external-audit-2026-05-30.md. The Euclidean Gauss–Bonnet identity Σ(2π−Θ_v) = 2π·χ is ONLY valid for flat/Euclidean and spherical metrics. For hyperbolic metrics (HyperIdeal) the correct identity is Σ(2π−Θ_v) − Area(M) = 2π·χ. The previously provided gauss_bonnet_sum(HyperIdealMaps) overload would silently pass the wrong LHS to check_gauss_bonnet, which would always throw "deficit = ±Area" for valid hyperbolic targets. Fix: - gauss_bonnet_sum(mesh, HyperIdealMaps) → = delete + explanation comment - enforce_gauss_bonnet(mesh, HyperIdealMaps&) → = delete + explanation comment - Header box comment rewritten with the correct hyperbolic Gauss–Bonnet identity and a clear "HyperIdeal: NOT SUPPORTED" section New test in test_phase6.cpp: - HyperIdeal_EuclideanSumDiscrepancy_DocumentsWhyCheckIsDeleted verifies numerically that the Euclidean sum = 0 but 2π·χ = 4π for a regular tetrahedron, documenting the −4π discrepancy that motivated the deletion - Three compile-time static_asserts (SFINAE) confirm the overload is not invocable with HyperIdealMaps but remains so with Euclidean/SphericalMaps 263/263 CGAL tests pass, 0 failed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,14 +7,28 @@
|
||||
// Phase 6 — Gauss–Bonnet consistency check for prescribed target angles.
|
||||
//
|
||||
// Before calling newton_*() with custom target angles, verify that
|
||||
// the angle defect sum matches the topology:
|
||||
// the angle defect sum matches the topology.
|
||||
//
|
||||
// Σ_v (2π − Θ_v) = 2π · χ(M) (Euclidean / flat)
|
||||
// Σ_v (2π − Θ_v) > 0 (spherical, χ > 0)
|
||||
// Σ_v (2π − Θ_v) < 0 (hyperbolic, χ < 0)
|
||||
// ┌─────────────────────────────────────────────────────────────────────────┐
|
||||
// │ Geometry Identity to satisfy │
|
||||
// │ ───────────────────────────────────────────────────────────────────── │
|
||||
// │ Euclidean/flat Σ_v (2π − Θ_v) = 2π · χ(M) (exact equality) │
|
||||
// │ Spherical Σ_v (2π − Θ_v) > 0 (sufficient, χ > 0) │
|
||||
// │ │
|
||||
// │ HyperIdeal — NOT SUPPORTED by this header. │
|
||||
// │ The correct hyperbolic Gauss–Bonnet identity is │
|
||||
// │ Σ_v (2π − Θ_v) − Area(M) = 2π · χ(M) │
|
||||
// │ which differs from the Euclidean identity by the Area(M) > 0 term. │
|
||||
// │ Computing Area(M) from the HyperIdeal DOFs is non-trivial. │
|
||||
// │ gauss_bonnet_sum(mesh, HyperIdealMaps) and │
|
||||
// │ enforce_gauss_bonnet(mesh, HyperIdealMaps) are therefore DELETED. │
|
||||
// │ Do NOT call check_gauss_bonnet before newton_hyper_ideal — │
|
||||
// │ it is not needed; the HyperIdeal energy is strictly convex so Newton │
|
||||
// │ converges without a pre-check. │
|
||||
// └─────────────────────────────────────────────────────────────────────────┘
|
||||
//
|
||||
// If this fails, no conformal factor can realise the target angles and
|
||||
// Newton will silently fail to converge.
|
||||
// If the Euclidean/Spherical check fails, no conformal factor can realise
|
||||
// the target angles and Newton will silently fail to converge.
|
||||
//
|
||||
// PRECONDITION — closed meshes only. Every function here sums (2π − Θ_v)
|
||||
// over ALL vertices. On a mesh with boundary the boundary vertices carry a
|
||||
@@ -26,11 +40,12 @@
|
||||
// API:
|
||||
// int euler_characteristic(mesh)
|
||||
// int genus(mesh)
|
||||
// double gauss_bonnet_sum(mesh, maps) — Σ(2π − Θ_v)
|
||||
// double gauss_bonnet_sum(mesh, EuclideanMaps/SphericalMaps) — Σ(2π − Θ_v)
|
||||
// double gauss_bonnet_rhs(mesh) — 2π · χ(M)
|
||||
// double gauss_bonnet_deficit(mesh, maps) — lhs − rhs (0 = satisfied)
|
||||
// void check_gauss_bonnet(mesh, maps [, tol]) — throws if violated
|
||||
// void enforce_gauss_bonnet(mesh, maps) — shifts θ_v by uniform Δ
|
||||
// (HyperIdealMaps overloads are deleted — see box above)
|
||||
|
||||
#include "conformal_mesh.hpp"
|
||||
#include "euclidean_functional.hpp"
|
||||
@@ -78,15 +93,23 @@ inline double gauss_bonnet_sum(
|
||||
}
|
||||
|
||||
/// `gauss_bonnet_sum` for the Euclidean-functional property bundle.
|
||||
inline double gauss_bonnet_sum(const ConformalMesh& m, const EuclideanMaps& mp)
|
||||
inline double gauss_bonnet_sum(const ConformalMesh& m, const EuclideanMaps& mp)
|
||||
{ return gauss_bonnet_sum(m, mp.theta_v); }
|
||||
/// `gauss_bonnet_sum` for the Spherical-functional property bundle.
|
||||
inline double gauss_bonnet_sum(const ConformalMesh& m, const SphericalMaps& mp)
|
||||
{ return gauss_bonnet_sum(m, mp.theta_v); }
|
||||
/// `gauss_bonnet_sum` for the HyperIdeal-functional property bundle.
|
||||
inline double gauss_bonnet_sum(const ConformalMesh& m, const HyperIdealMaps& mp)
|
||||
inline double gauss_bonnet_sum(const ConformalMesh& m, const SphericalMaps& mp)
|
||||
{ return gauss_bonnet_sum(m, mp.theta_v); }
|
||||
|
||||
// gauss_bonnet_sum for HyperIdealMaps is intentionally DELETED.
|
||||
// The correct hyperbolic Gauss–Bonnet identity is
|
||||
// Σ(2π−Θ_v) − Area(M) = 2π·χ(M)
|
||||
// not the Euclidean form Σ(2π−Θ_v) = 2π·χ(M). Providing this overload
|
||||
// would silently skip the Area term, making check_gauss_bonnet always
|
||||
// fail for valid hyperbolic targets (e.g. a genus-2 mesh with Θ_v=2π
|
||||
// gives Σ(2π−Θ_v)=0 but 2π·χ=−4π → deficit=4π ≠ 0 every time).
|
||||
// Use newton_hyper_ideal directly — no pre-check is needed because the
|
||||
// HyperIdeal energy is strictly convex (Springborn 2020 Theorem 1.3).
|
||||
inline double gauss_bonnet_sum(const ConformalMesh&, const HyperIdealMaps&) = delete;
|
||||
|
||||
// ── Right-hand side 2π · χ(M) ───────────────────────────────────────────────
|
||||
|
||||
/// Right-hand side of Gauss-Bonnet: `2π · χ(M)`.
|
||||
@@ -157,10 +180,18 @@ inline void enforce_gauss_bonnet(
|
||||
}
|
||||
|
||||
/// Distribute the Gauss-Bonnet deficit uniformly across `maps.theta_v`.
|
||||
/// Supported for EuclideanMaps and SphericalMaps only.
|
||||
/// HyperIdealMaps overload is deleted — see header comment for why.
|
||||
template <typename Maps>
|
||||
inline void enforce_gauss_bonnet(ConformalMesh& mesh, Maps& maps)
|
||||
{
|
||||
enforce_gauss_bonnet(mesh, maps.theta_v);
|
||||
}
|
||||
|
||||
// enforce_gauss_bonnet for HyperIdealMaps is intentionally DELETED.
|
||||
// The Euclidean identity Σ(2π−Θ_v)=2π·χ is not the correct pre-condition
|
||||
// for HyperIdeal. Calling this function would silently shift Θ_v to
|
||||
// satisfy the wrong identity, producing incorrect target angles.
|
||||
inline void enforce_gauss_bonnet(ConformalMesh&, HyperIdealMaps&) = delete;
|
||||
|
||||
} // namespace conformallab
|
||||
|
||||
Reference in New Issue
Block a user