fix: correct 16 inconsistencies found by consistency audit
Math / code: - layout.hpp: add explanatory comment for Möbius deck transformation (from_three with z1=w1, z2=w2 encodes T fixing cut-edge endpoints) - layout.hpp: document spherical holonomy limitation — Vector2d stores only (x,y) of 3-D position diff; full SO(3) representation deferred Gradient sign convention (CLAUDE.md was wrong): - Euclidean and Spherical both use G_v = Θ_v − actual (target minus actual) - HyperIdeal uses G_v = actual − Θ_v - Hessian sign differs: Euclidean PSD, Spherical NSD → −H, HyperIdeal PSD Test counts (were inconsistent across all files): - Actual: 176 CGAL tests, 2 GTEST_SKIP (not 173/170/174, not 1 skip) - The 2 skips are EuclideanFunctional + SphericalFunctional Hessian gradient checks (Java @Ignore ports) — not HyperIdeal Hessian as previously stated - doc/api/tests.md: add missing SmokeEuclidean suite (3 tests), EuclideanLayout (2), SphericalLayout (1), fix GaussBonnet 8→12, MeshIO 9→6, Layout 8→6, EuclideanFunctional 11→12, HomologyGenerators no longer a GTEST_SKIP stub (live test on brezel2.obj) - doc/roadmap/phases.md: Phase 7 cumulative 158→176 tests - doc/roadmap/phases.md: Phase 3 clarified — HyperIdeal Hessian is FD - CLAUDE.md: suite count 28→34, test ref 173+36→174+36 - scripts/try_it.sh: expected output 173/1 skipped → 174/2 skipped CI table (CLAUDE.md): - test-cgal now triggers on pull requests only (not main/dev pushes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
21
CLAUDE.md
21
CLAUDE.md
@@ -126,8 +126,13 @@ After `compute_*_lambda0_from_mesh()` the original vertex positions are no longe
|
||||
### Newton solver (`newton_solver.hpp`)
|
||||
|
||||
The gradient sign convention differs between modes:
|
||||
- **Euclidean/HyperIdeal:** `G_v = actual_angle_sum − Θ_v`, H is PSD → `SimplicialLDLT(H)`
|
||||
- **Spherical:** `G_v = Θ_v − actual_angle_sum`, H is NSD → `SimplicialLDLT(−H)`
|
||||
- **Euclidean/Spherical:** `G_v = Θ_v − actual_angle_sum` (target minus actual)
|
||||
- **HyperIdeal:** `G_v = actual_angle_sum − Θ_v` (actual minus target)
|
||||
|
||||
Hessian sign and solver:
|
||||
- **Euclidean:** H is PSD (cotangent Laplacian) → `SimplicialLDLT(H)`
|
||||
- **Spherical:** H is NSD (concave energy) → `SimplicialLDLT(−H)` (sign flip inside `newton_spherical`)
|
||||
- **HyperIdeal:** H is PSD (strictly convex) → `SimplicialLDLT(H)`
|
||||
|
||||
When `SimplicialLDLT` fails (rank-deficient H — gauge mode on closed mesh without pinned vertex), the solver automatically retries with `Eigen::SparseQR` to find the minimum-norm step orthogonal to the null space. Public API: `solve_linear_system(H, rhs, &used_fallback)`.
|
||||
|
||||
@@ -235,11 +240,17 @@ Two jobs in `.gitea/workflows/cpp-tests.yml`:
|
||||
| Job | CMake flags | Deps | Triggers on |
|
||||
|---|---|---|---|
|
||||
| `test-fast` | *(none)* | Eigen + GTest only | all branches |
|
||||
| `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | `main`, `dev`, PRs only |
|
||||
| `test-cgal` | `-DWITH_CGAL_TESTS=ON` | + Boost | pull requests only |
|
||||
|
||||
Runner: `eulernest` — self-hosted Raspberry Pi, ARM64, Ubuntu 22.04. Docker image: `git.eulernest.eu/conformallab/ci-cpp:latest`. `test-cgal` needs `test-fast` to pass first (`needs: test-fast`).
|
||||
|
||||
Expected results: **36 non-CGAL tests pass**, **176 CGAL tests pass, 1 skipped** (intentional `GTEST_SKIP` stub for analytic HyperIdeal Hessian — deferred to Phase 9b).
|
||||
Expected results: **36 non-CGAL tests pass**, **174 CGAL tests pass, 2 skipped**.
|
||||
|
||||
The 2 intentional skips are Java `@Ignore` ports:
|
||||
- `EuclideanFunctional.GradientCheck_Hessian` — analytic Euclidean Hessian gradient check
|
||||
- `SphericalFunctional.GradientCheck_Hessian` — analytic Spherical Hessian gradient check
|
||||
|
||||
Both are deferred to Phase 9b. Do not remove them.
|
||||
|
||||
## Release state
|
||||
|
||||
@@ -287,7 +298,7 @@ Root-level files added at v0.7.0:
|
||||
| Full pipeline API for all three geometries | `doc/api/pipeline.md` |
|
||||
| What does each processing unit require/provide (contracts)? | `doc/api/contracts.md` |
|
||||
| How to add a new functional / geometry mode / port from Java | `doc/api/extending.md` |
|
||||
| All 28 test suites, 173+36 tests, individual counts | `doc/api/tests.md` |
|
||||
| All 34 test suites, 174+36 tests, individual counts | `doc/api/tests.md` |
|
||||
| Phase 8 CGAL package design + Declarative YAML pipeline spec | `doc/api/cgal-package.md` |
|
||||
|
||||
### Concepts & specs
|
||||
|
||||
@@ -698,6 +698,12 @@ inline Layout3D spherical_layout(
|
||||
result.pos[vs.idx()], result.pos[vt.idx()],
|
||||
arc_len(mesh.prev(hx)), arc_len(mesh.next(hx)));
|
||||
Eigen::Vector3d diff = p_tri - result.pos[vn.idx()];
|
||||
// Note: spherical holonomy is geometrically a 3-D rotation, not a 2-D
|
||||
// translation. The Vector2d here stores only the (x,y) component of the
|
||||
// S²-position difference across the cut, which is an approximation.
|
||||
// For accurate spherical holonomy (rotation axis + angle) use the full
|
||||
// 3-D positions in result.pos[] directly. Phase 10+ will replace this
|
||||
// with a proper SO(3) representation.
|
||||
holonomy->translations.push_back(Eigen::Vector2d(diff.x(), diff.y()));
|
||||
}
|
||||
}
|
||||
@@ -823,6 +829,13 @@ inline Layout2D hyper_ideal_layout(
|
||||
Eigen::Vector2d p_tri = detail::trilaterate_hyp(result.uv[vs.idx()], result.uv[vt.idx()], D, da, db);
|
||||
detail::set_face_huv_2d(result.halfedge_uv, mesh, hx, result.uv, p_tri);
|
||||
using C = std::complex<double>;
|
||||
// Möbius deck transformation T across cut edge (vs,vt):
|
||||
// T is the unique Möbius isometry of the Poincaré disk that:
|
||||
// - fixes vs and vt (z1=w1, z2=w2: the cut-edge endpoints are
|
||||
// identified across the seam, so T maps each to itself)
|
||||
// - maps vn (placed side) → p_tri (virtual side)
|
||||
// This uniquely determines the hyperbolic translation/rotation
|
||||
// along the geodesic through vs and vt.
|
||||
holonomy->mobius_maps.push_back(MobiusMap::from_three(
|
||||
C(result.uv[vs.idx()].x(), result.uv[vs.idx()].y()),
|
||||
C(result.uv[vs.idx()].x(), result.uv[vs.idx()].y()),
|
||||
|
||||
@@ -29,17 +29,17 @@ All tests have CTest prefix `cgal.` (set via `TEST_PREFIX "cgal."` in CMakeLists
|
||||
| `ConformalMeshProperties` | `test_conformal_mesh.cpp` | 5 | Property maps (λ, θ, idx, α, geometry type) |
|
||||
| `ConformalMeshValidity` | `test_conformal_mesh.cpp` | 1 | CGAL validity for all factory meshes |
|
||||
| `HyperIdealFunctional` | `test_hyper_ideal_functional.cpp` | 7 | FD gradient checks + Hessian symmetry |
|
||||
| `SphericalFunctional` | `test_spherical_functional.cpp` | 12 | Angle formula + gradient + gauge-fix |
|
||||
| `EuclideanFunctional` | `test_euclidean_functional.cpp` | 11 | Angle formula + gradient |
|
||||
| `SphericalFunctional` | `test_spherical_functional.cpp` | 12 | Angle formula + gradient + gauge-fix *(1 skipped)* |
|
||||
| `EuclideanFunctional` | `test_euclidean_functional.cpp` | 12 | Angle formula + gradient *(1 skipped)* |
|
||||
| `EuclideanHessian` | `test_euclidean_hessian.cpp` | 9 | Cotangent Laplacian structure, FD agreement, PSD, null space |
|
||||
| `SphericalHessian` | `test_spherical_hessian.cpp` | 8 | Derivative correctness, NSD at equilibrium |
|
||||
| `NewtonSolver` | `test_newton_solver.cpp` | 11 | Convergence: Euclidean ×3, Spherical ×4, HyperIdeal ×4 |
|
||||
| `SparseQRFallback` | `test_newton_solver.cpp` | 3 | Full-rank LDLT · singular matrix → QR · closed mesh gauge mode |
|
||||
| `MeshIO` | `test_mesh_io.cpp` | 9 | OFF/OBJ round-trips, error handling |
|
||||
| `MeshIO` | `test_mesh_io.cpp` | 6 | OFF/OBJ round-trips, error handling |
|
||||
| `Pipeline` | `test_pipeline.cpp` | 5 | End-to-end: build → setup → solve → export → reload |
|
||||
| `Layout` | `test_layout.cpp` | 8 | Edge-length preservation (Eucl./Spher.), Poincaré disk layout |
|
||||
| `Layout` | `test_layout.cpp` | 6 | Edge-length preservation (Eucl./Spher.), Poincaré disk layout |
|
||||
| `Serialization` | `test_layout.cpp` | 2 | JSON and XML round-trips (DOF vector + layout UVs) |
|
||||
| `GaussBonnet` | `test_phase6.cpp` | 8 | χ, genus, sum/RHS, deficit, check, enforce |
|
||||
| `GaussBonnet` | `test_phase6.cpp` | 12 | χ, genus, sum/RHS, deficit, check, enforce |
|
||||
| `CutGraph` | `test_phase6.cpp` | 6 | Tree-cotree, open/closed meshes, flag–index consistency |
|
||||
| `HyperbolicTrilateration` | `test_phase6.cpp` | 4 | Möbius + law of cosines: exact distances, disk interior, off-origin |
|
||||
| `Normalisation` | `test_phase6.cpp` | 4 | Euclidean centroid, length ratios, Möbius centring |
|
||||
@@ -51,16 +51,19 @@ All tests have CTest prefix `cgal.` (set via `TEST_PREFIX "cgal."` in CMakeLists
|
||||
| `PeriodMatrix` | `test_phase7.cpp` | 7 | τ ∈ ℍ, SL(2,ℤ) reduction, exception outside ℍ |
|
||||
| `FundamentalDomain` | `test_phase7.cpp` | 7 | Genus-1 parallelogram CCW, generators, g > 1 empty |
|
||||
| `TilingCopy/Neighbourhood` | `test_phase7.cpp` | 4 | Translation correct, tile count |
|
||||
| `CuttingUtility` | `test_geometry_utils.cpp` | 3 | point_in_triangle_2d: false, true, unit triangle (Java CuttinUtilityTest) |
|
||||
| `UnwrapUtility` | `test_geometry_utils.cpp` | 2 | corner angle: collinear → π, equilateral → π/3 (Java UnwrapUtilityTest) |
|
||||
| `ConvergenceUtility` | `test_geometry_utils.cpp` | 6 | circumradius + scale-invariant R_f/√A (Java ConvergenceUtilityTests) |
|
||||
| `HomologyGenerators` | `test_geometry_utils.cpp` | 1 | GTEST_SKIP stub — genus-2 mesh missing (Java HomologyTest Test 7, Phase 9c) |
|
||||
| `CuttingUtility` | `test_geometry_utils.cpp` | 3 | `point_in_triangle_2d`: false, true, unit triangle (Java CuttingUtilityTest) |
|
||||
| `UnwrapUtility` | `test_geometry_utils.cpp` | 2 | Corner angle: collinear → π, equilateral → π/3 (Java UnwrapUtilityTest) |
|
||||
| `ConvergenceUtility` | `test_geometry_utils.cpp` | 6 | Circumradius + scale-invariant R_f/√A (Java ConvergenceUtilityTests) |
|
||||
| `EuclideanLayout` | `test_geometry_utils.cpp` | 2 | Euclidean layout round-trip edge lengths |
|
||||
| `SphericalLayout` | `test_geometry_utils.cpp` | 1 | Spherical layout on unit sphere |
|
||||
| `HomologyGenerators` | `test_geometry_utils.cpp` | 1 | Genus-2 cut graph: χ = −2, 4 cut edges (`brezel2.obj`) |
|
||||
| `SmokeEuclidean` | `test_scalability_smoke.cpp` | 3 | Smoke tests on real meshes: CatHead (open), Brezel genus-1, Brezel2 genus-2 |
|
||||
|
||||
**Total: 170 tests, 1 intentional skip.**
|
||||
**Total: 176 tests, 2 intentional skips.**
|
||||
|
||||
The skip is `HomologyGenerators.Genus2_FourGeneratorPaths_BLOCKED` — blocked until
|
||||
a genus-2 mesh is available (Phase 9c). It corresponds to a Java `@Ignore`-annotated
|
||||
test in the original library.
|
||||
The 2 skips are Java `@Ignore` ports — stubs until the analytic Hessian gradient checks are implemented (Phase 9b):
|
||||
- `SphericalFunctional.GradientCheck_Hessian`
|
||||
- `EuclideanFunctional.GradientCheck_Hessian`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
Phase 1 Clausen / Lobachevsky / ImLi₂ special functions ✅
|
||||
Phase 2 Hyper-ideal geometry (ζ, lᵢⱼ, αᵢⱼ, σᵢ, σᵢⱼ) ✅
|
||||
Phase 3 CGAL Surface_mesh infrastructure + all three functionals
|
||||
(Euclidean, Spherical, HyperIdeal) + analytical Hessians ✅
|
||||
(Euclidean, Spherical, HyperIdeal)
|
||||
+ analytical Hessians for Euclidean + Spherical
|
||||
(HyperIdeal Hessian: symmetric FD — analytic deferred to 9b) ✅
|
||||
Phase 4 Newton solver (SimplicialLDLT + SparseQR fallback)
|
||||
+ Mesh I/O (OFF/OBJ/PLY) + example programs ✅ 68 tests
|
||||
Phase 5 Priority-BFS layout + CLI app + JSON/XML serialisation ✅ 95 tests
|
||||
@@ -24,7 +26,7 @@ Phase 6 Gauss–Bonnet check/enforce, tree-cotree cut graph (2g),
|
||||
exact hyperbolic trilateration, layout normalisation ✅ 121 tests
|
||||
Phase 7 MobiusMap, halfedge_uv, Möbius holonomy (SU(1,1)),
|
||||
period matrix τ∈ℍ + SL(2,ℤ) reduction,
|
||||
fundamental domain parallelogram + tiling ✅ 158 tests
|
||||
fundamental domain parallelogram + tiling ✅ 176 tests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
# bash scripts/try_it.sh
|
||||
#
|
||||
# Expected output (last lines):
|
||||
# [PASS] 173 CGAL tests pass, 1 skipped
|
||||
# [PASS] 174 CGAL tests pass, 2 skipped
|
||||
# [PASS] 36 non-CGAL tests pass
|
||||
# [EXAMPLE] Converged in N iterations. ||G||_inf < 1e-9
|
||||
|
||||
|
||||
Reference in New Issue
Block a user