docs(contracts): fix three errors in processing-unit contract table (U3)

Finding-U3 from doc/reviewer/usability-audit-2026-05-31.md.

Three concrete errors corrected:

1. check_gauss_bonnet() row (was: implies all map types work)
   → now: 'EuclideanMaps/SphericalMaps only' — HyperIdealMaps overload
     is = delete (compile error since external-audit Finding-B fix)

2. enforce_gauss_bonnet() row (same issue)
   → same restriction note added

3. newton_hyper_ideal() preconditions (was: 'lambda0 initialised')
   → WRONG: HyperIdeal computes lengths from b_v, a_e via zeta functions
     internally; lambda0 plays no role.  Correct precondition:
     'no lambda0 needed · no GB pre-check'
   → Added explanation: strictly convex energy (Springborn 2020 Thm 1.3)
     → Newton converges for any targets without a pre-check

Gauss-Bonnet prose section also updated:
  - Examples now show EuclideanMaps/SphericalMaps explicitly
  - HyperIdeal compile-error note added with the correct hyperbolic
    identity Σ(2π-Θ_v) - Area = 2π·χ and why no pre-check is needed
  - Open-mesh note added (GB identity does not hold for boundary meshes)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-31 01:44:36 +02:00
parent b20a835b82
commit b8d6e23adf
2 changed files with 20 additions and 9 deletions

View File

@@ -13,11 +13,11 @@ of all preceding units.
| `load_mesh()` | Valid file path, supported format (OFF/OBJ/PLY) | Manifold, oriented, triangulated `ConformalMesh` | | `load_mesh()` | Valid file path, supported format (OFF/OBJ/PLY) | Manifold, oriented, triangulated `ConformalMesh` |
| `setup_*_maps()` | Triangulated mesh | Initialised property maps; `lambda0` zeroed | | `setup_*_maps()` | Triangulated mesh | Initialised property maps; `lambda0` zeroed |
| `compute_*_lambda0_from_mesh()` | `setup_*_maps()` called | `lambda0[e]` set from 3-D edge lengths | | `compute_*_lambda0_from_mesh()` | `setup_*_maps()` called | `lambda0[e]` set from 3-D edge lengths |
| `check_gauss_bonnet()` | `theta_v[v]` set for all vertices | Throws `std::runtime_error` if Σ(2πΘᵥ) ≠ 2π·χ(M) | | `check_gauss_bonnet()` | `theta_v[v]` set · **EuclideanMaps or SphericalMaps only** | Throws `std::runtime_error` if Σ(2πΘᵥ) ≠ 2π·χ(M). Deleted overload for `HyperIdealMaps` — see note below. |
| `enforce_gauss_bonnet()` | `theta_v[v]` set | Σ(2πΘᵥ) = 2π·χ(M) guaranteed; `theta_v` modified | | `enforce_gauss_bonnet()` | `theta_v[v]` set · **EuclideanMaps or SphericalMaps only** | Σ(2πΘᵥ) = 2π·χ(M) guaranteed; `theta_v` modified. Deleted overload for `HyperIdealMaps` — see note below. |
| `newton_euclidean()` | GB satisfied · DOFs assigned · `lambda0` initialised | `NewtonResult.x` — converged scale factors; `.converged`, `.iterations`, `.grad_inf_norm` | | `newton_euclidean()` | GB satisfied · DOFs assigned · `lambda0` initialised | `NewtonResult.x` — converged scale factors; `.converged`, `.iterations`, `.grad_inf_norm` |
| `newton_spherical()` | GB satisfied · DOFs assigned · gauge vertex pinned | Same as above | | `newton_spherical()` | GB satisfied · DOFs assigned · gauge vertex pinned | Same as above |
| `newton_hyper_ideal()` | `assign_all_dof_indices()` called · `lambda0` initialised | Same as above | | `newton_hyper_ideal()` | `assign_all_dof_indices()` called · **no `lambda0` needed** · **no GB pre-check** | Same as above. HyperIdeal computes lengths internally from b_v, a_e (via ζ₁₃/ζ₁₄/ζ₁₅); `lambda0` is irrelevant. Energy is strictly convex → Newton converges for any targets without a pre-check. |
| `compute_cut_graph()` | Closed, orientable, triangulated mesh | `CutGraph.cut_edge_flags` — 2g seam edges; `.genus` | | `compute_cut_graph()` | Closed, orientable, triangulated mesh | `CutGraph.cut_edge_flags` — 2g seam edges; `.genus` |
| `euclidean_layout()` | `newton_euclidean()` converged | `Layout2D.uv[v]` · `.halfedge_uv[h]` · `HolonomyData.translations` | | `euclidean_layout()` | `newton_euclidean()` converged | `Layout2D.uv[v]` · `.halfedge_uv[h]` · `HolonomyData.translations` |
| `spherical_layout()` | `newton_spherical()` converged | `Layout3D.xyz[v]` | | `spherical_layout()` | `newton_spherical()` converged | `Layout3D.xyz[v]` |
@@ -53,17 +53,28 @@ The solver never writes to pinned DOFs. All indexing is 0-based and contiguous.
--- ---
## GaussBonnet — the most common source of failure ## GaussBonnet — the most common source of failure for Euclidean and Spherical
Prescribing angles that violate GaussBonnet means no conformal factor can realise the Prescribing angles that violate GaussBonnet means no conformal factor can realise the
target metric — the Newton solver will iterate indefinitely without converging. target metric — the Newton solver will iterate indefinitely without converging.
```cpp ```cpp
// Option A: verify before solving (throws on violation) // Option A: verify before solving throws std::runtime_error on violation.
check_gauss_bonnet(mesh, maps); // Works with EuclideanMaps and SphericalMaps only.
check_gauss_bonnet(mesh, euclidean_maps); // or spherical_maps
check_gauss_bonnet(mesh, spherical_maps);
// Option B: auto-correct (redistributes defect uniformly across all vertices) // Option B: auto-correct redistributes the defect uniformly across all vertices.
enforce_gauss_bonnet(mesh, maps); enforce_gauss_bonnet(mesh, euclidean_maps);
enforce_gauss_bonnet(mesh, spherical_maps);
// ⚠ HyperIdealMaps: both functions have deleted overloads — calling them is a
// compile error. Reason: the correct hyperbolic GaussBonnet identity includes
// a surface area term: Σ(2πΘᵥ) Area(M) = 2π·χ(M), not Σ(2πΘᵥ) = 2π·χ(M).
// No pre-check is needed for HyperIdeal: the energy is strictly convex
// (Springborn 2020 Theorem 1.3), so newton_hyper_ideal converges for any targets.
``` ```
The target violation is `|Σ(2πΘᵥ) 2π·χ(M)| > tol`. Default tolerance: `1e-10`. The target violation is `|Σ(2πΘᵥ) 2π·χ(M)| > tol`. Default tolerance: `1e-10`.
Applicable to closed meshes only — for open meshes, pin the boundary directly
and skip the GaussBonnet check (the identity does not hold for meshes with boundary).

