# 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 ```cpp // my_functional.hpp struct MyMaps { ConformalMesh::Property_map lambda0; // initial log-lengths ConformalMesh::Property_map theta_v; // target angles ConformalMesh::Property_map v_idx; // DOF index, -1 = pinned // add edge DOFs if needed: ConformalMesh::Property_map 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]`. ```cpp std::vector my_gradient( const ConformalMesh& mesh, const std::vector& x, const MyMaps& maps) { std::vector 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`: ```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 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 ```cpp // Reuse solve_linear_system from newton_solver.hpp Eigen::SparseMatrix 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](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](cgal-package.md). --- ## Porting from Java When porting a class from the Java library: 1. Locate the original at [github.com/varylab/conformallab](https://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: ```cpp // 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.