Quickstart

The shortest reliable Python workflow is:

  1. Build a geometric path q(s).

  2. Sample the path on a station grid s and store path derivatives in a Robot or Constraints buffer.

  3. Add physical limits.

  4. Build a TOPP or COPP problem descriptor.

  5. Run a solver and convert the path-domain profile into time-domain samples.

The second-order state used by TOPP2/COPP2 is

\[a(s) = \dot{s}^2,\qquad b(s) = \ddot{s}.\]

TOPP2/COPP2 solvers return the node profile a. The helper s_to_t_topp2 integrates it into arrival times, and t_to_s_topp2_uniform or t_to_s_topp2_samples inverts the mapping for controller or plotting samples.

Minimal TOPP2-RA Example

The following example constructs an analytic path, applies symmetric velocity and acceleration limits, solves TOPP2-RA, and samples s(t) on a uniform time grid.

 1"""Use TOPP2-RA to convert a JAX-defined path into a second-order time-optimal trajectory.
 2
 3The example constrains each axis by velocity and acceleration limits in
 4``[-1, 1]``, then converts the returned ``a(s)`` profile into ``t(s)`` and
 5uniform ``s(t)`` samples.
 6"""
 7
 8import numpy as np
 9
10import copp_py as copp
11
12
13def main() -> None:
14    try:
15        import jax
16        import jax.numpy as jnp
17    except ImportError as exc:
18        raise SystemExit(
19            'Install JAX to run this example: python -m pip install "copp-py[jax]"'
20        ) from exc
21
22    jax.config.update("jax_enable_x64", True)
23
24    dim = 3
25    n = 1001
26    dt = 1.0e-3
27
28    # 1) Define q(s). Path.from_jax differentiates it up to third order.
29    def q_fn(s):
30        freq = jnp.array([2.0 * jnp.pi, 3.0 * jnp.pi, 5.0 * jnp.pi], dtype=jnp.float64)
31        phase = jnp.array([0.0, 0.3, 0.7], dtype=jnp.float64)
32        return jnp.sin(freq * s + phase)
33
34    path = copp.Path.from_jax(q_fn, 0.0, 1.0)
35    s = np.linspace(0.0, 1.0, n, dtype=np.float64)
36
37    # 2) Build robot constraints (3-axis), then apply symmetric limits
38    # velocity/acceleration = [-1, 1].
39    robot = copp.Robot(dim, capacity=n)
40    robot.append_s(s)
41    robot.set_q_from_path_2nd(path, 0, n)
42
43    upper = np.ones(dim, dtype=np.float64)
44    lower = -upper
45    robot.add_velocity_limits(upper, lower, start_idx_s=0, length=n)
46    robot.add_acceleration_limits(upper, lower, start_idx_s=0, length=n)
47
48    # 3) Solve TOPP2-RA with boundary values a(0) = 0 and a(1) = 0.
49    problem = copp.solver.topp2_ra.Problem(
50        robot.constraints,
51        idx_s_interval=(0, n - 1),
52        a_boundary=(0.0, 0.0),
53    )
54    options = copp.solver.topp2_ra.Options()
55    a_ra = copp.solver.topp2_ra.solve(problem, options)
56
57    # 4) Post-process TOPP2-RA results: a(s) -> t(s) -> s(t).
58    t_final, t_s = copp.interpolation.s_to_t_topp2(s, a_ra, 0.0)
59    s_t = copp.interpolation.t_to_s_topp2_uniform(
60        s,
61        a_ra,
62        t_s,
63        dt,
64        t0=0.0,
65        include_final=True,
66    )
67
68    # 5) Print the tutorial summary.
69    print("TOPP2-RA done.")
70    print(f"dim = {dim}, N = {n}")
71    print(f"t_final = {t_final:.6f} s")
72    print(f"a_profile.len() = {len(a_ra)}")
73    print(f"s(t) samples = {len(s_t)}")
74
75
76if __name__ == "__main__":
77    main()

Import Style

The package follows the Rust crate layout. Solver entry points live under copp.solver with one module per algorithm:

import copp_py as copp

problem = copp.solver.topp2_ra.Problem(...)
options = copp.solver.topp2_ra.Options()
a_profile = copp.solver.topp2_ra.solve(problem, options)

Modeling helpers live in focused namespaces such as copp.path, copp.robot, copp.constraints, copp.objective, copp.interpolation, and copp.clarabel. A few common modeling types are also re-exported at package root for interactive use, but solver-specific classes and functions are intentionally kept in copp.solver.

Next Steps

Read Optimal Path Parameterization for the variables and constraint model, then choose a solver with Solver Selection. The tutorial pages embed the same runnable files stored in bindings/python/examples.