docs: Doxygen-API + Validierungsprotokoll + Porting-Tutorial
All checks were successful
C++ Tests / test-fast (push) Successful in 2m15s
C++ Tests / test-cgal (push) Has been skipped

Für einen Mathematiker der unabhängig validieren und eigene Forschung
einbringen möchte.

Doxygen-Kommentare (code/include/):
  newton_solver.hpp — newton_euclidean(), newton_spherical(), newton_hyper_ideal()
    je mit \param, \return, \note, \see inkl. mathematischer Begründung
    (Konvexität, Vorzeichenkonvention, SparseQR-Fallback-Erklärung)
  layout.hpp — euclidean_layout(), spherical_layout(), hyper_ideal_layout()
    mit vollständiger Parameter-Doku, halfedge_uv-Semantik, Poincaré-Disk-Note

Neues Dokument:
  doc/math/validation-protocol.md
    7 reproduzierbare Checks mit konkreten Befehlen und erwartetem Output:
    0. 170 Tests, 1 Skip
    1. Gauss–Bonnet exakt (1e-10)
    2. FD-Gradientencheck < 1e-6 für alle 3 Geometrien
    3. Newton-Konvergenz < 50 Iterationen
    4. τ ∈ SL(2,ℤ)-Fundamentaldomäne (3 Invarianten)
    5. Möbius-Arithmetik (Inverse, Compose, from_three)
    6. End-to-End-Pipeline
    7. Manueller τ-Check für torus_4x4.off (Codebeispiel)

Neues Tutorial:
  doc/tutorials/add-inversive-distance.md
    Vollständiger Step-by-Step-Port von Phase 9a (Luo 2004):
    Header anlegen, Energie/Gradient implementieren, FD-Check,
    Newton-Wrapper, CMakeLists, Java-Referenzvergleich, Checkliste.

doc/getting-started.md:
  Abschnitt "Known issues": macOS-Finder-Duplikate (rm-Befehl),
  Warnung "First build 30–90s" (Tarball-Extraktion)

README.md:
  Zwei neue Links in der Dokumentationstabelle (validation-protocol,
  tutorial)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-05-18 01:19:44 +02:00
parent e28aee7051
commit b235666725
6 changed files with 571 additions and 18 deletions

View File

