Files
ConformalLabpp/doc/tutorials/add-inversive-distance.md
Tarik Moussa b235666725
All checks were successful
C++ Tests / test-fast (push) Successful in 2m15s
C++ Tests / test-cgal (push) Has been skipped
docs: Doxygen-API + Validierungsprotokoll + Porting-Tutorial
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>
2026-05-18 01:19:44 +02:00

203 lines
6.2 KiB
Markdown
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.

# 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