# 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<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]`.

```cpp
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`:

```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

```cpp
// 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](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.
