Optimal Path Parameterization

COPP solves optimal path-parameterization problems. The geometric path is fixed:

\[q = q(s),\qquad s \in [s_0, s_f].\]

The solver chooses a monotone time law s(t) so that the executed trajectory q(s(t)) satisfies the configured limits. This separation is useful in robotics because geometric planning and timing can be handled independently: the COPP stage never moves the path away from q(s).

Second-Order Variables

For velocity, acceleration, and many torque constraints, the useful path-domain variables are

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

The chain rule gives

\[\dot{q} = q_s \dot{s},\]

and

\[\ddot{q} = q_{ss}\dot{s}^2 + q_s\ddot{s} = q_{ss}a + q_s b.\]

After sampling the path, second-order limits are stored as rows of the form

\[u_i(s_k)a_k + v_i(s_k)b_k \le h_i(s_k),\]

plus first-order upper bounds on a. TOPP2/COPP2 solvers optimize over this state and return a node-based a profile.

Third-Order Variables

Third-order solvers add a control-like term

\[c(s) = \frac{\dddot{s}}{\dot{s}},\]

which is convenient when working in the path domain. Jerk-level rows use the sampled form

\[\sqrt{a_k}\,(r_i a_k + s_i b_k + t_i c_k + d_i) \le f_i.\]

Because these rows depend on sqrt(a), the Python TOPP3/COPP3 problem descriptors require an a_linearization profile. A common practical pattern is:

  1. Solve TOPP2-RA to obtain a feasible second-order seed a.

  2. Optionally tighten robot.constraints with amax_substitute.

  3. Build a TOPP3 or COPP3 problem with that seed.

  4. Rebuild the third-order problem with the previous third-order solution when running a second refinement iteration.

Problem Descriptors and Live Data

Python Problem objects are lightweight descriptors over live model data. Topp2Problem stores a reference to a Constraints proxy, while Copp2Problem stores a reference to the Robot plus Python-owned objective descriptors. They do not copy the robot or constraint buffer. If you mutate the referenced robot or constraints after constructing the problem, later validate() and solver calls use the current contents.

The descriptor configuration itself is immutable. To change an interval, boundary value, objective list, or third-order linearization profile, construct a new problem object.

For third-order problems, construction follows the Rust build_with_linearization() model. Topp3Problem and Copp3Problem copy the supplied a_linearization into the descriptor, then build a Rust problem and refresh the affine jerk-linearization rows cached in the referenced constraint buffer. Raw nonlinear jerk rows remain unchanged. Calling validate() or a third-order solver later repeats that refresh against the current robot or constraint data and the descriptor’s copied a_linearization.

When refining a third-order solve with a new profile, build a new descriptor:

problem = copp.solver.topp3_socp.Problem(
    robot.constraints,
    profile.a,
    idx_s_start=0,
    a_boundary=(0.0, 0.0),
    b_boundary=(0.0, 0.0),
)

TOPP vs COPP

TOPP means time-optimal path parameterization. The objective is traversal time:

\[J = t_f.\]

COPP means convex-objective path parameterization. The solver optimizes a weighted convex objective over the path-domain profile. In Python, objective terms are created under copp.objective:

objectives = [
    copp.objective.Time(1.0),
    copp.objective.ThermalEnergy(0.1, normalize),
]

The same path and constraints can therefore be used for time-optimal planning or for application-specific convex cost tradeoffs.

Python Data Contract

Numerical inputs accept NumPy-compatible ArrayLike values and are copied into contiguous float64 buffers at the native extension boundary. Most sampled matrices default to sample_major layout:

shape = (n_samples, dim)

The alternative dim_major layout stores dimensions first:

shape = (dim, n_samples)

Both layouts are accepted where the API exposes a layout argument. New Python code should use the default sample_major layout unless it is sharing arrays directly with an external dim-major pipeline.