copp 0.2.2

Convex-objective path parameterization for robotic trajectory planning.
Documentation
\page cpp_interpolation Interpolation And Time Profiles

Interpolation helpers convert path-domain profiles into time-domain schedules.
Solvers usually return profiles over station samples `s[k]`; controllers often
need cumulative time `t(s)` or samples of the inverse map `s(t)`.

## Include

```cpp
#include <copp/interpolation.hpp>
```

or:

```cpp
#include <copp/copp.hpp>
```

## TOPP2 Profile

TOPP2/COPP2 solvers return a node profile:

@f[
a_k = a(s_k) = \dot{s}(s_k)^2.
@f]

The segment profile is:

@f[
b_k = \frac{a_{k+1} - a_k}{2(s_{k+1} - s_k)}.
@f]

```cpp
std::vector<double> s{0.0, 0.5, 1.0};
std::vector<double> a{1.0, 1.0, 1.0};

auto b = copp::interpolation::a_to_b_topp2(s, a);
auto time = copp::interpolation::s_to_t_topp2(s, a);

auto s_uniform = copp::interpolation::t_to_s_topp2_uniform(
    s,
    a,
    time.t_s,
    0.1);

std::vector<double> t_query{0.0, 0.25, 0.5, 1.0};
auto s_query = copp::interpolation::t_to_s_topp2_samples(
    s,
    a,
    time.t_s,
    t_query);
```

Input requirements:

- `s.size() >= 2`
- `a.size() == s.size()`
- `s` is strictly increasing
- all values are finite
- every segment has a positive finite speed denominator

## TOPP3 Profile

TOPP3/COPP3 use node-based `a` and `b` arrays:

@f[
a_k = \dot{s}(s_k)^2, \qquad b_k = \ddot{s}(s_k).
@f]

`copp::Profile3rd` owns those arrays:

```cpp
std::vector<double> s{0.0, 0.5, 1.0};
copp::Profile3rd profile{
    {1.0, 1.0, 1.0},
    {0.0, 0.0, 0.0},
};

auto time = copp::interpolation::s_to_t_topp3(s, profile);
auto s_uniform = copp::interpolation::t_to_s_topp3_uniform(
    s,
    profile,
    time.t_s,
    0.1);
```

`copp::Profile3rdRef` is the zero-copy borrowed view.  It is useful for
external storage and for interpolating only part of a profile:

```cpp
auto part = profile.slice(10, 40); // inclusive station indices
auto local_s = copp::Span<const double>(s.data() + 10, 31);
auto local_time = copp::interpolation::s_to_t_topp3(local_s, part);
```

Stationary counts are clipped to the local slice.  A slice must still contain at
least one non-stationary segment after that adjustment.

## Uniform Versus Explicit Time Grids

Uniform sampling is convenient for controllers:

```cpp
copp::interpolation::TimeGridOptions grid;
grid.t0 = 0.0;
grid.include_final = true;

auto s_t = copp::interpolation::t_to_s_topp2_uniform(
    s,
    a,
    time.t_s,
    0.001,
    grid);
```

Explicit samples are better for diagnostics, plotting, or synchronizing with an
external time base:

```cpp
std::vector<double> t_sample{0.0, 0.02, 0.05, time.t_final};
auto s_t = copp::interpolation::t_to_s_topp3_samples(
    s,
    profile,
    time.t_s,
    t_sample);
```

Out-of-range query times are returned as `NaN`, matching Rust/Python.

## Tutorial Sources

- `bindings/cpp/tests/test_topp2_interpolation.cpp`
- `bindings/cpp/tests/test_topp3_interpolation.cpp`