// example_hyper_ideal.cpp // // conformallab++ — Hyper-ideal discrete conformal map (headless example) // // Demonstrates the full library pipeline for the HYPER-IDEAL discrete conformal // functional (Springborn 2020). The hyper-ideal functional operates in // hyperbolic geometry: vertices have "horoball radii" (DOF b_i) and edges have // "intersection lengths" (DOF a_e). The energy is strictly convex, so Newton // converges globally from any valid starting point. // // Pipeline: // 1. Load (or synthesise) a triangle mesh // 2. Set up HyperIdeal maps + assign all vertex and edge DOFs // 3. Choose equilibrium base point (b=1.0, a=0.5) and set natural targets // 4. Perturb and solve with Newton // 5. Print DOF values at equilibrium // 6. Save result mesh // // Build (requires -DWITH_CGAL=ON): // cmake -S code -B build -DWITH_CGAL=ON // cmake --build build --target example_hyper_ideal // ./build/examples/example_hyper_ideal [input.off] [output.off] #include "conformal_mesh.hpp" #include "mesh_builder.hpp" #include "mesh_io.hpp" #include "hyper_ideal_functional.hpp" #include "newton_solver.hpp" #include #include #include #include using namespace conformallab; int main(int argc, char* argv[]) { // ── Step 1: obtain mesh ─────────────────────────────────────────────── ConformalMesh mesh; std::string input_path = (argc > 1) ? argv[1] : ""; std::string output_path = (argc > 2) ? argv[2] : "/tmp/conformallab_hyper_ideal_out.off"; if (input_path.empty()) { std::cout << "[example_hyper_ideal] No input file — using make_triangle().\n"; mesh = make_triangle(); } else { std::cout << "[example_hyper_ideal] Loading mesh from: " << input_path << "\n"; try { mesh = load_mesh(input_path); } catch (const std::exception& e) { std::cerr << "Error loading mesh: " << e.what() << "\n"; return 1; } } std::cout << "[example_hyper_ideal] Mesh: " << mesh.number_of_vertices() << " vertices, " << mesh.number_of_faces() << " faces.\n"; // ── Step 2: set up functional maps ──────────────────────────────────── auto maps = setup_hyper_ideal_maps(mesh); int n = assign_hyper_ideal_all_dof_indices(mesh, maps); std::cout << "[example_hyper_ideal] DOFs: " << n << " (" << mesh.number_of_vertices() << " vertex + " << mesh.number_of_edges() << " edge).\n"; // ── Step 3: choose equilibrium base point and set natural targets ───── // // x = 0 is degenerate for the HyperIdeal functional (log-space). // We pick a valid base point (b_i = b_base, a_e = a_base), evaluate // the gradient there, and absorb it into the target angles so that // G(xbase) = 0. This makes xbase the equilibrium x*. // // In a real application you would set theta_v / theta_e to the desired // hyperbolic angle targets (e.g. from a reference mesh). const double b_base = 1.0; // horoball radii at equilibrium const double a_base = 0.5; // edge-length DOFs at equilibrium const auto sz = static_cast(n); std::vector xbase(sz, 0.0); for (auto v : mesh.vertices()) { int iv = maps.v_idx[v]; if (iv >= 0) xbase[static_cast(iv)] = b_base; } for (auto e : mesh.edges()) { int ie = maps.e_idx[e]; if (ie >= 0) xbase[static_cast(ie)] = a_base; } // G = Σβ − theta_target; absorb G(xbase) into targets so G(xbase) = 0 auto G0 = evaluate_hyper_ideal(mesh, xbase, maps, false).gradient; for (auto v : mesh.vertices()) { int iv = maps.v_idx[v]; if (iv >= 0) maps.theta_v[v] += G0[static_cast(iv)]; } for (auto e : mesh.edges()) { int ie = maps.e_idx[e]; if (ie >= 0) maps.theta_e[e] += G0[static_cast(ie)]; } // ── Step 4: perturb and solve ───────────────────────────────────────── const double perturb = 0.25; std::vector x0 = xbase; for (auto& v : x0) v += perturb; double g_start = 0.0; for (double v : G0) g_start = std::max(g_start, std::abs(v)); std::cout << "[example_hyper_ideal] Starting Newton from perturbation +" << perturb << " (G at xbase = " << g_start << ").\n"; auto result = newton_hyper_ideal(mesh, x0, maps, /*tol=*/1e-9, /*max_iter=*/200); // ── Step 5: report ──────────────────────────────────────────────────── if (result.converged) { std::cout << "[example_hyper_ideal] Converged in " << result.iterations << " iterations. ||G||_inf = " << result.grad_inf_norm << "\n"; } else { std::cout << "[example_hyper_ideal] Did NOT converge after " << result.iterations << " iterations. ||G||_inf = " << result.grad_inf_norm << "\n"; } std::cout << "[example_hyper_ideal] DOF values at equilibrium:\n"; for (auto v : mesh.vertices()) { int iv = maps.v_idx[v]; if (iv < 0) continue; std::cout << " v" << v << " b = " << result.x[static_cast(iv)] << " (expected " << b_base << ")\n"; } for (auto e : mesh.edges()) { int ie = maps.e_idx[e]; if (ie < 0) continue; std::cout << " e" << e << " a = " << result.x[static_cast(ie)] << " (expected " << a_base << ")\n"; } // ── Step 6: write output mesh ───────────────────────────────────────── try { save_mesh(output_path, mesh); std::cout << "[example_hyper_ideal] Mesh saved to: " << output_path << "\n"; } catch (const std::exception& e) { std::cerr << "Warning: could not write output: " << e.what() << "\n"; } return result.converged ? 0 : 1; }