feat(phase4): HyperIdeal Newton solver, SparseQR fallback, examples, docs
Phase 4 complete — 87 CGAL tests pass, 2 skipped. Newton solver (phase4a): - hyper_ideal_hessian.hpp: symmetric FD Hessian (O(ε²), PSD by convexity) - newton_hyper_ideal(): Newton + backtracking for the HyperIdeal functional - detail::solve_with_fallback(): optional bool* fallback_used parameter - solve_linear_system(): public API exposing LDLT→SparseQR fallback SparseQR fallback tests (SparseQRFallback.*): - FullRankSystem_CorrectSolution: LDLT path, fallback_used=false - SingularMatrix_FallbackActivated: zero-pivot → QR activated, fallback_used=true - Euclidean_ClosedMeshNoPinConverges: gauge-mode null space handled via QR HyperIdeal Newton tests (NewtonSolver.HyperIdeal_*): - ConvergesTriangleAllVariable, ResultFieldsConsistent, ConvergesTetrahedron, SparseQRFallbackNoCrash - Natural-target base point (b=1.0, a=0.5) — x=0 is degenerate in log-space Pipeline tests (test_pipeline.cpp): - End-to-end: all three geometries, mesh I/O round-trip, solve+export Example programs (code/examples/): - example_euclidean.cpp: headless Euclidean pipeline - example_hyper_ideal.cpp: headless HyperIdeal pipeline - example_viewer.cpp: interactive libigl viewer with jet colour map README: - Mathematical scope table: C++ vs Java original (18 rows) - "For mathematicians" section: mental model, step-by-step new-functional guide, half-edge traversal snippets, recommended reading Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
117
code/examples/example_euclidean.cpp
Normal file
117
code/examples/example_euclidean.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
// example_euclidean.cpp
|
||||
//
|
||||
// conformallab++ — Euclidean discrete conformal map (headless example)
|
||||
//
|
||||
// This program demonstrates the full library pipeline for the EUCLIDEAN
|
||||
// discrete conformal functional:
|
||||
//
|
||||
// 1. Load a triangle mesh from an OFF file
|
||||
// 2. Set up the Euclidean functional maps
|
||||
// 3. Pin one vertex (gauge fix for open surfaces)
|
||||
// 4. Set target angles via "natural equilibrium" (x* = x_input)
|
||||
// 5. Solve with Newton + backtracking line search
|
||||
// 6. Print per-vertex conformal factors u_i = x[v_idx[v]]
|
||||
// 7. Save the result mesh (same geometry, solver state printed)
|
||||
//
|
||||
// Build (requires -DWITH_CGAL=ON):
|
||||
// cmake -S code -B build -DWITH_CGAL=ON
|
||||
// cmake --build build --target example_euclidean
|
||||
// ./build/examples/example_euclidean [input.off] [output.off]
|
||||
//
|
||||
// If no input file is given the built-in make_quad_strip() mesh is used.
|
||||
|
||||
#include "conformal_mesh.hpp"
|
||||
#include "mesh_builder.hpp"
|
||||
#include "mesh_io.hpp"
|
||||
#include "euclidean_functional.hpp"
|
||||
#include "newton_solver.hpp"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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_euclidean_out.off";
|
||||
|
||||
if (input_path.empty()) {
|
||||
std::cout << "[example_euclidean] No input file given — using make_quad_strip().\n";
|
||||
mesh = make_quad_strip();
|
||||
} else {
|
||||
std::cout << "[example_euclidean] 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_euclidean] Mesh: "
|
||||
<< mesh.number_of_vertices() << " vertices, "
|
||||
<< mesh.number_of_faces() << " faces.\n";
|
||||
|
||||
// ── Step 2: set up functional maps ────────────────────────────────────
|
||||
auto maps = setup_euclidean_maps(mesh);
|
||||
compute_euclidean_lambda0_from_mesh(mesh, maps);
|
||||
|
||||
// ── Step 3: pin the first vertex (gauge fix) ──────────────────────────
|
||||
auto vit = mesh.vertices().begin();
|
||||
Vertex_index v_pinned = *vit++;
|
||||
maps.v_idx[v_pinned] = -1; // pinned: u[v_pinned] = 0 (fixed)
|
||||
int idx = 0;
|
||||
for (; vit != mesh.vertices().end(); ++vit)
|
||||
maps.v_idx[*vit] = idx++;
|
||||
const int n = idx;
|
||||
|
||||
std::cout << "[example_euclidean] DOFs: " << n << " (1 vertex pinned).\n";
|
||||
|
||||
// ── Step 4: natural equilibrium — set theta_v = actual angle sum at x=0 ─
|
||||
// After this step x* = 0 is the equilibrium (no deformation).
|
||||
// In a real application you would set theta_v = desired angle (e.g. 2π
|
||||
// for flat disks, or the cone angles for a cone metric).
|
||||
{
|
||||
std::vector<double> x0(static_cast<std::size_t>(n), 0.0);
|
||||
auto G0 = euclidean_gradient(mesh, x0, maps);
|
||||
for (auto v : mesh.vertices()) {
|
||||
int iv = maps.v_idx[v];
|
||||
if (iv >= 0) maps.theta_v[v] -= G0[static_cast<std::size_t>(iv)];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step 5: solve from a small perturbation to demonstrate Newton ─────
|
||||
std::vector<double> x0(static_cast<std::size_t>(n), -0.05);
|
||||
std::cout << "[example_euclidean] Solving Newton system…\n";
|
||||
auto result = newton_euclidean(mesh, x0, maps, /*tol=*/1e-9, /*max_iter=*/100);
|
||||
|
||||
// ── Step 6: report ────────────────────────────────────────────────────
|
||||
if (result.converged) {
|
||||
std::cout << "[example_euclidean] Converged in " << result.iterations
|
||||
<< " iterations. ||G||_inf = " << result.grad_inf_norm << "\n";
|
||||
} else {
|
||||
std::cout << "[example_euclidean] Did NOT converge after " << result.iterations
|
||||
<< " iterations. ||G||_inf = " << result.grad_inf_norm << "\n";
|
||||
}
|
||||
|
||||
std::cout << "[example_euclidean] Per-vertex conformal factors u_i:\n";
|
||||
for (auto v : mesh.vertices()) {
|
||||
int iv = maps.v_idx[v];
|
||||
double u = (iv >= 0) ? result.x[static_cast<std::size_t>(iv)] : 0.0;
|
||||
std::cout << " v" << v << " u = " << u;
|
||||
if (iv < 0) std::cout << " (pinned)";
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// ── Step 7: write output mesh ─────────────────────────────────────────
|
||||
try {
|
||||
save_mesh(output_path, mesh);
|
||||
std::cout << "[example_euclidean] 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;
|
||||
}
|
||||
Reference in New Issue
Block a user