Files
ConformalLabpp/doc/api/extending.md
Tarik Moussa 14134b99ce
Some checks failed
C++ Tests / test-fast (push) Successful in 2m25s
C++ Tests / test-cgal (push) Failing after 1m58s
docs: restructure documentation into focused files
README.md: reduced from 703 to ~75 lines — what/why, status, quick
start, minimal usage example, navigation table to doc/ files.

doc/architecture/overall_pipeline.md: trimmed — roadmap, extension
points, declarative pipeline YAML, and references sections removed
(each now has its own dedicated file). Replaced with a link table.

New files:
  doc/getting-started.md       — build modes, single-test invocation, CLI
  doc/api/pipeline.md          — full pipeline API with code for all 3 geometries
  doc/api/extending.md         — new functionals, geometry modes, Java porting guide
  doc/api/contracts.md         — processing unit preconditions/provides table
  doc/api/cgal-package.md      — Phase 8 CGAL package design + YAML pipeline (TODO)
  doc/math/geometry-modes.md   — Euclidean/Spherical/HyperIdeal comparison
  doc/math/references.md       — all papers by module
  doc/roadmap/phases.md        — Phases 1–10 with porting/research boundary
  doc/roadmap/java-parity.md   — Java vs C++ feature parity table
  doc/contributing.md          — language policy, test standards, release flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 21:17:15 +02:00

4.6 KiB
Raw Blame History

Extending conformallab++

Adding a new functional

All three existing functionals (euclidean_functional.hpp, spherical_functional.hpp, hyper_ideal_functional.hpp) follow the same pattern. Copy the structure of the simplest one (euclidean_functional.hpp) as a template.

Step 1 — Maps struct

// my_functional.hpp
struct MyMaps {
    ConformalMesh::Property_map<Vertex_index, double> lambda0;  // initial log-lengths
    ConformalMesh::Property_map<Vertex_index, double> theta_v;  // target angles
    ConformalMesh::Property_map<Vertex_index, int>    v_idx;    // DOF index, -1 = pinned
    // add edge DOFs if needed:
    ConformalMesh::Property_map<Edge_index,   int>    e_idx;
};

MyMaps setup_my_maps(ConformalMesh& mesh);
void compute_my_lambda0_from_mesh(ConformalMesh& mesh, MyMaps& maps);

Step 2 — Gradient

The gradient must satisfy: G_v = Σ(angle contributions at v) theta_v[v].

std::vector<double> my_gradient(
    const ConformalMesh& mesh,
    const std::vector<double>& x,
    const MyMaps& maps)
{
    std::vector<double> G(n_dofs, 0.0);
    for (auto f : mesh.faces()) {
        // compute angles in face f given current x
        // accumulate into G[maps.v_idx[v]] for each vertex v of f
    }
    // subtract target angles
    for (auto v : mesh.vertices())
        if (maps.v_idx[v] >= 0)
            G[maps.v_idx[v]] -= maps.theta_v[v];
    return G;
}

Step 3 — Gradient check test

Before claiming correctness, verify with finite differences. Copy any GradientCheck_* test suite from tests/cgal/test_*_functional.cpp:

TEST(MyFunctional, GradientCheck_Triangle) {
    ConformalMesh mesh = make_single_triangle();
    MyMaps maps = setup_my_maps(mesh);
    // ... assign DOFs, set natural theta ...

    const double eps = 1e-5;
    auto G = my_gradient(mesh, x0, maps);
    for (int i = 0; i < n; ++i) {
        std::vector<double> xp = x0, xm = x0;
        xp[i] += eps;  xm[i] -= eps;
        double fd = (my_energy(mesh, xp, maps) - my_energy(mesh, xm, maps)) / (2*eps);
        EXPECT_NEAR(G[i], fd, 1e-7) << "DOF " << i;
    }
}

Step 4 — Hessian and Newton wrapper

// Reuse solve_linear_system from newton_solver.hpp
Eigen::SparseMatrix<double> H = my_hessian(mesh, x, maps);
bool used_fallback;
auto dx = conformallab::solve_linear_system(H, -G, &used_fallback);

Or write a thin newton_my() wrapper following the structure of newton_euclidean().


Adding a new geometry mode

To add a new space (e.g. de Sitter, flat 3-torus):

  1. Trilateration function — implement trilaterate_my(p1, p2, l12, l13, l23) that places a third point given two placed points and three edge lengths. This is the only geometry-specific part of the layout.

  2. BFS structure — reuse the priority-BFS loop from euclidean_layout() in layout.hpp. The loop itself is geometry-agnostic; swap in your trilateration function.

  3. Holonomy — if the space has a holonomy group, record the transition maps at seam edges the same way HolonomyData does for translations (Euclidean) or Möbius maps (hyperbolic).


Adding a new processing unit

  1. Declare preconditions and outputs explicitly (see contracts.md).

  2. Write a header-only implementation in code/include/my_unit.hpp.

  3. Add tests in code/tests/cgal/test_my_unit.cpp and register in code/tests/cgal/CMakeLists.txt.

  4. Pipeline validation is currently manual (documented contracts). A compile-time or runtime check via the declarative YAML pipeline (Phase 8e) is the planned upgrade — see cgal-package.md.


Porting from Java

When porting a class from the Java library:

  1. Locate the original at github.com/varylab/conformallab under src/main/java/de/varylab/discreteconformal/.

  2. The Java library uses CoHDS (half-edge data structure) with intrusive vertex/edge data. Map these to CGAL property maps:

    Java: vertex.getLambda()     → C++: maps.lambda0[v]
    Java: edge.getAlpha()        → C++: maps.e_alpha[e]
    Java: vertex.getTheta()      → C++: maps.theta_v[v]
    Java: vertex.getSolverIndex() → C++: maps.v_idx[v]
    
  3. Java uses HalfedgeInterface adapters with named accessors like getOppositeVertex(). C++ equivalent:

    // Java: h.getOppositeVertex()
    Vertex_index v_opp = mesh.target(mesh.next(h));
    
    // Java: h.getNextHalfedge().getOppositeVertex()
    Vertex_index v_opp2 = mesh.target(mesh.next(mesh.next(h)));
    
  4. Port the test cases from the Java @Test methods. The "natural theta" trick (theta_v[v] = actual_angle_sum) works the same way in both languages.