feat(phase3c): port SphericalFunctional onto ConformalMesh; update README

New headers:
- spherical_geometry.hpp: spherical arc length l(λ) and half-angle formula
  for interior angles of a spherical triangle (SphericalFaceAngles struct)
- spherical_functional.hpp: SphericalMaps bundle, setup/assign DOF helpers,
  compute_lambda0_from_mesh(), gradient (Θ_v − Σα_v; Schläfli edge formula),
  energy via 10-point Gauss-Legendre path integral, gradient_check_spherical()

Updated:
- mesh_builder.hpp: add make_spherical_tetrahedron() (vertices on unit sphere)
  and make_octahedron_face() (single right-angled spherical triangle)
- tests/cgal/CMakeLists.txt: enable test_spherical_functional.cpp
- README.md: rewrite for CGAL-package goal, two test targets, all headers,
  updated project tree, Phase progress table, key design decisions

Tests (cgal.SphericalFunctional.*): 8 active + 1 skip
- OctaFaceAnglesAreRightAngles, SpherTetAngleSumExceedsPi
- GradientCheck_{OctaFaceVertex, SpherTetVertex, SpherTetAllDofs,
  SpherFan4Vertex, MixedPinnedVertices}
- AnglesFiniteAtKnownPoint
All 30 cgal.* tests pass (2 @Ignore skips).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-12 00:01:24 +02:00
parent 516ac89bd8
commit a4c2a89e7e
6 changed files with 851 additions and 33 deletions

View File

@@ -0,0 +1,82 @@
#pragma once
// spherical_geometry.hpp
//
// Pure-math building blocks for the spherical discrete conformal map.
// Ported from de.varylab.discreteconformal.functional.SphericalFunctional
// (the geometry helpers embedded there).
//
// Notation:
// u_i vertex conformal factor (DOF)
// λ°_e base log-length of edge e (fixed initial value)
// λ_ij effective log-length = λ°_ij + u_i + u_j
// l_ij spherical arc length = 2·asin(min(exp(λ_ij/2), 1))
// α_k interior angle of the spherical triangle at vertex k
#include <cmath>
#include <algorithm>
namespace conformallab {
constexpr double PI_SPHER = 3.14159265358979323846264338328;
// ── Effective spherical arc length ────────────────────────────────────────────
// l(λ) = 2·asin(min(exp(λ/2), 1)).
// Clamps exp(λ/2) to [0, 1] so the arcsin stays in domain.
inline double spherical_l(double lambda)
{
double half = std::exp(lambda * 0.5);
if (half >= 1.0) half = 1.0 - 1e-15;
if (half <= 0.0) return 0.0;
return 2.0 * std::asin(half);
}
// ── Interior angles of a spherical triangle ──────────────────────────────────
struct SphericalFaceAngles {
double alpha1, alpha2, alpha3; // corner angles at v1, v2, v3
bool valid; // false when the three lengths fail the
// spherical triangle inequality
};
// Compute corner angles from spherical arc lengths using the half-angle formula.
//
// Convention (matching the halfedge cycle h0→v1→v2, h1→v2→v3, h2→v3→v1):
// l12 arc length of edge opposite v3 (edge e12)
// l23 arc length of edge opposite v1 (edge e23)
// l31 arc length of edge opposite v2 (edge e31)
//
// Half-angle formula (spherical law of cosines):
// α_k = 2·atan2(sqrt(sin(s-a)·sin(s-b)), sqrt(sin(s)·sin(s-c)))
// where a,b are the two edges ADJACENT to vertex k, c is the opposite edge.
//
// Equivalently (in terms of s-deficiencies):
// α1 = 2·atan2( sqrt(sin(s12)·sin(s31)), sqrt(sin(s)·sin(s23)) )
// α2 = 2·atan2( sqrt(sin(s12)·sin(s23)), sqrt(sin(s)·sin(s31)) )
// α3 = 2·atan2( sqrt(sin(s23)·sin(s31)), sqrt(sin(s)·sin(s12)) )
//
// where s = (l12+l23+l31)/2 and s_ij = s - l_ij.
inline SphericalFaceAngles spherical_angles(double l12, double l23, double l31)
{
double s = (l12 + l23 + l31) * 0.5;
double s12 = s - l12;
double s23 = s - l23;
double s31 = s - l31;
// Spherical triangle inequalities: all s-deficiencies > 0 and s < π.
if (s12 <= 0.0 || s23 <= 0.0 || s31 <= 0.0 || s >= PI_SPHER)
return {0.0, 0.0, 0.0, false};
const double ss = std::sin(s);
const double ss12 = std::sin(s12);
const double ss23 = std::sin(s23);
const double ss31 = std::sin(s31);
double a1 = 2.0 * std::atan2(std::sqrt(ss12 * ss31), std::sqrt(ss * ss23));
double a2 = 2.0 * std::atan2(std::sqrt(ss12 * ss23), std::sqrt(ss * ss31));
double a3 = 2.0 * std::atan2(std::sqrt(ss23 * ss31), std::sqrt(ss * ss12));
return {a1, a2, a3, true};
}
} // namespace conformallab