View File

@@ -538,7 +538,7 @@ exposed via the CGAL public API, but a CLI user cannot reach them.
|----|------|------|----------|--------| |----|------|------|----------|--------|
| U1 | README + 4 examples | Usability | 🔴 Critical | ✅ Fixed 2026-05-31 | | U1 | README + 4 examples | Usability | 🔴 Critical | ✅ Fixed 2026-05-31 |
| U2 | examples/ (missing) | Usability | 🔴 Critical | ✅ Fixed 2026-05-31 | | U2 | examples/ (missing) | Usability | 🔴 Critical | ✅ Fixed 2026-05-31 |
| U3 | `contracts.md:16` | Doc error | 🟡 Medium | 🟡 Open | | U3 | `contracts.md:16` | Doc error | 🟡 Medium | ✅ Fixed 2026-05-31 |
| U4 | `README.md:17` | Stale | 🟡 Medium | 🟡 Open | | U4 | `README.md:17` | Stale | 🟡 Medium | 🟡 Open |
| U5 | `Discrete_conformal_map.h:14` | Stale | 🟡 Medium | 🟡 Open | | U5 | `Discrete_conformal_map.h:14` | Stale | 🟡 Medium | 🟡 Open |
| U6 | README + 2 examples | Stale | 🟡 Medium | 🟡 Open | | U6 | README + 2 examples | Stale | 🟡 Medium | 🟡 Open |