@@ -0,0 +1,202 @@
# Tutorial: Porting the Inversive-Distance Functional (Phase 9a)
This is a complete, step-by-step example of how to add a new functional
to conformallab++. It ports `InversiveDistanceFunctional.java` from the
Java reference implementation (Luo 2004).
**Prerequisite:** Read [doc/api/extending.md](../api/extending.md) first
for the general pattern. This tutorial fills in every detail for one
specific case.
---
## Mathematical background
Inversive distance (Luo 2004) uses a different edge-length update formula:
```
Given inversive distances I_{ij} ∈ for each edge {i,j}:
cosh(l̃_{ij}) = I_{ij} · cosh((u_i + u_j) / 2)
+ (cosh²((u_i - u_j) / 2) - 1) · ...
```
For the Euclidean version the angle formula is the same law of cosines,
but the log-lengths Λ̃ are computed differently from the u-vector.
**Reference:** Luo, F. (2004). *Combinatorial Yamabe Flow on Surfaces*.
Communications in Contemporary Mathematics, 6(5), 765780.
**Java source:** `de.varylab.discreteconformal.functional.InversiveDistanceFunctional`
---
## Step 1 — Create the header
```bash
cp code/include/euclidean_functional.hpp \
code/include/inversive_distance_functional.hpp
```
Edit the new file:
```cpp
#pragma once
// inversive_distance_functional.hpp
//
// Phase 9a — Inversive-distance discrete conformal functional (Luo 2004).
// Ported from de.varylab.discreteconformal.functional.InversiveDistanceFunctional.
//
// Usage: identical to euclidean_functional.hpp — replace lambda0 with
// inversive_distance0 (the initial inversive distances per edge).
#include "conformal_mesh.hpp"
#include <Eigen/Dense>
#include <cmath>
namespace conformallab {
struct InversiveDistanceMaps {
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Edge_index, double> inv_dist0; ///< I_{ij} per edge
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Vertex_index, double> theta_v; ///< target corner angles Θ_v
CGAL::Surface_mesh<CGAL::Simple_cartesian<double>::Point_3>
::Property_map<Vertex_index, int> v_idx; ///< DOF index (-1 = pinned)
};
// ... (follow euclidean_functional.hpp exactly, replacing lambda0 with inv_dist0
// and the angle formula with the Luo (2004) version)
```
---
## Step 2 — Implement the energy, gradient, and angle formula
The Euclidean angle formula uses the law of cosines on edge lengths
derived from log-lengths. For inversive distance, the edge lengths
are derived from inversive distances I_{ij} and the u-vector:
```cpp
// Euclidean (reference):
// lambda_ij = lambda0_ij + u_i + u_j
// l_ij = exp(lambda_ij / 2)
// Inversive distance (Luo 2004):
// cosh(l̃_ij) = I_ij * cosh((u_i + u_j) / 2) [simplified form]
// l̃_ij = acosh(I_ij * cosh((u_i + u_j) / 2))
```
Then the **corner angle** at vertex k opposite edge {i,j} in triangle {i,j,k}
is computed by the standard law of cosines from l̃_{ij}, l̃_{jk}, l̃_{ki}.
The **gradient** is the same as Euclidean:
```cpp
G_v = Σ_{faces containing v} alpha_v(face, u) Theta_v
```
---
## Step 3 — Write the gradient-check test
Create `code/tests/cgal/test_inversive_distance.cpp`:
```cpp
#include "inversive_distance_functional.hpp"
#include "mesh_factory.hpp"
#include <gtest/gtest.h>
// Finite-difference gradient check: copy the pattern from
// test_euclidean_functional.cpp :: EuclideanFunctional.GradientCheck_Triangle
TEST(InversiveDistance, GradientCheck_Triangle)
{
// Build a single equilateral triangle (open mesh, no boundary issues).
ConformalMesh mesh = MeshFactory::make_open_mesh(MeshFactory::Kind::triangle);
InversiveDistanceMaps maps = setup_inversive_distance_maps(mesh);
compute_inversive_distance0_from_mesh(mesh, maps); // I_ij from 3D edge lengths
const int n = /* count free DOFs */;
std::vector<double> x0(n, 0.0);
constexpr double eps = 1e-5;
auto G = inversive_distance_gradient(mesh, x0, maps);
for (int i = 0; i < n; ++i) {
auto xp = x0; xp[i] += eps;
auto xm = x0; xm[i] -= eps;
double Ep = inversive_distance_energy(mesh, xp, maps);
double Em = inversive_distance_energy(mesh, xm, maps);
double fd = (Ep - Em) / (2.0 * eps);
EXPECT_NEAR(G[i], fd, 1e-6) << "gradient mismatch at DOF " << i;
}
}
```
Run with:
```bash
./build/conformallab_cgal_tests --gtest_filter="InversiveDistance.*"
```
---
## Step 4 — Register in CMakeLists
Add to `code/tests/cgal/CMakeLists.txt` inside the `add_executable` block:
```cmake
# ── Phase 9a: Inversive-distance functional ────────────────────────────────
test_inversive_distance.cpp
```
---
## Step 5 — Add a Newton wrapper
In `code/include/newton_solver.hpp` add:
```cpp
/// Solve the inversive-distance conformal problem.
/// \see newton_euclidean() — identical structure.
inline NewtonResult newton_inversive_distance(
ConformalMesh& mesh,
std::vector<double> x0,
const InversiveDistanceMaps& m,
double tol = 1e-8,
int max_iter = 200)
{
// Copy newton_euclidean() exactly, replacing euclidean_* with
// inversive_distance_*. The Hessian is PSD (Luo 2004, Thm. 1),
// so SimplicialLDLT with SparseQR fallback applies directly.
}
```
---
## Step 6 — Verify against Java output
The Java library outputs text results for a triangulated torus. To compare:
1. Run Java ConformalLab on the same OFF mesh with Inversive-Distance mode.
2. Record the final gradient norm and angle sums.
3. Run the C++ equivalent and check:
```cpp
EXPECT_LT(res.grad_inf_norm, 1e-8);
EXPECT_LT(res.iterations, 50); // inversive-distance converges faster than hyper-ideal
```
**Java reference:** `InversiveDistanceFunctionalTest.java` in `de.varylab.discreteconformal.test`
---
## Checklist
- [ ] `code/include/inversive_distance_functional.hpp` compiles
- [ ] `GradientCheck_Triangle` passes
- [ ] `GradientCheck_OpenMesh` passes (copy from Euclidean tests)
- [ ] Newton converges on `torus_4x4.off`
- [ ] Result matches Java output (gradient norm < 1e-8 on same mesh)
- [ ] Registered in `CMakeLists.txt`
- [ ] `doc/roadmap/java-parity.md` updated: Phase 9a