Files
ConformalLabpp/code/include/gauss_bonnet.hpp
Tarik Moussa 4fc48b39f0 feat(phase6): exact hyperbolic layout, Gauss–Bonnet, cut graph, normalisation — 121 tests
New files:
- gauss_bonnet.hpp: euler_characteristic, genus, Σ(2π-Θ_v) sum/rhs/deficit,
  check_gauss_bonnet (throws), enforce_gauss_bonnet (correct sign: Δ=(lhs-rhs)/V)
- cut_graph.hpp: CutGraph struct + compute_cut_graph (tree-cotree, Erickson–Whittlesey
  2005); boundary edges correctly excluded from cut set
- test_phase6.cpp: 26 new tests (GaussBonnet ×8, CutGraph ×6, HyperbolicTrilateration
  ×4, Normalisation ×4 — all pass)

layout.hpp (Phase 6 rewrite):
- detail::trilaterate_hyp: exact Möbius + hyperbolic law of cosines replacing old tanh(d/2)
- detail::center_poincare_disk: Möbius centering for hyperbolic normalisation
- normalise_euclidean: centroid → origin + PCA major-axis rotation
- normalise_hyperbolic: Möbius centering in the Poincaré disk
- normalise_spherical: Rodrigues rotation → north pole
- euclidean_layout / hyper_ideal_layout: optional CutGraph* + HolonomyData* + normalise

Bug fixes caught by new tests:
- gauss_bonnet.hpp: enforce_gauss_bonnet had wrong sign for delta
- cut_graph.hpp: boundary edges were incorrectly marked as cut edges

121 tests pass, 2 skipped (Hessian stubs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 01:15:41 +02:00

146 lines
5.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
// gauss_bonnet.hpp
//
// Phase 6 — GaussBonnet consistency check for prescribed target angles.
//
// Before calling newton_*() with custom target angles, verify that
// 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)
//
// If this fails, no conformal factor can realise the target angles and
// Newton will silently fail to converge.
//
// API:
// int euler_characteristic(mesh)
// int genus(mesh)
// double gauss_bonnet_sum(mesh, maps) — Σ(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 Δ
#include "conformal_mesh.hpp"
#include "euclidean_functional.hpp"
#include "spherical_functional.hpp"
#include "hyper_ideal_functional.hpp"
#include "constants.hpp"
#include <stdexcept>
#include <sstream>
#include <cmath>
#include <string>
namespace conformallab {
// ── Topology helpers ──────────────────────────────────────────────────────────
/// Euler characteristic χ = V E + F.
/// For closed orientable surfaces: χ = 2 2g.
inline int euler_characteristic(const ConformalMesh& mesh)
{
return static_cast<int>(mesh.number_of_vertices())
- static_cast<int>(mesh.number_of_edges())
+ static_cast<int>(mesh.number_of_faces());
}
/// Genus of a closed orientable surface: g = (2 χ) / 2.
/// Returns 0 for open meshes (boundary present) — callers should check.
inline int genus(const ConformalMesh& mesh)
{
int chi = euler_characteristic(mesh);
return (2 - chi) / 2;
}
// ── Left-hand side Σ(2π Θ_v) ─────────────────────────────────────────────
inline double gauss_bonnet_sum(
const ConformalMesh& mesh,
const ConformalMesh::Property_map<Vertex_index, double>& theta)
{
double s = 0.0;
for (auto v : mesh.vertices())
s += TWO_PI - theta[v];
return s;
}
inline double gauss_bonnet_sum(const ConformalMesh& m, const EuclideanMaps& mp)
{ return gauss_bonnet_sum(m, mp.theta_v); }
inline double gauss_bonnet_sum(const ConformalMesh& m, const SphericalMaps& mp)
{ return gauss_bonnet_sum(m, mp.theta_v); }
inline double gauss_bonnet_sum(const ConformalMesh& m, const HyperIdealMaps& mp)
{ return gauss_bonnet_sum(m, mp.theta_v); }
// ── Right-hand side 2π · χ(M) ───────────────────────────────────────────────
inline double gauss_bonnet_rhs(const ConformalMesh& mesh)
{
return TWO_PI * static_cast<double>(euler_characteristic(mesh));
}
// ── Deficit: lhs rhs (0 = GaussBonnet satisfied) ─────────────────────────
template <typename Maps>
inline double gauss_bonnet_deficit(const ConformalMesh& mesh, const Maps& maps)
{
return gauss_bonnet_sum(mesh, maps) - gauss_bonnet_rhs(mesh);
}
// ── check_gauss_bonnet — throws std::runtime_error if |deficit| > tol ─────────
inline void check_gauss_bonnet(const ConformalMesh& mesh,
double lhs,
double tol = 1e-8)
{
double rhs = gauss_bonnet_rhs(mesh);
double def = lhs - rhs;
if (std::abs(def) > tol) {
std::ostringstream msg;
msg << "GaussBonnet violated:\n"
<< " Σ(2πΘ_v) = " << lhs
<< " expected 2π·χ = " << rhs
<< " (χ = " << euler_characteristic(mesh)
<< ", genus = " << genus(mesh) << ")\n"
<< " deficit = " << def;
throw std::runtime_error(msg.str());
}
}
template <typename Maps>
inline void check_gauss_bonnet(const ConformalMesh& mesh,
const Maps& maps,
double tol = 1e-8)
{
check_gauss_bonnet(mesh, gauss_bonnet_sum(mesh, maps), tol);
}
// ── enforce_gauss_bonnet — adjust θ_v by uniform Δ ───────────────────────────
//
// Adds δ = (rhs lhs) / V to every θ_v so that GaussBonnet holds exactly.
// After this call, check_gauss_bonnet() will not throw (up to floating-point).
// Only modifies free vertices (v_idx[v] >= 0 for EuclideanMaps / SphericalMaps;
// always all vertices for the raw property-map overload).
inline void enforce_gauss_bonnet(
ConformalMesh& mesh,
ConformalMesh::Property_map<Vertex_index, double>& theta)
{
double lhs = gauss_bonnet_sum(mesh, theta);
double rhs = gauss_bonnet_rhs(mesh);
// Adding δ to every θ_v decreases the sum Σ(2πθ_v) by V·δ.
// We need lhs V·δ = rhs, so δ = (lhs rhs) / V.
double delta = (lhs - rhs) / static_cast<double>(mesh.number_of_vertices());
for (auto v : mesh.vertices())
theta[v] += delta;
}
template <typename Maps>
inline void enforce_gauss_bonnet(ConformalMesh& mesh, Maps& maps)
{
enforce_gauss_bonnet(mesh, maps.theta_v);
}
} // namespace conformallab