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>
4.6 KiB
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):
-
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. -
BFS structure — reuse the priority-BFS loop from
euclidean_layout()inlayout.hpp. The loop itself is geometry-agnostic; swap in your trilateration function. -
Holonomy — if the space has a holonomy group, record the transition maps at seam edges the same way
HolonomyDatadoes for translations (Euclidean) or Möbius maps (hyperbolic).
Adding a new processing unit
-
Declare preconditions and outputs explicitly (see contracts.md).
-
Write a header-only implementation in
code/include/my_unit.hpp. -
Add tests in
code/tests/cgal/test_my_unit.cppand register incode/tests/cgal/CMakeLists.txt. -
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:
-
Locate the original at github.com/varylab/conformallab under
src/main/java/de/varylab/discreteconformal/. -
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] -
Java uses
HalfedgeInterfaceadapters with named accessors likegetOppositeVertex(). 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))); -
Port the test cases from the Java
@Testmethods. The "natural theta" trick (theta_v[v] = actual_angle_sum) works the same way in both languages.