# 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), 765–780. **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 #include namespace conformallab { struct InversiveDistanceMaps { CGAL::Surface_mesh::Point_3> ::Property_map inv_dist0; ///< I_{ij} per edge CGAL::Surface_mesh::Point_3> ::Property_map theta_v; ///< target corner angles Θ_v CGAL::Surface_mesh::Point_3> ::Property_map 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 // 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 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 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 → ✅