Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Continuous-Time Transcription and Collocation

The preceding chapters formulated and optimized trajectories through discrete transition maps. Physical models are often given instead by ordinary differential equations, so their states and controls are functions of time. A finite-dimensional optimizer cannot choose an entire function directly. Continuous-time trajectory optimization therefore begins by replacing those functions and their differential equations with finitely many variables and constraints on those variables.

How can that replacement preserve enough of the differential equation to make the resulting trajectory meaningful between the stored time points?

This replacement is called transcription, and the resulting finite optimization problem is a nonlinear program (NLP).

The direct-collocation formulation used here stores state and control values at selected times and interpolates between them with low-degree polynomials. The differential equation is enforced at selected points on each time interval. This chapter develops that construction from one simple example through Euler, trapezoidal, and Hermite--Simpson transcriptions, then applies it to the motion of an overhead crane.

A One-Interval Example

How can a constraint on four endpoint values ensure that the chosen control produces the required displacement?

Consider a point that must move from x(0)=0x(0)=0 to x(1)=1x(1)=1. Its velocity is the control, so

x˙(t)=u(t),\dot x(t)=u(t),

and the objective penalizes squared control effort,

minimize⁡x(⋅),u(⋅)∫01u(t)2 dt.\underset{x(\cdot),u(\cdot)}{\operatorname{minimize}} \quad \int_0^1 u(t)^2\,dt.

Both xx and uu are unknown functions. As a first finite approximation, retain only their endpoint values x0,x1,u0,u1x_0,x_1,u_0,u_1 and let the control vary linearly between u0u_0 and u1u_1. Integrating x˙=u\dot x=u means that the change in state equals the area under this control. The shaded region below is a unit-width trapezoid. Its area is the average of its two endpoint heights, u0u_0 and u1u_1, multiplied by the width.

Figure 1:Drag the endpoint controls or play the accumulation from left to right. The shaded rectangle and triangle sum to the state change implied by x˙=u\dot x=u. Setting the interval width to h=1h=1 gives the state-change relation x1−x0=(u0+u1)/2x_1-x_0=(u_0+u_1)/2.

Loading...
A linear control between two endpoint values forms a trapezoid whose rectangle and triangle areas sum to the state change.

Figure 2:A linear control connects u0u_0 and u1u_1 across an interval of width hh. The rectangle hu0hu_0 and triangle h(u1−u0)/2h(u_1-u_0)/2 sum to h(u0+u1)/2h(u_0+u_1)/2. Because x˙=u\dot x=u, this area equals x1−x0x_1-x_0.

The optimizer must choose endpoint states and controls that agree with this area calculation. Otherwise, it could choose x0=0x_0=0 and x1=1x_1=1 while setting both controls to zero: the endpoint conditions would hold, but zero velocity could not produce the displacement. To exclude such choices, the stored state change x1−x0x_1-x_0 must equal the change (u0+u1)/2(u_0+u_1)/2 predicted by integrating the control. Moving both quantities to the left gives the constraint

x1−x0−12(u0+u1)=0.x_1-x_0-\frac{1}{2}(u_0+u_1)=0.

The left-hand side measures the mismatch between these two changes and is called the defect. Requiring that mismatch to vanish gives a defect constraint, which enforces the dynamics within the chosen approximation. The equation is algebraic because it relates the four numbers x0,x1,u0,u1x_0,x_1,u_0,u_1 through arithmetic operations; there is no unknown function to differentiate or integrate when evaluating it. An optimizer can therefore check the constraint directly for each candidate set of endpoint values.

Approximating the running-cost integral by the average of its endpoint values gives the finite nonlinear program

minimize⁡x0,x1,u0,u112(u02+u12)subject tox0=0,x1=1,x1−x0−12(u0+u1)=0.\begin{aligned} \underset{x_0,x_1,u_0,u_1}{\operatorname{minimize}} \quad&\frac{1}{2}(u_0^2+u_1^2)\\ \text{subject to}\quad&x_0=0,\qquad x_1=1,\\ &x_1-x_0-\frac{1}{2}(u_0+u_1)=0. \end{aligned}

The defect integrates the linear control exactly. The objective uses another approximation: u(t)2u(t)^2 is generally quadratic, so averaging its endpoint values need not give its exact integral. Both calculations become exact for a constant control.

The boundary conditions reduce the defect to u0+u1=2u_0+u_1=2. Substituting u1=2−u0u_1=2-u_0 into the objective gives

12(u02+(2−u0)2)=(u0−1)2+1.\frac12\left(u_0^2+(2-u_0)^2\right) =(u_0-1)^2+1.

The squared term is minimized at u0=1u_0=1, which also gives u1=1u_1=1. The resulting interpolation is the exact solution u(t)=1u(t)=1 and x(t)=tx(t)=t.

This example already contains the main ingredients of direct collocation. The function values became optimization variables, integration became a weighted sum, and the differential equation became an equality constraint. The remaining sections construct these operations systematically for nonlinear vector dynamics and higher-degree polynomials.

From a Continuous Problem to a Finite NLP

Which quantities and constraints must a transcription retain from the continuous-time control problem?

We retain the earlier notation x∈Rn\mathbf x\in\mathbb R^n for the state, u∈Rm\mathbf u\in\mathbb R^m for the control, and cc for cost. Here tt is continuous physical time, while kk will index mesh intervals. The running cost c(x,u,t)c(\mathbf x,\mathbf u,t) is a cost per unit time; integrating it over an interval produces the counterpart of the discrete stage cost ckc_k. The terminal cost is written cfc_f because the final physical time is tft_f. Scalar examples omit boldface.

With inequalities g≤0\mathbf g\leq\mathbf0 and equalities h=0\mathbf h=\mathbf0, the continuous-time Bolza problem is

minimize⁡x(⋅),u(⋅),tfcf(x(tf),tf)+∫t0tfc(x(t),u(t),t) dtsubject tox˙(t)=f(x(t),u(t),t),h(x(t0),x(tf),tf)=0,g(x(t),u(t),t)≤0.\begin{aligned} \underset{\mathbf{x}(\cdot),\mathbf{u}(\cdot),t_f}{\operatorname{minimize}} \quad& c_f(\mathbf{x}(t_f),t_f) +\int_{t_0}^{t_f} c(\mathbf{x}(t),\mathbf{u}(t),t)\,dt\\ \text{subject to}\quad& \dot{\mathbf{x}}(t)=\mathbf{f}(\mathbf{x}(t),\mathbf{u}(t),t),\\ &\mathbf{h}(\mathbf{x}(t_0),\mathbf{x}(t_f),t_f)=\mathbf0,\\ &\mathbf{g}(\mathbf{x}(t),\mathbf{u}(t),t)\leq\mathbf0. \end{aligned}

The equality h=0\mathbf h=\mathbf0 imposes endpoint conditions, while g≤0\mathbf g\leq\mathbf0 imposes constraints along the path, such as actuator limits or a bound on the state. A fixed final time is supplied as data and omitted from the decision variables. Setting c=0c=0 gives the Mayer special case, while setting cf=0c_f=0 gives the Lagrange special case. All three forms use the same transcription.

Two transcription strategies differ in which values become decision variables and in how they impose the differential equation:

StrategyFinite decision variablesTreatment of the ODE
ShootingControl parameters and, in multiple shooting, selected boundary statesA time integrator advances the state sequentially inside each shooting interval
Direct collocationState and control values at selected nodesAlgebraic defect equations enforce the ODE simultaneously

Shooting and collocation are therefore distinct transcription strategies. A collocation method may reproduce a familiar integration formula, but it exposes the state values to the NLP rather than hiding every state update inside a simulation. The term direct indicates that the continuous problem is converted directly into an optimization problem, without first deriving necessary conditions such as the Pontryagin equations.

To apply a local approximation repeatedly over the horizon, divide time into intervals with endpoints

t0<t1<⋯<tN=tf,hk=tk+1−tk.t_0<t_1<\cdots<t_N=t_f,\qquad h_k=t_{k+1}-t_k.

This sequence is a mesh, with k=0,…,N−1k=0,\ldots,N-1 indexing its intervals. We write xk\mathbf x_k and uk\mathbf u_k for values at mesh time tkt_k, as in the earlier discrete-time chapters; the subscript is an index, not a physical time. On each interval, the normalized coordinate is τ=(t−tk)/hk\tau=(t-t_k)/h_k, or equivalently

t=tk+hkτ,0≤τ≤1.t=t_k+h_k\tau,\qquad 0\leq\tau\leq1.

Every physical interval is thereby mapped to the same reference interval [0,1][0,1]. Differentiation and integration formulas can be constructed once on this reference interval; the length hkh_k then supplies the physical-time scaling. If the final time is also optimized, the interval lengths become variables or fixed fractions of the variable horizon; the reference-interval operators remain unchanged.

Polynomial pieces on a time mesh

A single polynomial can represent a trajectory over the entire horizon; the opening example’s solution x(t)=tx(t)=t already does so. Why introduce several pieces? Consider the same scalar dynamics x˙=u\dot x=u, starting from x(0)=0x(0)=0, but now moving right at unit speed until t=1/2t=1/2 and then left at unit speed:

u(t)={1,0≤t<12,−1,12<t≤1,x(t)={t,0≤t≤12,1−t,12≤t≤1.u(t)=\begin{cases} 1,&0\leq t<\tfrac12,\\ -1,&\tfrac12<t\leq1, \end{cases} \qquad x(t)=\begin{cases} t,&0\leq t\leq\tfrac12,\\ 1-t,&\tfrac12\leq t\leq1. \end{cases}

The state is continuous, but its slope changes abruptly at the switching time. Two line segments represent it exactly. A single polynomial has a continuous derivative, so it cannot reproduce this corner exactly, although it can approximate the trajectory. The ODE here holds on either side of the switch; the control’s value at that one instant does not affect its integral.

The comparison below uses the quadratic q(t)=2t(1−t)q(t)=2t(1-t), which passes through the same states at t=0,1/2,1t=0,1/2,1. Matching these three values does not reproduce the motion between them: the quadratic has a continuously varying slope, while the prescribed velocity switches from +1 to -1.

The triangular state trajectory consists of two straight segments meeting at time one half. A dashed quadratic passes through the same three points but curves above the segments. Below, the exact slope jumps from plus one to minus one, whereas the quadratic's slope decreases continuously from plus two to minus two.

Figure 3:Two linear pieces represent the prescribed motion exactly. The dashed orange quadratic q(t)=2t(1−t)q(t)=2t(1-t) matches the three black sample points but differs between them. The lower panel compares their physical-time derivatives. Open circles indicate the two one-sided slopes at the switch, where the exact state has no derivative. Higher-degree global polynomials can improve the approximation, but every single polynomial has a continuous derivative.

Even when the trajectory is smooth, some portions may change much faster than others. Separate polynomial pieces let us shorten the mesh intervals near a rapid change while retaining longer intervals elsewhere. Increasing the degree of one global polynomial adds flexibility across the whole horizon. The local representation also gives the optimizer useful structure: each interval’s dynamics constraints involve only values on that interval and neighboring endpoint states. A global polynomial couples values across the horizon through its differentiation matrix.

For a trajectory that is smooth throughout the horizon, a single polynomial of sufficiently high degree can be an efficient choice. Piecewise polynomials give us local control over resolution, allow changes in slope at joins, and keep the constraint derivatives sparse. They require us to connect adjacent pieces explicitly so that the state remains continuous.

We therefore represent the trajectory by a separate polynomial on each mesh interval [tk,tk+1][t_k,t_{k+1}]. The full approximation xh\mathbf{x}_h is piecewise polynomial. To construct the piece on interval kk, use its local coordinate τ=(t−tk)/hk\tau=(t-t_k)/h_k, where hk=tk+1−tkh_k=t_{k+1}-t_k, and write

pk(τ):=xh(tk+hkτ),0≤τ≤1.\mathbf{p}_k(\tau):=\mathbf{x}_h(t_k+h_k\tau),\qquad 0\le\tau\le1.

Thus pk(0)\mathbf{p}_k(0) is the state at the start of this interval and pk(1)\mathbf{p}_k(1) is the state at its end. On the next interval, the local coordinate starts again at zero, and a different polynomial pk+1\mathbf{p}_{k+1} describes the trajectory. The reference interval and its integration weights can be reused even when the physical intervals have different lengths.

A continuous scalar trajectory has three polynomial pieces on the physical intervals zero to one, one to three, and three to four. Beneath it, the same pieces each occupy a local time axis from zero to one. Adjacent pieces meet at the shared endpoint states x1 and x2.

Figure 4:A scalar example of the piecewise representation. The upper panel joins three quadratic pieces on physical intervals of lengths 1, 2, and 1. The lower panels show those same pieces in their own coordinates 0≤τ≤10\leq\tau\leq1, preserving their colors and line styles. The middle piece uses t=1+2τt=1+2\tau, so two units of physical time occupy one unit of local time. Black dots mark endpoint states; matching the right value of one piece to the left value of the next keeps the trajectory continuous. The slopes need not match across joins.

One Polynomial, Two Coordinate Systems

Should a polynomial trajectory be stored by its coefficients or by its values at selected points, and how are the two descriptions related?

The opening example stored a linear control by its two endpoint values. Each piece of a collocation trajectory needs a similar reconstruction: given the values the optimizer stores, what curve do they define between nodes? Start with a scalar polynomial pp on the reference interval [0,1][0,1]. The construction applies to each state and control component.

Suppose p(0)=y0p(0)=y_0 and p(1)=y1p(1)=y_1, and restrict pp to degree at most one. A line has the form p(τ)=a0+a1τp(\tau)=a_0+a_1\tau. The first endpoint condition gives a0=y0a_0=y_0, and the second gives a0+a1=y1a_0+a_1=y_1, so a1=y1−y0a_1=y_1-y_0. Substitution and regrouping give

p(τ)=y0(1−τ)+y1τ.p(\tau)=y_0(1-\tau)+y_1\tau.

The weights 1−τ1-\tau and τ\tau enforce the two endpoint conditions: at 0, the formula returns y0y_0, and at 1, it returns y1y_1. Between the endpoints, the weights vary continuously; at τ=1/4\tau=1/4, for example, the value is 3y0/4+y1/43y_0/4+y_1/4. This is the unique line through the two prescribed values. Without the degree restriction, other curves could pass through them. Even for this same line, y0+(y1−y0)τy_0+(y_1-y_0)\tau is an equivalent representation. The weighted form is convenient because each stored value has its own function that determines its contribution throughout the interval. The functions 1−τ1-\tau and τ\tau form a basis for linear polynomials, just as 1 and τ\tau do. In the first basis, the coefficients are the endpoint values y0,y1y_0,y_1; in the monomial basis, they are a0=y0a_0=y_0 and a1=y1−y0a_1=y_1-y_0.

Higher-degree pieces use the same idea with more stored values. To specify which curves those values may define, restrict the degree to at most rr. The resulting polynomial space is

Pr={p:deg⁡p≤r}.\mathcal P_r=\{p:\deg p\leq r\}.

This space, denoted by Pr\mathcal P_r, has dimension r+1r+1. Choosing a basis {ϕ0,…,ϕr}\{\phi_0,\ldots,\phi_r\} gives coefficient coordinates

p(τ)=∑j=0rajϕj(τ).p(\tau)=\sum_{j=0}^{r}a_j\phi_j(\tau).

The monomial choice ϕj(τ)=τj\phi_j(\tau)=\tau^j gives the familiar expression a0+a1τ+⋯+arτra_0+a_1\tau+\cdots+a_r\tau^r. A different basis changes the coefficients used to describe the same function.

The same polynomial can instead be identified by its values. Choose r+1r+1 distinct points

σ0,…,σr∈[0,1],\sigma_0,\ldots,\sigma_r\in[0,1],

and record

yi=p(σi).y_i=p(\sigma_i).

These points are called support nodes, or interpolation nodes: once the degree is restricted to at most rr, the r+1r+1 stored values determine the whole polynomial. Storing values is useful for trajectory optimization because a bound on the state at a node then becomes a bound on a decision variable. To evaluate the dynamics between nodes, however, we need a formula that reconstructs the polynomial from those values.

With more nodes, the same construction needs one weight function ℓj\ell_j per stored value yjy_j. To recover yjy_j at its own node without altering the values at the other nodes, ℓj\ell_j must equal one at σj\sigma_j and zero at every other support node. A polynomial with those zeros contains the factors (τ−σm)(\tau-\sigma_m) for all m≠jm\ne j. Dividing their product by its value at σj\sigma_j makes the value there equal to one. This constructs the Lagrange basis function, also called a cardinal function,

ℓj(τ)=∏m=0m≠jrτ−σmσj−σm.\ell_j(\tau) =\prod_{\substack{m=0\\m\neq j}}^r \frac{\tau-\sigma_m}{\sigma_j-\sigma_m}.

At a support node σi\sigma_i, one factor in the numerator is zero unless i=ji=j. When i=ji=j, every numerator equals its corresponding denominator. Consequently,

ℓj(σi)=δij,\ell_j(\sigma_i)=\delta_{ij},

where the Kronecker delta δij\delta_{ij} is shorthand for one when i=ji=j and zero otherwise. This identity specifies the values at the nodes only. At any other τ\tau, the product formula gives a smoothly varying polynomial weight, which can be negative or exceed one for higher degrees. Thus, unlike a line between two endpoint values, a higher-degree interpolant can take values outside the range of its stored values. The cardinal functions reconstruct the polynomial throughout the interval by

p(τ)=∑j=0ryjℓj(τ),yj=p(σj).\boxed{ p(\tau)=\sum_{j=0}^{r}y_j\ell_j(\tau), \qquad y_j=p(\sigma_j). }

At a node σi\sigma_i, all terms except yiℓi(σi)=yiy_i\ell_i(\sigma_i)=y_i vanish. Between nodes, the weighted sum supplies the intervening values. This expression is the Lagrange form of pp. When the values yjy_j are prescribed and pp is constructed to pass through them, pp is called the Lagrange interpolating polynomial. Each ℓj\ell_j is one basis function; pp is their weighted sum. Changing one stored value yjy_j by Δyj\Delta y_j changes the curve by Δyjℓj(τ)\Delta y_j\ell_j(\tau), so the same function describes how that variable affects the entire polynomial.

The uniqueness of this reconstruction follows from a basic root-counting argument. If two polynomials in Pr\mathcal P_r have the same r+1r+1 nodal values, their difference has r+1r+1 distinct roots. A nonzero polynomial of degree at most rr cannot have that many roots, so the two polynomials must be identical.

Consequently, every polynomial in Pr\mathcal P_r has a unique expansion in ℓ0,…,ℓr\ell_0,\ldots,\ell_r. These functions form the Lagrange basis associated with the chosen support nodes. Its coefficients are exactly the nodal values yj=p(σj)y_j=p(\sigma_j). Storing a polynomial by its values is therefore also a basis representation, with the basis chosen so that its coefficients have a direct interpretation as function values.

For a scalar polynomial, collect the coefficients and values into column vectors a=(a0,…,ar)⊤\mathbf a=(a_0,\ldots,a_r)^\top and y=(y0,…,yr)⊤\mathbf y=(y_0,\ldots,y_r)^\top. The coefficient and nodal descriptions are related by the evaluation matrix

Vij=ϕj(σi),y=Va.V_{ij}=\phi_j(\sigma_i), \qquad \mathbf{y}=V\mathbf{a}.

Distinct support nodes make VV invertible by the same root-counting argument. Thus a\mathbf{a} and y\mathbf{y} are two coordinate vectors for one polynomial, rather than two different approximations.

For example, take p(τ)=1+2τ−τ2p(\tau)=1+2\tau-\tau^2 and the nodes 0,12,10,\tfrac12,1. In monomial coordinates,

a=[12−1],V=[10011214111],\mathbf{a}= \begin{bmatrix}1\\2\\-1\end{bmatrix}, \qquad V= \begin{bmatrix} 1&0&0\\ 1&\tfrac12&\tfrac14\\ 1&1&1 \end{bmatrix},

whereas the nodal coordinates are

y=Va=[1742].\mathbf{y}=V\mathbf{a}= \begin{bmatrix}1\\\tfrac74\\2\end{bmatrix}.

In the Lagrange basis for these three nodes, the same quadratic is

p(τ)=ℓ0(τ)+74ℓ1(τ)+2ℓ2(τ).p(\tau)=\ell_0(\tau)+\tfrac74\ell_1(\tau)+2\ell_2(\tau).

Here ℓ0,ℓ1,ℓ2\ell_0,\ell_1,\ell_2 select the nodes 0,12,10,\tfrac12,1, respectively. Both coefficient vectors describe the same quadratic. The transcription used here chooses coordinates like y\mathbf y for the state and control: values that can be inserted directly into the dynamics, cost, and bounds. Monomial coefficients remain useful for deriving formulas that act on those values.

Polynomial spaces, bases, and nodes

The construction separates three decisions that are easy to conflate:

Changing the basis does not change the exact polynomial space, although it can change numerical conditioning. Changing the nodes changes the interpolation and the operators built from it. The node families introduced later can still use Lagrange nodal coordinates in the NLP.

Polynomial Interpolation and Least-Squares Regression

When should a polynomial pass through every supplied value, and when should it approximate those values with a smaller number of coefficients?

Interpolation and polynomial regression impose different requirements on the same data. Six values at six distinct nodes determine one polynomial of degree at most five that passes through every point. If the values are noisy observations and the aim is to estimate a quadratic trend, a quadratic will generally be unable to pass through all six. Least-squares regression then chooses its three coefficients to minimize the sum of squared discrepancies. The figure below applies both choices to the same six points. Write VV for the basis evaluation matrix, as in y=Va\mathbf y=V\mathbf a. It has six rows in both cases, but six columns for the degree-five interpolant and three for the quadratic fit.

Polynomial interpolationLeast-squares regression
InputExact value conditionsUsually noisy or overdetermined observations
Algebraic problemSatisfy Va=yV\mathbf{a}=\mathbf{y} exactlyMinimize ∥Va−y∥22\lVert V\mathbf{a}-\mathbf{y}\rVert_2^2
ResidualZero when the value conditions uniquely determine a polynomialGenerally nonzero
Typical purposeRepresent a function from exact nodal dataEstimate a trend or conditional mean

If VV is square and invertible, minimizing the squared residual also returns the exact interpolant. The two procedures coincide in that case because the chosen polynomial space can satisfy every value condition.

Figure 5:Two polynomials fitted to the same six points (black dots). The solid blue degree-five interpolant passes through every point. The dashed orange quadratic regression minimizes the sum of squared residuals, shown as gray vertical segments. The polynomial degrees differ because a quadratic generally cannot satisfy all six value conditions exactly.

<Figure size 550x280 with 1 Axes>

In a nodal state representation of direct collocation, the stored states are unknown decision variables rather than observations. Each candidate vector of support values defines one interpolating polynomial exactly. The optimizer selects a candidate whose ODE residual vanishes at the collocation nodes. A numerical solver may temporarily work with a scalar measure of constraint violation, but the collocation conditions remain equality constraints rather than a statistical regression loss.

Fixed Operators from Nodal Values

How can the stored values give us both the slopes and the integral of every candidate polynomial?

The nodal representation also turns differentiation and integration into matrix and vector products. Once the nodes are chosen, the required arrays can be computed once and reused for every candidate trajectory. The optimizer can then evaluate slopes and integrals as it changes the nodal values, without recomputing those arrays. Each cardinal function is a known polynomial, so its derivative and antiderivative can be computed in closed form by changing its coefficients. Constructing the arrays therefore does not require finite-difference approximations or numerical quadrature of the basis functions. The resulting operators are exact for the represented polynomial, apart from floating-point error in their construction and application.

This is possible because the cardinal functions stay fixed while only their coefficients yjy_j change. In the representation

p(τ)=∑j=0ryjℓj(τ),p(\tau)=\sum_{j=0}^r y_j\ell_j(\tau),

each yjy_j is constant with respect to τ\tau, so differentiation acts only on ℓj\ell_j. To obtain the slopes needed by the ODE, differentiate and then evaluate at a node σi\sigma_i:

p′(σi)=∑j=0rℓj′(σi)⏟Dijyj,p'(\sigma_i)=\sum_{j=0}^r \underbrace{\ell_j'(\sigma_i)}_{D_{ij}}y_j,

while integration over the reference interval gives

∫01p(τ) dτ=∑j=0r(∫01ℓj(τ) dτ)⏟wjyj.\int_0^1p(\tau)\,d\tau =\sum_{j=0}^r \underbrace{\left(\int_0^1\ell_j(\tau)\,d\tau\right)}_{w_j}y_j.

The resulting differentiation matrix DD and integration weights ww depend only on the chosen nodes. Their entries record the derivatives and areas of the fixed cardinal functions. Multiplication by the current nodal values combines these contributions into the derivative or integral of the current polynomial. This is how the transcription supplies the calculus needed by the dynamics and cost through arithmetic on the decision variables.

For a concrete construction, suppose a quadratic is stored by its values y0,ym,y1y_0,y_m,y_1 at 0,12,10,\tfrac12,1, where mm denotes the midpoint. The desired outputs are its slope at each of these nodes and its integral over [0,1][0,1]. To build the arrays that return those outputs for any choice of the three values, first construct the three cardinal functions multiplying them:

ℓ0(τ)=(τ−12)(τ−1)(0−12)(0−1)=2τ2−3τ+1,ℓm(τ)=τ(τ−1)(12−0)(12−1)=4τ(1−τ),ℓ1(τ)=τ(τ−12)(1−0)(1−12)=2τ2−τ.\begin{aligned} \ell_0(\tau) &=\frac{(\tau-\frac12)(\tau-1)}{(0-\frac12)(0-1)} =2\tau^2-3\tau+1,\\ \ell_m(\tau) &=\frac{\tau(\tau-1)}{(\frac12-0)(\frac12-1)} =4\tau(1-\tau),\\ \ell_1(\tau) &=\frac{\tau(\tau-\frac12)}{(1-0)(1-\frac12)} =2\tau^2-\tau. \end{aligned}

These are the three contributions in p(τ)=y0ℓ0(τ)+ymℓm(τ)+y1ℓ1(τ)p(\tau)=y_0\ell_0(\tau)+y_m\ell_m(\tau)+y_1\ell_1(\tau). Their derivatives are 4τ−34\tau-3, 4−8τ4-8\tau, and 4τ−14\tau-1. At the left endpoint they give the weights −3,4,−1-3,4,-1, so p′(0)=−3y0+4ym−y1p'(0)=-3y_0+4y_m-y_1. Evaluation at the midpoint and right endpoint supplies the other two rows of the differentiation matrix:

[p′(0)p′(12)p′(1)]=[−34−1−1011−43]⏟D[y0ymy1].\begin{bmatrix} p'(0)\\ p'(\tfrac12)\\ p'(1) \end{bmatrix} = \underbrace{ \begin{bmatrix} -3&4&-1\\ -1&0&1\\ 1&-4&3 \end{bmatrix}}_{D} \begin{bmatrix} y_0\\y_m\\y_1 \end{bmatrix}.

Thus a matrix-vector product turns the three stored values into the three nodal derivatives. Integrating the same cardinal functions gives

∫01p(τ) dτ=[164616]⏟wT[y0ymy1].\int_0^1 p(\tau)\,d\tau = \underbrace{ \begin{bmatrix} \tfrac16&\tfrac46&\tfrac16 \end{bmatrix}}_{w^\mathsf T} \begin{bmatrix} y_0\\y_m\\y_1 \end{bmatrix}.

The row wTw^\mathsf T turns the same three values into the exact integral of their quadratic interpolant. The matrix DD differentiates every polynomial in P2\mathcal P_2 exactly, and ww integrates every polynomial in that space exactly. “Fixed operator” means that these arrays remain constant while the NLP changes the nodal values.

For the earlier polynomial p(τ)=1+2τ−τ2p(\tau)=1+2\tau-\tau^2, the stored values are y=(1,74,2)⊤\mathbf y=(1,\tfrac74,2)^\top. Applying the arrays gives

Dy=[210],w⊤y=16+4674+16 2=53.D\mathbf y=\begin{bmatrix}2\\1\\0\end{bmatrix}, \qquad w^\top\mathbf y =\frac16+\frac46\frac74+\frac16\,2=\frac53.

The first result agrees with p′(τ)=2−2τp'(\tau)=2-2\tau at the three nodes; the second agrees with the integral of 1+2τ−τ21+2\tau-\tau^2 over [0,1][0,1]. The figure collects the coordinate change and these two operations for that same polynomial.

The quadratic p of tau equals one plus two tau minus tau squared is represented by its monomial coefficient vector and by its values at support nodes zero, one half, and one. Fixed differentiation and quadrature operators act on the nodal vector.

Figure 6:One quadratic, two coordinate systems. Evaluating the monomial coefficients a\mathbf{a} at the support nodes gives y=Va\mathbf{y}=V\mathbf{a}. Direct collocation stores the nodal vector y\mathbf{y}; the fixed operators DD and ww then return its nodal derivatives and exact integral without adding optimization variables.

A transcription may use nodes for three distinct purposes:

Node roleWhat it does
Support nodeSupplies a value used to define a particular polynomial by interpolation
Collocation nodeSpecifies where the reconstructed state must satisfy the ODE
Quadrature nodeSupplies a point used to approximate an integral

A method often reuses one set of points for two or three roles. Support nodes belong to a particular polynomial: the state, control, and slope polynomials need not use the same interpolation nodes. For example, a state polynomial can be supported at one set of nodes and differentiated at different collocation nodes. In the integration construction below, the collocation nodes also serve as support nodes for the slope polynomial. Both integration and differentiation forms have collocation nodes. Their algebraic residuals differ: one matches state values after integrating slopes, while the other matches slopes after interpolating state values.

From Nodal Slopes to Collocation Constraints

How do those fixed maps convert differential equations into algebraic constraints at the chosen collocation nodes?

The preceding operators apply to any polynomial. A trajectory must also satisfy the dynamics. On each interval, the construction will interpolate the slopes supplied by the ODE and integrate them from the stored left state. Constraints will then require the stored states to agree with the resulting polynomial.

Interval kk extends from physical time tkt_k to tk+1t_{k+1} and contains one polynomial piece. Its local time coordinate runs from τ=0\tau=0 at the left endpoint to τ=1\tau=1 at the right endpoint. Choose ss distinct collocation nodes τ1,…,τs\tau_1,\ldots,\tau_s on [0,1][0,1]. A node τj\tau_j specifies one time within this interval, tk+hkτjt_k+h_k\tau_j; for example, τj=1/2\tau_j=1/2 specifies its midpoint. The state and control values at that time are xk,j\mathbf{x}_{k,j} and uk,j\mathbf{u}_{k,j}.

This evaluation point, together with its state and control values, is called a stage. A stage is associated with a single time, so it has no separate beginning or end. The index kk identifies the interval and jj identifies a stage in that interval. Depending on the method, stages may be interior points or may coincide with an endpoint: τj=0\tau_j=0 places a stage at the start and τj=1\tau_j=1 places one at the end. At each stage, the differential equation prescribes the physical-time slope

fk,j=f(xk,j,uk,j,tk+hkτj)\mathbf{f}_{k,j} =\mathbf{f}(\mathbf{x}_{k,j},\mathbf{u}_{k,j},t_k+h_k\tau_j)

for j=1,…,sj=1,\ldots,s. The figure below shows one such evaluation for the scalar dynamics x˙=u\dot x=u. The state and control are read at the same physical time; the ODE then gives the slope that the state polynomial must match there.

State and control curves on the physical interval from time two to four. An orange vertical line at time three marks the collocation node tau j equals one half. The stage state is two and the stage control is three halves. A dashed tangent at the state point has physical-time slope three halves, as required by x dot equals u.

Figure 7:A stage combines a time, a state value, and a control value. Here tk=2t_k=2, hk=2h_k=2, and τj=1/2\tau_j=1/2, so the stage occurs at physical time 3. The orange dots mark xk,j=2x_{k,j}=2 and uk,j=3/2u_{k,j}=3/2. For x˙=u\dot x=u, the prescribed slope is fk,j=3/2f_{k,j}=3/2, drawn as a dashed tangent in the upper panel. The curves are uh(t)=1/2+(t−2)u_h(t)=1/2+(t-2) and xh(t)=1+(t−2)/2+(t−2)2/2x_h(t)=1+(t-2)/2+(t-2)^2/2. Both panels use physical time; in local coordinates the derivative is pk′(τj)=hkfk,j=3p_k'(\tau_j)=h_kf_{k,j}=3.

As before, pk(τ)=xh(tk+hkτ)\mathbf p_k(\tau)=\mathbf x_h(t_k+h_k\tau) denotes the state piece in normalized time. Here ℓj\ell_j is the Lagrange cardinal function for the slope nodes τ1,…,τs\tau_1,\ldots,\tau_s; the ss slope values determine a polynomial of degree at most s−1s-1. Interpolating them gives the physical-time derivative on this one interval:

x˙h(tk+hkτ)=∑j=1sfk,jℓj(τ).\dot{\mathbf{x}}_h(t_k+h_k\tau) =\sum_{j=1}^{s}\mathbf{f}_{k,j}\ell_j(\tau).

This polynomial agrees with each sampled slope fk,j\mathbf{f}_{k,j} at its collocation node. Each sample was evaluated at a stored stage state xk,j\mathbf x_{k,j}; the state curve recovered by integration need not yet pass through those stored states. The stage equations below will enforce that agreement. To recover state values from these slopes, we need to reverse differentiation. For example, both τ+2τ2\tau+2\tau^2 and 1+τ+2τ21+\tau+2\tau^2 have derivative 1+4τ1+4\tau. Each is an antiderivative of 1+4τ1+4\tau: differentiating it returns that function. Adding any constant gives another antiderivative, because the derivative of a constant is zero. The slopes therefore determine changes in state, but a starting value is needed to determine the state itself. This starting value fixes the integration constant.

The fundamental theorem of calculus connects antiderivatives to definite integrals. For a scalar function pp with a continuous derivative, it states that integrating the derivative between two points gives the difference between the function values:

∫0τp′(η) dη=p(τ)−p(0),p(τ)=p(0)+∫0τp′(η) dη.\int_0^\tau p'(\eta)\,d\eta=p(\tau)-p(0), \qquad p(\tau)=p(0)+\int_0^\tau p'(\eta)\,d\eta.

Here η\eta is a dummy variable that runs over the integration interval; τ\tau specifies where that interval ends. Over a short interval of width Δη\Delta\eta, the change in pp is approximately p′(η)Δηp'(\eta)\Delta\eta. The definite integral is the limit of sums of these signed changes as the interval widths approach zero. Adding p(0)p(0) to this accumulated change recovers the value at τ\tau. Polynomial derivatives are continuous, so the theorem applies to the pieces used here. For a vector state, the same identity applies to each component.

The sampled slopes fk,j\mathbf f_{k,j} are derivatives with respect to physical time, whereas pk\mathbf p_k is expressed in local time. Since t=tk+hkτt=t_k+h_k\tau, the chain rule gives

pk′(τ)=hkx˙h(tk+hkτ)=hk∑j=1sfk,jℓj(τ).\mathbf p_k'(\tau) =h_k\dot{\mathbf x}_h(t_k+h_k\tau) =h_k\sum_{j=1}^{s}\mathbf f_{k,j}\ell_j(\tau).

The factor hkh_k accounts for the duration of the physical interval: a change of Δτ\Delta\tau in local time corresponds to hkΔτh_k\Delta\tau units of physical time. For example, a constant physical slope f\mathbf f acting from 0 to τi\tau_i changes the state by hkτifh_k\tau_i\mathbf f.

Applying the fundamental theorem with the left-endpoint condition pk(0)=xk\mathbf p_k(0)=\mathbf x_k now gives the state piece:

pk(τ)=xk+∫0τpk′(η) dη=xk+hk∑j=1s(∫0τℓj(η) dη)fk,j.\begin{aligned} \mathbf{p}_k(\tau) &=\mathbf{x}_k+\int_0^\tau\mathbf p_k'(\eta)\,d\eta\\ &=\mathbf{x}_k+h_k\sum_{j=1}^{s} \left(\int_0^\tau\ell_j(\eta)\,d\eta\right)\mathbf{f}_{k,j}. \end{aligned}

The final expression follows by substituting the slope polynomial and integrating its terms separately; the stage slopes fk,j\mathbf f_{k,j} are constant with respect to η\eta. At τ=0\tau=0, every integral vanishes, leaving pk(0)=xk\mathbf p_k(0)=\mathbf x_k. Differentiating the expression returns the prescribed slope polynomial. Thus the stored left state selects the unique antiderivative with that starting value. Integrating a polynomial of degree at most s−1s-1 gives a state piece of degree at most ss.

For the scalar example above, hk=2h_k=2 and the physical-time slope is 1/2+2τ1/2+2\tau, so pk′(τ)=1+4τp_k'(\tau)=1+4\tau. Starting from xk=1x_k=1 gives

pk(τ)=1+∫0τ(1+4η) dη=1+[η+2η2]0τ=1+τ+2τ2.p_k(\tau)=1+\int_0^\tau(1+4\eta)\,d\eta =1+\left[\eta+2\eta^2\right]_0^\tau =1+\tau+2\tau^2.

The brackets mean evaluating the antiderivative at the upper limit and subtracting its value at the lower limit. At τi=1/2\tau_i=1/2, the accumulated change is 1, so pk(1/2)=2p_k(1/2)=2. At τ=1\tau=1, the accumulated change is 3, so pk(1)=4p_k(1)=4. These are two evaluations of the same polynomial: both integrals begin at the left endpoint, but one stops at a stage and the other at the right endpoint.

This integration is exact for the interpolated slope polynomial. It does not generally give the exact solution of the original ODE: between collocation nodes, the interpolated slope can differ from f(pk(τ),uh(tk+hkτ),tk+hkτ)\mathbf f(\mathbf p_k(\tau),\mathbf u_h(t_k+h_k\tau),t_k+h_k\tau).

One quadratic state piece spans local time zero to one, corresponding to physical time two to four. A circular stage point at local time one half has state two; square endpoints have states one and four. A blue arrow from zero to one half shows the integration span for a stage equation. An orange arrow from zero to one shows the integration span for the endpoint equation. Both arrows start at the same left endpoint.

Figure 8:Two integration limits on one polynomial piece. Here tk=2t_k=2, hk=2h_k=2, and pk(τ)=1+τ+2τ2p_k(\tau)=1+\tau+2\tau^2. The circle marks stage ii at τi=1/2\tau_i=1/2, corresponding to physical time 3; the squares mark the interval endpoints. The blue span accumulates a state change of 1 from τ=0\tau=0 to τi\tau_i, giving xk,i=2x_{k,i}=2. The orange span accumulates a state change of 3 from τ=0\tau=0 to 1, giving xk+1=4x_{k+1}=4. The arrows mark integration spans, not successive steps of a numerical solver. The changes come from integrating the derivative of the state curve shown. Only stage ii is highlighted; each sum uses all the slope stages on the interval.

The integration construction already determines the derivative at each collocation node. Because ℓj(τi)=δij\ell_j(\tau_i)=\delta_{ij}, any candidate stage values give

1hkpk′(τi)=fk,i=f(xk,i,uk,i,tk+hkτi),∀ k=0,…,N−1,i=1,…,s.\begin{gathered} \frac{1}{h_k}\mathbf p_k'(\tau_i) =\mathbf f_{k,i} =\mathbf f(\mathbf x_{k,i},\mathbf u_{k,i},t_k+h_k\tau_i),\\ \forall\,k=0,\ldots,N-1,\quad i=1,\ldots,s. \end{gathered}

This identity holds by construction, before the NLP constraints have been satisfied. It does not yet guarantee that the reconstructed curve satisfies the ODE: the dynamics on the right are evaluated at the stored state xk,i\mathbf x_{k,i}, which may differ from the curve’s value pk(τi)\mathbf p_k(\tau_i).

The remaining condition is agreement between these two state values. The point (τi,xk,i)(\tau_i,\mathbf{x}_{k,i}) must lie on the graph of pk\mathbf{p}_k. Imposing xk,i=pk(τi)\mathbf{x}_{k,i}=\mathbf{p}_k(\tau_i) at every stage of every interval gives the stage equations

xk,i=xk+hk∑j=1sAijfk,j,∀ k=0,…,N−1,i=1,…,s.\mathbf{x}_{k,i} =\mathbf{x}_k+h_k\sum_{j=1}^{s}A_{ij}\mathbf{f}_{k,j}, \qquad \forall\,k=0,\ldots,N-1,\quad i=1,\ldots,s.

Their coefficients are

Aij=∫0τiℓj(τ) dτ,i,j=1,…,s.A_{ij}=\int_0^{\tau_i}\ell_j(\tau)\,d\tau, \qquad i,j=1,\ldots,s.

Each stage equation therefore sets a state-matching residual to zero:

rk,iint=xk,i−pk(τi)=0,∀ k=0,…,N−1,i=1,…,s.\mathbf r^{\mathrm{int}}_{k,i} =\mathbf x_{k,i}-\mathbf p_k(\tau_i)=\mathbf0, \qquad \forall\,k=0,\ldots,N-1,\quad i=1,\ldots,s.

Once this residual vanishes, the stored state used to evaluate fk,i\mathbf f_{k,i} is the state on the reconstructed curve. Substituting xk,i=pk(τi)\mathbf x_{k,i}=\mathbf p_k(\tau_i) into the derivative identity above then gives the ODE at that node. Thus the integration form enforces the ODE at collocation nodes even though its explicit stage residuals compare state values rather than slopes.

For example, consider x˙=x\dot x=x on an interval of length hk=1h_k=1, starting from xk=1x_k=1, with one collocation node at τ1=1/2\tau_1=1/2. A trial stage value xk,1=3x_{k,1}=3 supplies the slope fk,1=3f_{k,1}=3 and hence the state piece pk(τ)=1+3τp_k(\tau)=1+3\tau. Its midpoint value is 5/25/2, not the stored value 3. The polynomial’s slope is 3, but the ODE evaluated on the curve at the midpoint asks for 5/25/2. The state equation xk,1=1+12xk,1x_{k,1}=1+\tfrac12 x_{k,1} instead gives xk,1=2x_{k,1}=2. The reconstructed polynomial is then pk(τ)=1+2τp_k(\tau)=1+2\tau, whose midpoint state and slope both equal 2. The state-matching constraint has made the ODE hold at the collocation node.

Row ii of AA integrates each slope basis function from 0 to τi\tau_i. Here ii selects the destination stage and jj runs over all slope stages that contribute to the polynomial. All these integrals start at 0, including when i>1i>1; they do not start at the preceding stage. The stage equations are simultaneous constraints, since the slopes on the right depend on the state and control variables being chosen.

For example, take three slope stages at (τ1,τ2,τ3)=(0,12,1)(\tau_1,\tau_2,\tau_3)=(0,\tfrac12,1) and fix the destination i=2i=2. Every integral in row 2 of AA now ends at τ2=12\tau_2=\tfrac12. Varying jj selects the basis function being integrated: ℓ1\ell_1, ℓ2\ell_2, or ℓ3\ell_3. Figure 9 shows these three signed areas. Although the third slope is evaluated at the right endpoint τ3=1\tau_3=1, its basis function ℓ3\ell_3 is nonzero between 0 and 12\tfrac12. That slope therefore contributes to the midpoint state as well. The sum runs over all j=1,…,sj=1,\ldots,s, not only stages with j≤ij\leq i.

Three panels show the cardinal slope basis functions for nodes zero, one half, and one. The destination is fixed at stage i equals two, marked by the same vertical dashed line at one half in each panel. Shaded signed areas from zero to one half give A21 equals five twenty-fourths, A22 equals one third, and A23 equals minus one twenty-fourth. All three slopes enter the midpoint state equation.

Figure 9:Fixing ii chooses the integration endpoint; varying jj includes every slope basis function. For nodes (0,12,1)(0,\tfrac12,1), the blue curves are ℓ1(τ)=2τ2−3τ+1\ell_1(\tau)=2\tau^2-3\tau+1, ℓ2(τ)=4τ(1−τ)\ell_2(\tau)=4\tau(1-\tau), and ℓ3(τ)=2τ2−τ\ell_3(\tau)=2\tau^2-\tau. Each blue point marks the node where its basis function equals one. The orange dashed line marks the common upper limit τi=τ2=12\tau_i=\tau_2=\tfrac12. The shaded signed areas form row 2 of AA: (A21,A22,A23)=(524,13,−124)(A_{21},A_{22},A_{23})=(\tfrac5{24},\tfrac13,-\tfrac1{24}). The third area is negative because ℓ3\ell_3 lies below zero on this span; it is not absent just because stage 3 lies after the destination. The bottom equation expands the sum for a scalar state; the same coefficients multiply vector-valued slopes in the general case.

The right endpoint has local coordinate 1. Evaluating the same polynomial there uses the full integration span [0,1][0,1], in place of the span [0,τi][0,\tau_i] used for stage ii. Requiring this value pk(1)\mathbf{p}_k(1) to equal the stored endpoint state xk+1\mathbf{x}_{k+1} gives

xk+1=xk+hk∑j=1sbjfk,j,bj=∫01ℓj(τ) dτ.\boxed{ \mathbf{x}_{k+1} =\mathbf{x}_k+h_k\sum_{j=1}^{s}b_j\mathbf{f}_{k,j}, \qquad b_j=\int_0^1\ell_j(\tau)\,d\tau. }

The coefficients bjb_j integrate the cardinal functions over the full reference interval. In code, both AijA_{ij} and bjb_j are obtained by forming an antiderivative polynomial and evaluating it at the integration limits. No repeated integration of the nonlinear dynamics is involved in computing these coefficients. The worked calculation in A reusable coefficient generator and residual evaluator shows the coefficient arithmetic. If a stage lies at τi=1\tau_i=1, then Aij=bjA_{ij}=b_j for every jj, so its stage equation gives the same polynomial value as the endpoint equation. Its state must therefore equal xk+1\mathbf{x}_{k+1}; an implementation can share that variable.

The endpoint equation is a defect constraint: it requires the stored endpoint to match the endpoint obtained by integrating the slopes of piece kk. The next piece starts from that same stored value, so the two pieces meet:

pk(1)=xk+1=pk+1(0).\mathbf{p}_k(1)=\mathbf{x}_{k+1}=\mathbf{p}_{k+1}(0).

This shared variable enforces state continuity across the join. If an implementation stores separate endpoint variables for the two pieces, it must impose an equality between them. Choosing an endpoint as a collocation node does not by itself join the pieces. Continuity of the state also does not require continuity of its derivative; neighboring pieces can meet with different slopes.

Three quadratic state segments meet at shared endpoint states in physical time. The middle segment is repeated on a normalized time axis from zero to one, with two interior stage values and their slopes marked.

Figure 10:A scalar trajectory consists of polynomial pieces joined at shared endpoint states (squares). The middle piece pkp_k is shown in blue in both panels; only its time coordinate changes. The stage values (orange dots) lie at τ1=1/3\tau_1=1/3 and τ2=2/3\tau_2=2/3. Tangent marks in the lower panel have slopes pk′(τj)=hkfk,jp_k'(\tau_j)=h_kf_{k,j} because that axis uses normalized time. The stage equations place the dots on the piece, and the endpoint defect makes it reach xk+1x_{k+1}, where the next piece begins. This schematic uses quadratic state pieces and linear slope interpolants.

The running-cost integral can be treated by exactly the same construction. As in the earlier Bolza-to-Mayer reduction, introduce a scalar state y(t)y(t) that records the cost accumulated since t0t_0:

y(t)=∫t0tc(x(s),u(s),s) ds,y˙(t)=c(x(t),u(t),t),y(t0)=0.y(t)=\int_{t_0}^{t}c(\mathbf{x}(s),\mathbf{u}(s),s)\,ds, \qquad \dot y(t)=c(\mathbf{x}(t),\mathbf{u}(t),t),\qquad y(t_0)=0.

The original objective is now cf(x(tf),tf)+y(tf)c_f(\mathbf{x}(t_f),t_f)+y(t_f). The augmented state (x⊤,y)⊤(\mathbf{x}^{\top},y)^{\top} has two rates of change: f\mathbf{f} supplies the physical state rate and cc supplies the cost rate. Applying the same collocation method to this augmented ODE evaluates both rates at each stage’s state, control, and time. In particular, the cost rate at stage jj of interval kk is

ck,j:=c(xk,j,uk,j,tk+hkτj).c_{k,j}:=c(\mathbf{x}_{k,j},\mathbf{u}_{k,j},t_k+h_k\tau_j).

Let yky_k and yk,iy_{k,i} denote the accumulated-cost values at the interval’s left endpoint and at stage ii. Integrating the cost-rate interpolant to a stage and to the right endpoint gives

yk,i=yk+hk∑j=1sAijck,j,yk+1=yk+hk∑j=1sbjck,j.y_{k,i}=y_k+h_k\sum_{j=1}^{s}A_{ij}c_{k,j}, \qquad y_{k+1}=y_k+h_k\sum_{j=1}^{s}b_jc_{k,j}.

These are the same stage and endpoint equations as for x\mathbf{x}, with ck,jc_{k,j} in place of fk,j\mathbf{f}_{k,j}. The increase in accumulated cost across interval kk therefore supplies its contribution to the discrete objective:

ck:=yk+1−yk=hk∑j=1sbjck,j≈∫tktk+1c(xh(t),uh(t),t) dt.c_k:=y_{k+1}-y_k =h_k\sum_{j=1}^{s}b_jc_{k,j} \approx\int_{t_k}^{t_{k+1}}c(\mathbf{x}_h(t),\mathbf{u}_h(t),t)\,dt.

Here uh\mathbf{u}_h is the piecewise control approximation. The single-index ckc_k is an interval cost, whereas ck,jc_{k,j} is a sampled cost rate; the factor hkh_k gives them different units. The weighted sum integrates the polynomial interpolating the sampled cost rates exactly. It approximates the running-cost integral because the composed function c(xh(t),uh(t),t)c(\mathbf{x}_h(t),\mathbf{u}_h(t),t) need not itself be that polynomial. Such a weighted-sum approximation is called a quadrature rule.

Since y0=0y_0=0, summing the interval increments gives yN=∑k=0N−1cky_N=\sum_{k=0}^{N-1}c_k. The accumulated-cost variables can therefore be eliminated and the sum used directly in the objective. Introducing yy explains why applying one collocation scheme to both rates uses the same nodes and weights; it does not require adding cost variables to the implementation. A different quadrature rule is also possible, with the state and control polynomials evaluated at that rule’s nodes.

Choosing a polynomial for the control

The state and control play different roles. The ODE constrains the state’s derivative, so integrating an interpolant of ss slopes gives a state polynomial of degree at most ss. There is no corresponding ODE for the control in this problem. Its representation is a separate choice: a constant command on each interval, a line between endpoint commands, or a higher-degree polynomial.

For example, with two endpoint slopes the state polynomial is quadratic. The control can still be the line

uh(tk+hkτ)=(1−τ)uk+τuk+1.\mathbf u_h(t_k+h_k\tau) =(1-\tau)\mathbf u_k+\tau\mathbf u_{k+1}.

These degrees need not match. Also, substituting polynomial state and control curves into a nonlinear f\mathbf f need not produce a polynomial. Collocation matches the state derivative to f\mathbf f at the chosen nodes, rather than requiring the ODE to hold identically between them.

To make the control choice explicit, select distinct control support nodes ρ0,…,ρdu∈[0,1]\rho_0,\ldots,\rho_{d_u}\in[0,1] and construct their cardinal functions ψ0,…,ψdu\psi_0,\ldots,\psi_{d_u}. The decision variables u^k,r\widehat{\mathbf u}_{k,r} are control values at these support nodes:

uh(tk+hkτ)=∑r=0duu^k,rψr(τ),uk,j=∑r=0duBjru^k,r,Bjr:=ψr(τj).\mathbf u_h(t_k+h_k\tau) =\sum_{r=0}^{d_u}\widehat{\mathbf u}_{k,r}\psi_r(\tau), \qquad \mathbf u_{k,j} =\sum_{r=0}^{d_u}B_{jr}\widehat{\mathbf u}_{k,r}, \quad B_{jr}:=\psi_r(\tau_j).

Thus the stage controls uk,j\mathbf u_{k,j} are computed from the control variables; they are independent variables only when the representation allows independent values at all those stages. For a constant control, du=0d_u=0 and every entry of BB is one. For a linear control supported at 0,10,1, row jj of BB is (1−τj,τj)(1-\tau_j,\tau_j).

Using more control support values than collocation stages leaves some control variations invisible to those stage samples. The examples below use du+1≤sd_u+1\leq s; a richer control representation needs additional sampling or other constraints to account for those variations.

The ODE requires continuous states, but it can admit controls that jump between intervals. Independent control polynomials therefore need no continuity constraint unless the model or chosen parameterization requires one. Sharing endpoint controls between neighboring linear pieces enforces continuity. With separate local variables, the equivalent equality is

∑r=0duψr(1)u^k,r=∑r=0duψr(0)u^k+1,r.\sum_{r=0}^{d_u}\psi_r(1)\widehat{\mathbf u}_{k,r} =\sum_{r=0}^{d_u}\psi_r(0)\widehat{\mathbf u}_{k+1,r}.

At a jump, endpoint stages use the control belonging to their own interval. Finally, bounds on support values guarantee bounds throughout a constant or linear control segment, because its values are convex combinations of its endpoints. A higher-degree polynomial can overshoot those values, so support bounds alone no longer give that guarantee.

Assembling the nonlinear program

The state and control representations now specify which quantities the optimizer chooses and which it evaluates from those choices. The mesh states xk,xk+1\mathbf x_k,\mathbf x_{k+1} are shared decision variables at the ends of interval kk. The stage states xk,j\mathbf x_{k,j} are decision variables at the collocation nodes; a stage at an endpoint can use the mesh state itself. For the control, the optimizer chooses the support values u^k,r\widehat{\mathbf u}_{k,r} at ρr\rho_r. Evaluating the control polynomial at τj\tau_j gives uk,j\mathbf u_{k,j}. Together with xk,j\mathbf x_{k,j} and the time tk+hkτjt_k+h_k\tau_j, this evaluated control supplies the inputs to f\mathbf f and cc.

State and control curves share one interval with collocation nodes at one third and two thirds. Filled blue squares mark shared mesh state variables, filled blue circles mark stage state variables, and filled blue diamonds mark control support variables at zero and one. An open orange circle marks the stage control evaluated using B. The selected stage state, evaluated control, and time determine the dynamics and cost rates on the right.

Figure 11:Decision variables and evaluated quantities on one interval. The curves show a scalar example with two collocation nodes, τ1=1/3\tau_1=1/3 and τ2=2/3\tau_2=2/3; the highlighted stage is j=2j=2. Squares mark mesh states shared with neighboring intervals, and filled circles mark stage states. Diamonds mark the independent control values at ρ0=0\rho_0=0 and ρ1=1\rho_1=1. For this linear control, Bj,:=(1−τj,τj)B_{j,:}=(1-\tau_j,\tau_j) evaluates the open circle uk,j\mathbf u_{k,j} from the two diamonds. The quantities fk,j\mathbf f_{k,j} and ck,jc_{k,j} are then evaluated at the stage state, control, and physical time. Filled blue markers are decision variables; the orange control value and rates are computed from them. The curves depict a feasible choice: the stage and endpoint constraints place the state variables on the integrated polynomial. If a collocation node is an endpoint, its stage state can be identified with the shared mesh variable.

Collect the independent state and control values across all intervals into the decision vector

z=col⁡ ⁣({xk}k=0N,{xk,j}k=0, j=1N−1, s,{u^k,r}k=0, r=0N−1, du).\mathbf z=\operatorname{col}\!\left( \{\mathbf x_k\}_{k=0}^{N}, \{\mathbf x_{k,j}\}_{k=0,\,j=1}^{N-1,\,s}, \{\widehat{\mathbf u}_{k,r}\}_{k=0,\,r=0}^{N-1,\,d_u} \right).

Here col⁡\operatorname{col} stacks the listed vectors into one column, omitting duplicate variables when a stage shares a mesh endpoint. Stage controls are evaluated using BB, and any chosen control-continuity equalities are added to the constraints. If tft_f is optimized, it is included in z\mathbf z as well. The resulting finite optimization problem has the form

minimize⁡zcf(xN,tf)+∑k=0N−1cksubject tostage equations,endpoint defects and continuity,boundary, path, and bound constraints.\begin{aligned} \underset{\mathbf{z}}{\operatorname{minimize}}\quad& c_f(\mathbf{x}_N,t_f)+\sum_{k=0}^{N-1}c_k\\ \text{subject to}\quad& \text{stage equations},\\ &\text{endpoint defects and continuity},\\ &\text{boundary, path, and bound constraints}. \end{aligned}

In the preceding chapter’s NLP notation, F(z)F(\mathbf z) is this scalar objective, H(z)=0H(\mathbf z)=\mathbf0 stacks the stage, endpoint, and boundary equalities, and G(z)≤0G(\mathbf z)\leq\mathbf0 stacks the sampled path and bound inequalities. The slope fk,j\mathbf f_{k,j} is a vector, distinct from FF.

The original path and bound constraints apply at every continuous time. This NLP imposes them at selected points, usually its support or collocation nodes. Feasibility at those nodes does not rule out a violation between them. Checking the polynomial between nodes and simulating the chosen control provide complementary diagnostics, developed below.

Every interval constraint touches only local stage variables and neighboring endpoint states. Consequently, most derivatives of the constraints with respect to the decision variables are zero. With variables ordered by time, the nonzero blocks lie near the diagonal of the Jacobian, the matrix of constraint derivatives. NLP solvers can exploit this sparse, block-banded pattern. The arrays AA and bb are numerical constants computed before optimization.

Equivalent differentiation form

The differentiation form uses the same collocation nodes τi\tau_i as the integration form. It changes which consistency condition is built into the representation and which must be enforced by a residual. The integration form constructs a curve whose nodal derivatives match the evaluated stage slopes, then constrains its values to match the stored stage states. The differentiation form constructs a curve from state-support values, evaluates its stage states directly on that curve, and constrains its derivatives to match the ODE.

To represent the same state polynomials, retain the same degree. The integral of the degree-(s−1)(s-1) slope polynomial has degree at most ss, so take d=sd=s and choose d+1d+1 support nodes σ0,…,σd\sigma_0,\ldots,\sigma_d for the state polynomial. Write ℓrstate\ell_r^{\mathrm{state}} for their cardinal functions to distinguish this support-node basis from the slope-node basis ℓj\ell_j. Denote the support values by x^k,r\widehat{\mathbf x}_{k,r}; these need not be the stage values xk,j\mathbf x_{k,j} at τj\tau_j:

xh(tk+hkτ)=∑r=0dx^k,rℓrstate(τ).\mathbf{x}_h(t_k+h_k\tau) =\sum_{r=0}^{d}\widehat{\mathbf x}_{k,r}\ell_r^{\mathrm{state}}(\tau).

At a collocation node τi\tau_i, the state is a weighted sum of its support values, and its derivative with respect to τ\tau is another weighted sum. The fixed arrays containing these weights are

Eir=ℓrstate(τi),Dir=(ℓrstate)′(τi).E_{ir}=\ell_r^{\mathrm{state}}(\tau_i), \qquad D_{ir}=(\ell_r^{\mathrm{state}})'(\tau_i).

The iith row of EE computes pk(τi)\mathbf p_k(\tau_i), the point on the state polynomial that we supply to the dynamics function. The iith row of DD computes pk′(τi)\mathbf p_k'(\tau_i), the tangent slope measured per unit of local time τ\tau. Both calculations use the same stored support states x^k,r\widehat{\mathbf x}_{k,r}; they do not introduce new decision variables. In particular, the stage state is defined as xk,i=pk(τi)=∑rEirx^k,r\mathbf x_{k,i}=\mathbf p_k(\tau_i)=\sum_r E_{ir}\widehat{\mathbf x}_{k,r}. State-value consistency therefore holds for every candidate support array; there is no separate state-matching residual at a stage.

To compare this tangent slope with the rate returned by f\mathbf f, both must be measured in physical time. A local-time increment Δτ\Delta\tau corresponds to a physical-time increment Δt=hkΔτ\Delta t=h_k\Delta\tau. We therefore divide the local-time slope by hkh_k to obtain the physical-time slope: this is the chain rule d/dt=(1/hk)d/dτd/dt=(1/h_k)d/d\tau used earlier. The remaining constraint sets the physical-time slope residual to zero:

rk,idiff=1hkpk′(τi)−f ⁣(pk(τi),uk,i,tk+hkτi)=0,∀ k=0,…,N−1,i=1,…,s.\begin{gathered} \mathbf r^{\mathrm{diff}}_{k,i} =\frac{1}{h_k}\mathbf p_k'(\tau_i) -\mathbf f\!\left(\mathbf p_k(\tau_i),\mathbf u_{k,i},t_k+h_k\tau_i\right) =\mathbf0,\\ \forall\,k=0,\ldots,N-1,\quad i=1,\ldots,s. \end{gathered}

Both slopes are evaluated at the same point on the state curve. Unlike state-value consistency, their equality is not built into the state interpolant. Writing these evaluations using E,DE,D and multiplying by hkh_k gives

∑r=0dDirx^k,r=hkf(∑r=0dEirx^k,r,uk,i,tk+hkτi).∀ k=0,…,N−1,i=1,…,s.\begin{gathered} \boxed{ \sum_{r=0}^{d}D_{ir}\widehat{\mathbf x}_{k,r} =h_k \mathbf{f}\left( \sum_{r=0}^{d}E_{ir}\widehat{\mathbf x}_{k,r}, \mathbf{u}_{k,i}, t_k+h_k\tau_i \right). }\\ \forall\,k=0,\ldots,N-1,\quad i=1,\ldots,s. \end{gathered}

The left side is the polynomial’s slope with respect to τ\tau; the right side is the ODE’s physical-time rate multiplied by hkh_k. The scalar quadratic in Figure 12 shows both calculations at a collocation node that is not a support node.

Three square support states define a quadratic. At a collocation node between the support nodes, E computes the value shown by an open circle and D computes the dashed tangent's slope. Dividing this slope by the interval length gives the physical-time rate that must match the ODE.

Figure 12:Evaluation and differentiation of the same state polynomial. The support states (1,2,4)(1,2,4) at σ=(0,12,1)\sigma=(0,\tfrac12,1) define pk(τ)=1+τ+2τ2p_k(\tau)=1+\tau+2\tau^2. At the highlighted collocation node τi=14\tau_i=\tfrac14, EE computes the state pk(τi)=118p_k(\tau_i)=\tfrac{11}{8}, while DD computes the tangent slope pk′(τi)=2p_k'(\tau_i)=2. With hk=2h_k=2, the physical-time slope is 2/2=12/2=1. For the dynamics x˙=u\dot x=u and stage control uk,i=1u_{k,i}=1, the ODE also returns 1, so the collocation constraint holds at this node. Taking tk=2t_k=2 places the node at physical time tk+hkτi=52t_k+h_k\tau_i=\tfrac52.

In matrix shorthand, all nodal constraints are DXk=hkFkD\mathsf X_k=h_k\mathsf F_k. Here Xk∈R(d+1)×n\mathsf X_k\in\mathbb R^{(d+1)\times n} has support states as rows, and Fk∈Rs×n\mathsf F_k\in\mathbb R^{s\times n} has stage slopes as rows; neither is a single state vector. Evaluation at the two ends connects this polynomial to the shared mesh states:

xk=∑r=0dℓrstate(0)x^k,r,xk+1=∑r=0dℓrstate(1)x^k,r.\mathbf{x}_{k}=\sum_{r=0}^{d}\ell_r^{\mathrm{state}}(0)\widehat{\mathbf x}_{k,r}, \qquad \mathbf{x}_{k+1}=\sum_{r=0}^{d}\ell_r^{\mathrm{state}}(1)\widehat{\mathbf x}_{k,r}.

When support and collocation nodes coincide, the cardinal property makes EE select the corresponding stored state directly. The slope-value and state-value forms describe the same polynomial construction; one integrates nodal slopes, while the other differentiates nodal states.

This implementation pattern is standard in direct collocation Kelly, 2017Andersson et al., 2019. The official CasADi direct-collocation example constructs fixed differentiation, endpoint, and quadrature arrays from Lagrange polynomials, while the NLP variables remain state and control values.

In the integration form, matching the evaluated stage slopes is built into the polynomial construction, while matching the stage states requires constraints. In the differentiation form, matching stage states to the curve is built into evaluation, while matching ODE slopes requires constraints. Both forms also connect the polynomial to the shared mesh endpoints. With the same collocation nodes, state degree, and control representation, their zero residuals describe the same collocation trajectories, although their residual values need not agree for an infeasible candidate. The node-to-NLP recipe for the differentiation form is developed in Constructing the differentiation form from its nodes.

Low-Order Transcriptions

Which familiar integration rules appear when the polynomial and node sets are reduced to their lowest-order choices?

The general construction becomes concrete when only one or two slope values are retained on each interval. These cases recover familiar integration formulas, but the formulas now appear as constraints inside an NLP. For the concrete problems below, fix the mesh and final time. The boundary map h(x0,xN,tN)=0\mathbf h(\mathbf x_0,\mathbf x_N,t_N)=\mathbf0 includes the prescribed initial state and any terminal conditions; g≤0\mathbf g\leq\mathbf0 includes path constraints and state/control bounds. Each displayed program specifies the points where those inequalities are imposed. Additional check points can be included, and a continuous replay still checks the result between nodes.

One slope value: explicit and implicit Euler

A single slope value determines a constant derivative on each interval. Choose a constant control uk,1\mathbf u_{k,1} on that interval as well: uh(tk+hkτ)=uk,1\mathbf u_h(t_k+h_k\tau)=\mathbf u_{k,1}. Different intervals may use different constants. The state is linear even though the control is constant. The following exercise applies the preceding construction to recover the two Euler methods. The solution gives a recipe that also extends to multiple slope nodes.

Solution to Exercise 1

The derivation follows five steps: choose where the ODE is evaluated, interpolate its slope, integrate that interpolant, match the stored endpoint, and integrate the cost rate with the same weights.

  1. Choose the stage and evaluate its slope. The left node uses the state, control, and time at the start of the interval; the right node uses those at its end:

    fk,1={f(xk,uk,1,tk),τ1=0,f(xk+1,uk,1,tk+1),τ1=1.\mathbf{f}_{k,1}=\begin{cases} \mathbf{f}(\mathbf{x}_k,\mathbf u_{k,1},t_k),&\tau_1=0,\\ \mathbf{f}(\mathbf{x}_{k+1},\mathbf u_{k,1},t_{k+1}),&\tau_1=1. \end{cases}
  2. Interpolate the physical-time derivative. With one node, the cardinal function is the constant ℓ1(τ)=1\ell_1(\tau)=1, since it must equal one at that node. Hence x˙h(tk+hkτ)=fk,1\dot{\mathbf{x}}_h(t_k+h_k\tau)=\mathbf{f}_{k,1} throughout the interval. Its full-interval integration weight is b1=∫01ℓ1(τ) dτ=1b_1=\int_0^1\ell_1(\tau)\,d\tau=1.

  3. Integrate from the left state. The reference-time derivative is pk′(τ)=hkfk,1\mathbf{p}_k'(\tau)=h_k\mathbf{f}_{k,1}. Integrating and imposing pk(0)=xk\mathbf{p}_k(0)=\mathbf{x}_k gives

    pk(τ)=xk+hkτfk,1.\mathbf{p}_k(\tau)=\mathbf{x}_k+h_k\tau \mathbf{f}_{k,1}.

    Thus the state polynomial has degree at most one. The factor hkh_k accounts for the physical duration of the interval.

  4. Match the stored right endpoint. Setting pk(1)=xk+1\mathbf{p}_k(1)=\mathbf{x}_{k+1} gives the defect xk+1−xk−hkfk,1=0\mathbf{x}_{k+1}-\mathbf{x}_k-h_k\mathbf{f}_{k,1}=\mathbf0. Substituting the two slope choices yields

    xk+1−xk−hkf(xk,uk,1,tk)=0(explicit Euler),xk+1−xk−hkf(xk+1,uk,1,tk+1)=0(implicit Euler).\begin{aligned} \mathbf{x}_{k+1}-\mathbf{x}_k-h_k\mathbf{f}(\mathbf{x}_k,\mathbf u_{k,1},t_k)&=\mathbf0 &&\text{(explicit Euler)},\\ \mathbf{x}_{k+1}-\mathbf{x}_k-h_k\mathbf{f}(\mathbf{x}_{k+1},\mathbf u_{k,1},t_{k+1})&=\mathbf0 &&\text{(implicit Euler)}. \end{aligned}

    In forward simulation, explicit Euler computes the next state from known left-endpoint data. Implicit Euler places the unknown next state inside f\mathbf{f}, so it generally requires solving an equation, which is nonlinear when the dynamics depend nonlinearly on that state. In direct transcription, both endpoints are optimization variables and either defect is imposed as an equality constraint.

  5. Apply the same integration rule to the cost rate. The single sampled cost rate is constant in the approximation, so its integral is that rate multiplied by hkh_k:

    ck={hkc(xk,uk,1,tk),τ1=0,hkc(xk+1,uk,1,tk+1),τ1=1.c_k=\begin{cases} h_kc(\mathbf{x}_k,\mathbf u_{k,1},t_k),&\tau_1=0,\\ h_kc(\mathbf{x}_{k+1},\mathbf u_{k,1},t_{k+1}),&\tau_1=1. \end{cases}

    These are the left- and right-endpoint approximations to the interval running-cost integral. They use the same stage as the corresponding dynamics constraint.

With additional slope nodes, the sequence stays the same. The constant cardinal function is replaced by several cardinal polynomials, and their integrals supply the weights in the stage equations, endpoint defect, and cost contribution.

The complete Euler NLP

Let θ=0\theta=0 for explicit Euler and θ=1\theta=1 for implicit Euler. Both methods optimize the mesh states x0,…,xN\mathbf x_0,\ldots,\mathbf x_N and the NN interval controls uk,1\mathbf u_{k,1}. There are no additional stage-state variables after identifying the sole stage with its endpoint:

min⁡{xk}k=0N, {uk,1}k=0N−1cf(xN,tN)+∑k=0N−1hkc(xk+θ,uk,1,tk+θ)subject toxk+1−xk−hkf(xk+θ,uk,1,tk+θ)=0,g(xk+θ,uk,1,tk+θ)≤0,k=0,…,N−1,h(x0,xN,tN)=0.\begin{aligned} \min_{\{\mathbf x_k\}_{k=0}^N,\,\{\mathbf u_{k,1}\}_{k=0}^{N-1}} \quad &c_f(\mathbf x_N,t_N) +\sum_{k=0}^{N-1}h_kc(\mathbf x_{k+\theta},\mathbf u_{k,1},t_{k+\theta})\\ \text{subject to}\quad &\mathbf x_{k+1}-\mathbf x_k -h_k\mathbf f(\mathbf x_{k+\theta},\mathbf u_{k,1},t_{k+\theta})=\mathbf0,\\ &\mathbf g(\mathbf x_{k+\theta},\mathbf u_{k,1},t_{k+\theta})\leq\mathbf0, \quad k=0,\ldots,N-1,\\ &\mathbf h(\mathbf x_0,\mathbf x_N,t_N)=\mathbf0. \end{aligned}

Here k+θk+\theta selects an endpoint, not an intermediate time. The path constraints use the same endpoint as the dynamics; bounds required at the other endpoint can be imposed there too. The two NLPs differ only in where the slope and cost rate are evaluated. Both are solved for the entire trajectory simultaneously.

Endpoint slope values: trapezoidal transcription

Use continuous piecewise-linear controls, with shared endpoint variables u0,…,uN\mathbf u_0,\ldots,\mathbf u_N. Choose the endpoint collocation nodes τ1=0\tau_1=0 and τ2=1\tau_2=1, and abbreviate the two ODE slopes by

fk=f(xk,uk,tk),fk+1=f(xk+1,uk+1,tk+1).\mathbf{f}_k=\mathbf{f}(\mathbf{x}_k,\mathbf{u}_k,t_k),\qquad \mathbf{f}_{k+1}=\mathbf{f}(\mathbf{x}_{k+1},\mathbf{u}_{k+1},t_{k+1}).

For this endpoint formula, label the cardinal functions by their locations, 0 and 1, rather than by the stage numbers 1 and 2:

ℓ0(τ)=1−τ,ℓ1(τ)=τ.\ell_0(\tau)=1-\tau,\qquad \ell_1(\tau)=\tau.

The derivative interpolant is the line joining the two slopes:

x˙h(tk+hkτ)=(1−τ)fk+τfk+1.\dot{\mathbf{x}}_h(t_k+h_k\tau) =(1-\tau)\mathbf{f}_k+\tau \mathbf{f}_{k+1}.

It equals fk\mathbf{f}_k at τ=0\tau=0 and fk+1\mathbf{f}_{k+1} at τ=1\tau=1. Integrating from the stored left state gives the continuous state approximation

xh(tk+hkτ)=xk+hk[(τ−τ22)fk+τ22fk+1].\mathbf{x}_h(t_k+h_k\tau) =\mathbf{x}_k+h_k\left[ \left(\tau-\frac{\tau^2}{2}\right)\mathbf{f}_k +\frac{\tau^2}{2}\mathbf{f}_{k+1} \right].

The state approximation is quadratic, even though an implementation may store only its endpoint states and slopes. Evaluating this polynomial at τ=1\tau=1 and equating it to the stored endpoint produces

xk+1−xk−hk2[f(xk,uk,tk)+f(xk+1,uk+1,tk+1)]=0.\boxed{ \mathbf{x}_{k+1}-\mathbf{x}_k -\frac{h_k}{2} \left[ \mathbf{f}(\mathbf{x}_k,\mathbf{u}_k,t_k) +\mathbf{f}(\mathbf{x}_{k+1},\mathbf{u}_{k+1},t_{k+1}) \right] =\mathbf0. }

The coefficients 1/21/2 are the integrals of the two linear cardinal functions. This is the trapezoidal defect derived in the opening example. Applying the same endpoint weights to the running cost gives

ck=hk2[c(xk,uk,tk)+c(xk+1,uk+1,tk+1)].c_k = \frac{h_k}{2} \left[ c(\mathbf{x}_k,\mathbf{u}_k,t_k) +c(\mathbf{x}_{k+1},\mathbf{u}_{k+1},t_{k+1}) \right].

The two endpoint costs are averaged and multiplied by the interval length. Integration increases the degree: a linear derivative interpolant produces a quadratic state interpolant. This construction also appears in Kelly (2017).

The complete trapezoidal NLP

The decision variables are the mesh states and controls. Write fk=f(xk,uk,tk)\mathbf f_k=\mathbf f(\mathbf x_k,\mathbf u_k,t_k) and ckrate=c(xk,uk,tk)c_k^{\mathrm{rate}}=c(\mathbf x_k,\mathbf u_k,t_k) for evaluated quantities, not additional optimization variables. Then the transcribed problem is

min⁡{xk,uk}k=0Ncf(xN,tN)+∑k=0N−1hk2(ckrate+ck+1rate)subject toxk+1−xk−hk2(fk+fk+1)=0,k=0,…,N−1,g(xk,uk,tk)≤0,k=0,…,N,h(x0,xN,tN)=0.\begin{aligned} \min_{\{\mathbf x_k,\mathbf u_k\}_{k=0}^{N}}\quad &c_f(\mathbf x_N,t_N) +\sum_{k=0}^{N-1}\frac{h_k}{2} \left(c_k^{\mathrm{rate}}+c_{k+1}^{\mathrm{rate}}\right)\\ \text{subject to}\quad &\mathbf x_{k+1}-\mathbf x_k -\frac{h_k}{2}(\mathbf f_k+\mathbf f_{k+1})=\mathbf0, \quad k=0,\ldots,N-1,\\ &\mathbf g(\mathbf x_k,\mathbf u_k,t_k)\leq\mathbf0, \quad k=0,\ldots,N,\\ &\mathbf h(\mathbf x_0,\mathbf x_N,t_N)=\mathbf0. \end{aligned}

The state between endpoints is the quadratic obtained above, while the control is linear. Shared mesh states and controls connect adjacent pieces; no separate continuity equations are needed in this representation.

Hermite--Simpson Transcription

Can midpoint state and slope information raise the transcription order without requiring a high-degree polynomial over the whole horizon?

Hermite--Simpson extends the trapezoidal construction by adding the midpoint state xk+12\mathbf{x}_{k+\frac12}. Retain the same piecewise-linear control, so its midpoint value is already determined:

uk+12=uk+uk+12.\mathbf u_{k+\frac12}=\frac{\mathbf u_k+\mathbf u_{k+1}}{2}.

Evaluate the midpoint ODE slope using that control:

fk+12=f(xk+12,uk+12,tk+hk2).\mathbf{f}_{k+\frac12}=\mathbf{f}\left( \mathbf{x}_{k+\frac12},\mathbf{u}_{k+\frac12},t_k+\frac{h_k}{2} \right).

The notation xk+12\mathbf x_{k+\frac12} and uk+12\mathbf u_{k+\frac12} denotes values at physical time tk+hk/2t_k+h_k/2, not another mesh endpoint.

The three stage nodes are τ1=0\tau_1=0, τ2=12\tau_2=\tfrac12, and τ3=1\tau_3=1. We label their cardinal functions 0,m,10,m,1 for left endpoint, midpoint, and right endpoint. The derivative interpolant is the quadratic built from the following three cardinal functions:

ℓ0(τ)=2τ2−3τ+1,ℓm(τ)=4τ(1−τ),ℓ1(τ)=2τ2−τ.\ell_0(\tau)=2\tau^2-3\tau+1,\qquad \ell_m(\tau)=4\tau(1-\tau),\qquad \ell_1(\tau)=2\tau^2-\tau.

Integrating each function over the full reference interval gives

b0=16,bm=46,b1=16.b_0=\frac16,\qquad b_m=\frac46,\qquad b_1=\frac16.

These are the familiar Simpson quadrature weights. Substituting them into the general endpoint equation produces the Simpson defect:

xk+1−xk−hk6(fk+4fk+12+fk+1)=0.\boxed{ \mathbf{x}_{k+1}-\mathbf{x}_k -\frac{h_k}{6} \left(\mathbf{f}_k+4\mathbf{f}_{k+\frac12}+\mathbf{f}_{k+1}\right) =\mathbf0. }

The midpoint state must also lie on the cubic obtained by integrating the quadratic derivative. Integration only to τ=12\tau=\tfrac12 gives

xk+12=xk+hk24(5fk+8fk+12−fk+1).\mathbf{x}_{k+\frac12} =\mathbf{x}_k+\frac{h_k}{24} \left(5\mathbf{f}_k+8\mathbf{f}_{k+\frac12}-\mathbf{f}_{k+1}\right).

This form still contains the midpoint slope. The endpoint defect gives

4fk+12=6hk(xk+1−xk)−fk−fk+1.4\mathbf{f}_{k+\frac12} =\frac{6}{h_k}(\mathbf{x}_{k+1}-\mathbf{x}_k)-\mathbf{f}_k-\mathbf{f}_{k+1}.

Substituting this expression into the midpoint equation and collecting the endpoint states and slopes yields

xk+12=xk+xk+12+hk8(fk−fk+1).\boxed{ \mathbf{x}_{k+\frac12} =\frac{\mathbf{x}_k+\mathbf{x}_{k+1}}{2} +\frac{h_k}{8}\left(\mathbf{f}_k-\mathbf{f}_{k+1}\right). }

The midpoint state relation and the definition of fk+12\mathbf{f}_{k+\frac12} together enforce the ODE at the midpoint. The resulting state interpolant is cubic because it integrates a quadratic derivative. The name reflects its two ingredients: the state is a cubic Hermite interpolant determined by state and slope information, and its endpoint defect uses Simpson weights.

The complete Hermite--Simpson NLP

With linear controls, optimize mesh states, mesh controls, and midpoint states. For q=kq=k or k+12k+\tfrac12, abbreviate fq=f(xq,uq,tq)\mathbf f_q=\mathbf f(\mathbf x_q,\mathbf u_q,t_q) and cqrate=c(xq,uq,tq)c_q^{\mathrm{rate}}=c(\mathbf x_q,\mathbf u_q,t_q), with tk+12=tk+hk/2t_{k+\frac12}=t_k+h_k/2 and the midpoint control given by the endpoint average. The complete program is

min⁡{xk,uk}k=0N, {xk+12}k=0N−1cf(xN,tN)+∑k=0N−1hk6(ckrate+4ck+12rate+ck+1rate)subject toxk+1−xk−hk6(fk+4fk+12+fk+1)=0,xk+12−xk+xk+12−hk8(fk−fk+1)=0,k=0,…,N−1,g(xq,uq,tq)≤0,q∈{0,12,1,32,…,N},h(x0,xN,tN)=0.\begin{aligned} \min_{\{\mathbf x_k,\mathbf u_k\}_{k=0}^{N},\, \{\mathbf x_{k+\frac12}\}_{k=0}^{N-1}}\quad &c_f(\mathbf x_N,t_N) +\sum_{k=0}^{N-1}\frac{h_k}{6} \left(c_k^{\mathrm{rate}}+4c_{k+\frac12}^{\mathrm{rate}}+c_{k+1}^{\mathrm{rate}}\right)\\ \text{subject to}\quad &\mathbf x_{k+1}-\mathbf x_k -\frac{h_k}{6}(\mathbf f_k+4\mathbf f_{k+\frac12}+\mathbf f_{k+1})=\mathbf0,\\ &\mathbf x_{k+\frac12}-\frac{\mathbf x_k+\mathbf x_{k+1}}{2} -\frac{h_k}{8}(\mathbf f_k-\mathbf f_{k+1})=\mathbf0, \quad k=0,\ldots,N-1,\\ &\mathbf g(\mathbf x_q,\mathbf u_q,t_q)\leq\mathbf0, \quad q\in\{0,\tfrac12,1,\tfrac32,\ldots,N\},\\ &\mathbf h(\mathbf x_0,\mathbf x_N,t_N)=\mathbf0. \end{aligned}

An alternative is to optimize the midpoint control independently. The three control values then define a quadratic on each interval; the midpoint-average relation is removed, and uk+12\mathbf u_{k+\frac12} joins the decision vector. The two state equations and Simpson cost weights remain the same. This changes the admissible controls, so it can change the optimizer’s solution.

The resulting representations can be compared directly. All degrees are upper bounds on each interval; they are not claims about convergence order.

Scheme used hereState degreeControl degreeIndependent values beyond mesh states
Explicit or implicit Euler10One control per interval
Trapezoidal21Shared endpoint controls
Hermite--Simpson, linear control31Shared endpoint controls and midpoint states
Hermite--Simpson, quadratic control32Shared endpoint controls, midpoint states, and midpoint controls

Constructing a Scheme from Its Nodes

How can a program generate these transcriptions from node choices without hand-deriving a new set of defect equations each time?

The same construction generates every scheme in the preceding table. Its inputs are the collocation nodes, the control support nodes, and a choice about control continuity. The number of collocation nodes determines the degree of the slope interpolant and hence the state polynomial. The control support nodes determine the control polynomial independently.

This recipe covers polynomial collocation obtained by interpolating slopes at distinct nodes and integrating them. It includes Gauss, Radau, and Lobatto collocation as well as the low-order examples. An arbitrary Runge--Kutta tableau need not come from such nodes, so specifying nodes does not generate every possible integration method.

The first recipe uses the integration form. Its differentiation-form counterpart in Constructing the differentiation form from its nodes also takes state-support nodes as input, because it stores values of the state polynomial rather than constructing that polynomial from its slopes.

The stage controls, slopes, and cost rates in step 3 are expressions in the decision variables. They are recomputed as the optimizer changes those variables; A,b,BA,b,B remain fixed. Automatic differentiation can supply the derivatives of the assembled expressions. If endpoint stages are retained as separate variables, the stage and endpoint equations connect them to the mesh states. Eliminating those copies produces the smaller low-order programs above; keeping both the copies and their defining equations is also valid. Identifying a copy with its endpoint requires removing the resulting duplicate or identically zero equation.

The demo Direct Collocation for a Pendulum Swing-Up carries out these five steps for a nonlinear problem with the two-stage Radau nodes. It computes AA and bb by hand, reproduces them in code, assembles FF and HH, solves the resulting NLP with SciPy, and checks the answer by simulation. Each step is accompanied by the corresponding lines of Python.

A reusable coefficient generator and residual evaluator

The full Python source on GitHub contains the coefficient generator and NLP residual evaluator. Its integration step uses closed-form polynomial calculus, rather than a numerical quadrature routine that samples a function to estimate its integral. To see what the code computes, return to the third slope basis function for nodes (0,12,1)(0,\tfrac12,1) in Figure 9:

ℓ3(τ)=2τ2−τ.\ell_3(\tau)=2\tau^2-\tau.

Integrating a term aτma\tau^m increases its exponent by one and divides its coefficient by that new exponent. Applying this rule to both terms gives an antiderivative

L3(τ)=23τ3−12τ2.L_3(\tau)=\frac23\tau^3-\frac12\tau^2.

Differentiating L3L_3 returns ℓ3\ell_3. We have chosen the integration constant to be zero; any other constant would cancel when evaluating a definite integral. The fundamental theorem of calculus now supplies both the full-interval weight and the midpoint weight:

b3=L3(1)−L3(0)=23−12=16,A23=L3(12)−L3(0)=112−18=−124.\begin{aligned} b_3 &=L_3(1)-L_3(0)=\frac23-\frac12=\frac16,\\ A_{23} &=L_3(\tfrac12)-L_3(0)=\frac1{12}-\frac18=-\frac1{24}. \end{aligned}

Only the upper limit changes between these calculations. The negative midpoint weight agrees with the signed area under ℓ3\ell_3 in the figure. Thus computing either weight requires an antiderivative and two polynomial evaluations, without approximating the area by small time steps.

The implementation represents a polynomial by its coefficients in increasing order of power. For ℓ3\ell_3, the array is [0,−1,2][0,-1,2]: the entries multiply 1,τ,τ21,\tau,\tau^2. Integration and differentiation transform this array into the coefficient arrays of new polynomials:

[0,−1,2]→integrate[0,0,−12,23],[0,−1,2]→differentiate[−1,4].\begin{aligned} [0,-1,2]&\xrightarrow{\text{integrate}} [0,0,-\tfrac12,\tfrac23],\\ [0,-1,2]&\xrightarrow{\text{differentiate}}[-1,4]. \end{aligned}

The first result represents L3L_3; the second represents ℓ3′(τ)=4τ−1\ell_3'(\tau)=4\tau-1. More generally, integrating a coefficient array [a0,a1,…,ad][a_0,a_1,\ldots,a_d] produces [C,a0,a1/2,…,ad/(d+1)][C,a_0,a_1/2,\ldots,a_d/(d+1)], where CC is the integration constant. Differentiating it produces [a1,2a2,…,dad][a_1,2a_2,\ldots,d a_d]. These operations require only arithmetic on the stored coefficients, not a symbolic algebra system. NumPy’s polynomial integration method implements the antiderivative operation:

from numpy.polynomial import Polynomial

ell3 = Polynomial([0, -1, 2])  # 2*tau**2 - tau
L3 = ell3.integ()             # (2/3)*tau**3 - (1/2)*tau**2
dell3 = ell3.deriv()          # 4*tau - 1

b3 = L3(1.0) - L3(0.0)       # 1/6, up to floating-point error
A23 = L3(0.5) - L3(0.0)      # -1/24, up to floating-point error

The generator below applies this procedure to every slope basis function. It first constructs each cardinal polynomial by multiplying its linear factors. Calling .integ() then constructs its antiderivative; evaluating that polynomial at the stage nodes and subtracting its value at zero supplies a column of AA. Evaluation at 1 supplies the corresponding entry of bb. Direct evaluation of the control basis functions supplies BB. The same coefficient differentiation rule, followed by evaluation at the collocation nodes, constructs DD from the state basis in the differentiation form. It does not estimate a derivative using a small finite-difference step.

These calculations occur once for the chosen node sets. During an NLP evaluation, the current states and controls supply new slopes fk,j\mathbf f_{k,j}, but the coefficients stay fixed. At that point, “integrating the interpolated slopes” means computing the weighted sums hk∑jAijfk,jh_k\sum_j A_{ij}\mathbf f_{k,j} or hk∑jbjfk,jh_k\sum_j b_j\mathbf f_{k,j}. The polynomial integral is exact in exact arithmetic; floating-point computation introduces roundoff, and replacing the nonlinear ODE by a slope interpolant still introduces a separate discretization error. Closed-form integration of the interpolant does not give a closed-form solution of the original ODE.

The following function computes A,b,BA,b,B for any distinct collocation and control support nodes:

collocation_transcription.py
def make_rule(nodes, control_nodes):
    """Precompute integration weights and control evaluation weights.

    A[i,j] = integral from 0 to nodes[i] of slope basis j.
    b[j] = integral from 0 to 1 of slope basis j.
    B[j,r] = control basis r evaluated at collocation node j.
    """
    slope_basis = cardinal_polynomials(nodes)
    control_basis = cardinal_polynomials(control_nodes)
    nodes = np.asarray(nodes, dtype=float)
    antiderivatives = [polynomial.integ() for polynomial in slope_basis]
    A = np.column_stack([p(nodes) - p(0.0) for p in antiderivatives])
    b = np.array([p(1.0) - p(0.0) for p in antiderivatives])
    B = np.column_stack([p(nodes) for p in control_basis])
    return CollocationRule(
        nodes=nodes,
        control_nodes=np.asarray(control_nodes, dtype=float),
        A=A,
        b=b,
        B=B,
        control_left=np.array([p(0.0) for p in control_basis]),
        control_right=np.array([p(1.0) for p in control_basis]),
    )

For example, the choices below recover the four low-order constructions:

from collocation_transcription import make_rule

explicit_euler = make_rule([0.0], [0.0])
implicit_euler = make_rule([1.0], [0.0])
trapezoidal = make_rule([0.0, 1.0], [0.0, 1.0])
hermite_simpson = make_rule([0.0, 0.5, 1.0], [0.0, 1.0])
# An independent midpoint control changes B, not A or b:
hermite_simpson_quadratic_u = make_rule([0.0, 0.5, 1.0], [0.0, 0.5, 1.0])

The implicit-Euler control support node can be 0 even though its collocation node is 1: a constant polynomial has the same value at both. For Hermite--Simpson with linear controls, the generated arrays are

A=[0005/241/3−1/241/62/31/6],b=[1/62/31/6],B=[101/21/201].A=\begin{bmatrix} 0&0&0\\ 5/24&1/3&-1/24\\ 1/6&2/3&1/6 \end{bmatrix},\qquad b=\begin{bmatrix}1/6\\2/3\\1/6\end{bmatrix},\qquad B=\begin{bmatrix}1&0\\1/2&1/2\\0&1\end{bmatrix}.

The middle row of AA gives the midpoint stage equation before elimination; the middle row of BB gives the endpoint-average control. Supplying three control support nodes instead makes BB the identity, allowing an independent midpoint control while leaving the state construction unchanged. The experiments of the pendulum demo change the collocation nodes in the same way and compare the accuracy of the resulting schemes on a nonlinear problem.

The same file’s residual evaluator, transcribed_problem, evaluates F,G,HF,G,H from arrays of mesh states, stage states, and control support values. Its default allows control jumps; continuous_control=True adds the endpoint-matching equations used by the linear-control examples. A solver wrapper flattens these arrays into z\mathbf z and reshapes each candidate before evaluation. The NumPy evaluator can be used with finite differences; an automatic-differentiation implementation uses the same array operations in its chosen backend. The returned inequality residuals use G≤0G\leq0; negate them for a solver interface that expects nonnegative residuals.

Download the coefficient generator and NLP evaluator

Explicit polynomial coefficients keep this implementation readable at modest degrees. At high degrees, use numerically stable basis evaluation and integration routines; the NLP assembly remains the same.

Constructing the differentiation form from its nodes

Both recipes retain collocation nodes where the reconstructed state must satisfy the ODE. The integration-form recipe interpolates evaluated stage slopes and integrates them; its residuals then match the resulting state values to the stored stage states. The differentiation form interpolates stored state-support values and evaluates the stage states on that curve; its residuals then match the curve’s derivatives to the ODE slopes. Choosing a form therefore changes the representation and residuals, not whether the scheme has collocation nodes.

For a concrete example, retain the trapezoidal rule’s two collocation nodes τ1=0\tau_1=0 and τ2=1\tau_2=1. Its slope interpolant is linear, so its state polynomial is quadratic. Storing only the two endpoint states and linearly interpolating between them would give a constant derivative. Enforcing that derivative at both endpoints would require the two ODE slopes to be equal, which the trapezoidal rule does not require. To represent the same quadratic as the integration form, choose three state-support nodes, for example σ=(0,12,1)\sigma=(0,\tfrac12,1).

The cardinal polynomials for these support nodes are 2τ2−3τ+12\tau^2-3\tau+1, 4τ−4τ24\tau-4\tau^2, and 2τ2−τ2\tau^2-\tau. Evaluating them and their derivatives at the two collocation nodes gives

E=[100001],D=[−34−11−43].E=\begin{bmatrix}1&0&0\\0&0&1\end{bmatrix}, \qquad D=\begin{bmatrix}-3&4&-1\\1&-4&3\end{bmatrix}.

There are three columns because each polynomial operation uses three support values, but only two rows because the ODE is enforced at two collocation nodes. Identify the endpoint support values with xk\mathbf x_k and xk+1\mathbf x_{k+1}, and call the midpoint support value x^k,1\widehat{\mathbf x}_{k,1}. The two differentiation constraints are

−3xk+4x^k,1−xk+1=hkfk,1,xk−4x^k,1+3xk+1=hkfk,2.\begin{aligned} -3\mathbf x_k+4\widehat{\mathbf x}_{k,1}-\mathbf x_{k+1} &=h_k\mathbf f_{k,1},\\ \mathbf x_k-4\widehat{\mathbf x}_{k,1}+3\mathbf x_{k+1} &=h_k\mathbf f_{k,2}. \end{aligned}

The slopes on the right are evaluated at the left and right endpoints, respectively. Adding the equations eliminates the midpoint support value. Dividing by two recovers the trapezoidal endpoint constraint:

xk+1−xk=hk2(fk,1+fk,2).\mathbf x_{k+1}-\mathbf x_k =\frac{h_k}{2}(\mathbf f_{k,1}+\mathbf f_{k,2}).

Substituting this relation into either differentiation constraint also determines the midpoint support value:

x^k,1=xk+xk+12+hk8(fk,1−fk,2).\widehat{\mathbf x}_{k,1} =\frac{\mathbf x_k+\mathbf x_{k+1}}{2} +\frac{h_k}{8}(\mathbf f_{k,1}-\mathbf f_{k,2}).

The midpoint value records the curvature of the quadratic. Storing it does not add an ODE constraint at the midpoint. A support node specifies where a polynomial value is stored; a collocation node specifies where its derivative must match the dynamics.

The same degree count applies to any number of stages. With ss distinct collocation nodes, the integration form constructs a state polynomial of degree at most ss. Its differentiation-form counterpart therefore uses s+1s+1 distinct state-support nodes σ0,…,σs\sigma_0,\ldots,\sigma_s. They need not coincide with the collocation nodes or include the interval endpoints. Changing these support locations changes the coordinates used to describe the polynomial, not the polynomial space. Control support nodes still specify a separate polynomial and can be chosen independently.

The factor hkh_k in the slope residual converts the ODE’s physical-time rate to a local-time derivative, as in Figure 12. The arrays are computed for τ∈[0,1]\tau\in[0,1], so the same arrays work on intervals of different lengths. Mesh refinement changes the interval lengths and variables, but does not require new arrays unless the local node sets change. The last section of the pendulum demo, The Same Construction with Derivatives, carries out this algorithm with the two-stage Radau nodes and shows that it returns the same trajectory as the integration form.

This algorithm keeps mesh states and support states as separate variables and connects them by equations. If a support node is an endpoint, its value can instead be identified with the corresponding mesh state, as in the quadratic example. Then omit its endpoint equality: after identification, that equality would only say xk−xk=0\mathbf x_k-\mathbf x_k=\mathbf0. If neither endpoint is a support node, retain both endpoint evaluations.

Differentiating the state does not remove integration from the objective. The running cost is still sampled at the ss collocation nodes and integrated with the same weights bb as in the integration form. Those weights integrate the collocation-node basis ℓi\ell_i, not the state-support basis ℓrstate\ell_r^{\mathrm{state}}. For the quadratic example, the cost contribution is the trapezoidal expression hk(ck,1+ck,2)/2h_k(c_{k,1}+c_{k,2})/2, even though three values represent the state.

Equivalence with the integration construction

At a feasible point, the differentiation constraints specify the values of pk′\mathbf p_k' at all ss collocation nodes. Because this derivative has degree at most s−1s-1, its values at those nodes determine it everywhere:

pk′(τ)=hk∑j=1sfk,jℓj(τ),∀τ∈[0,1].\mathbf p_k'(\tau)=h_k\sum_{j=1}^{s}\mathbf f_{k,j}\ell_j(\tau), \qquad \forall\tau\in[0,1].

The left endpoint equation supplies pk(0)=xk\mathbf p_k(0)=\mathbf x_k. Applying the fundamental theorem of calculus therefore gives

pk(τ)=xk+hk∑j=1sfk,j∫0τℓj(ξ) dξ,∀τ∈[0,1].\mathbf p_k(\tau)=\mathbf x_k +h_k\sum_{j=1}^{s}\mathbf f_{k,j}\int_0^\tau\ell_j(\xi)\,d\xi, \qquad \forall\tau\in[0,1].

Evaluation at τi\tau_i recovers the integration form’s stage equation; evaluation at 1, together with the right endpoint equation, recovers its endpoint defect. No additional integral defect is needed in the differentiation-form NLP. Conversely, the state polynomial constructed by the integration form has these nodal derivatives and can be represented by its values at any s+1s+1 distinct state-support nodes.

Thus the two formulations describe the same feasible polynomial trajectories when they use the same collocation nodes, degree, and control space. Using the same cost weights and constraint-sampling points also gives the same discrete objective and path constraints. Their decision arrays and residuals differ, so their numerical conditioning need not be identical.

A differentiation-form coefficient generator and residual evaluator

Constructing DD uses the coefficient differentiation introduced in A reusable coefficient generator and residual evaluator. For example, the first state basis function in the quadratic example is ℓ0state(τ)=1−3τ+2τ2\ell_0^{\mathrm{state}}(\tau)=1-3\tau+2\tau^2. Differentiation transforms its coefficient array [1,−3,2][1,-3,2] into [−3,4][-3,4]. Evaluating the resulting polynomial −3+4τ-3+4\tau at (0,1)(0,1) gives (−3,1)(-3,1), the first column of DD. NumPy’s polynomial derivative method performs this coefficient transformation. No small time increment or finite-difference estimate is involved.

The following generator uses the same cardinal_polynomials helper as make_rule. It evaluates each state basis function to form a column of EE, and evaluates its derivative to form the corresponding column of DD. It integrates the collocation-node basis only to obtain the cost weights:

collocation_transcription.py
def make_differentiation_rule(nodes, state_nodes, control_nodes):
    """Precompute the state-value form of a polynomial collocation rule.

    With s collocation nodes, use s+1 state nodes for a degree-s state.
    E[i,r] = state basis r evaluated at collocation node i.
    D[i,r] = its derivative with respect to local time, evaluated at node i.
    b integrates cost rates at collocation nodes, not state-support nodes.
    """
    state_basis = cardinal_polynomials(state_nodes)
    cost_basis = cardinal_polynomials(nodes)
    control_basis = cardinal_polynomials(control_nodes)
    nodes = np.asarray(nodes, dtype=float)
    if len(state_basis) != len(nodes) + 1:
        raise ValueError("s collocation nodes require s+1 state-support nodes")
    E = np.column_stack([p(nodes) for p in state_basis])
    D = np.column_stack([p.deriv()(nodes) for p in state_basis])
    antiderivatives = [p.integ() for p in cost_basis]
    b = np.array([p(1.0) - p(0.0) for p in antiderivatives])
    B = np.column_stack([p(nodes) for p in control_basis])
    return DifferentiationRule(
        nodes=nodes,
        state_nodes=np.asarray(state_nodes, dtype=float),
        control_nodes=np.asarray(control_nodes, dtype=float),
        E=E, D=D,
        state_left=np.array([p(0.0) for p in state_basis]),
        state_right=np.array([p(1.0) for p in state_basis]),
        b=b, B=B,
        control_left=np.array([p(0.0) for p in control_basis]),
        control_right=np.array([p(1.0) for p in control_basis]),
    )

The four low-order schemes can now be generated in differentiation form:

from collocation_transcription import make_differentiation_rule

explicit_euler = make_differentiation_rule([0], [0, 1], [0])
implicit_euler = make_differentiation_rule([1], [0, 1], [0])
trapezoidal = make_differentiation_rule([0, 1], [0, 0.5, 1], [0, 1])
hermite_simpson = make_differentiation_rule(
    [0, 0.5, 1], [0, 1/3, 2/3, 1], [0, 1]
)

The argument order is collocation nodes, state-support nodes, and control-support nodes. In the Hermite--Simpson call, four state-support values represent a cubic. The midpoint stage state is evaluated by EE from those values, since 1/21/2 is not a state-support node in this choice. Using only the three collocation nodes as state supports would restrict the state to a quadratic and would not reproduce the same method.

The fundamental theorem of calculus also supplies checks on the fixed arrays, before any dynamics function or NLP solver is involved. Subtracting the left endpoint evaluation from each row of EE must give the same operator as integrating the derivative with AA. Similarly, the difference of endpoint evaluations must equal full-interval integration with bb:

import numpy as np
from collocation_transcription import make_rule

rule = hermite_simpson
integral = make_rule(rule.nodes, rule.control_nodes)
np.testing.assert_allclose(
    rule.E - rule.state_left, integral.A @ rule.D, atol=1e-12
)
np.testing.assert_allclose(
    rule.state_right - rule.state_left, rule.b @ rule.D, atol=1e-12
)

In the first check, NumPy subtracts state_left from every row of E. These identities hold for any degree-ss state polynomial, not just one that satisfies the ODE. The tolerances allow for floating-point roundoff in constructing the coefficients.

The residual evaluator differentiation_problem takes mesh states with shape (N+1,n)(N+1,n), state-support values with shape (N,s+1,n)(N,s+1,n), and control-support values with shape (N,du+1,m)(N,d_u+1,m). For each candidate decision vector, E @ x_support[k] supplies the stage states and D @ x_support[k] - width * slopes supplies the slope residuals. The two endpoint evaluations connect the polynomial to the mesh states. The cost, boundary, path, and optional control-continuity conventions match transcribed_problem.

Inspect the complete module and both residual evaluators
collocation_transcription.py
"""Construct polynomial collocation rules and evaluate their finite NLP.

The routines use explicit polynomial coefficients for teaching and modest
degrees. They do not select a mesh, scale variables, or choose an NLP solver.
"""

from dataclasses import dataclass

import numpy as np
from numpy.polynomial import Polynomial


def cardinal_polynomials(nodes):
    """Return the Lagrange basis for distinct nodes in [0, 1]."""
    nodes = np.asarray(nodes, dtype=float)
    if nodes.ndim != 1 or nodes.size == 0:
        raise ValueError("nodes must be a nonempty one-dimensional array")
    if not np.all(np.isfinite(nodes)) or np.any((nodes < 0) | (nodes > 1)):
        raise ValueError("nodes must be finite and lie in [0, 1]")
    if np.unique(nodes).size != nodes.size:
        raise ValueError("nodes must be distinct")
    basis = []
    for j, node in enumerate(nodes):
        polynomial = Polynomial([1.0])
        for m, other in enumerate(nodes):
            if m != j:
                polynomial *= Polynomial([-other, 1.0]) / (node - other)
        basis.append(polynomial)
    return basis


@dataclass
class CollocationRule:
    nodes: np.ndarray
    control_nodes: np.ndarray
    A: np.ndarray
    b: np.ndarray
    B: np.ndarray
    control_left: np.ndarray
    control_right: np.ndarray


def make_rule(nodes, control_nodes):
    """Precompute integration weights and control evaluation weights.

    A[i,j] = integral from 0 to nodes[i] of slope basis j.
    b[j] = integral from 0 to 1 of slope basis j.
    B[j,r] = control basis r evaluated at collocation node j.
    """
    slope_basis = cardinal_polynomials(nodes)
    control_basis = cardinal_polynomials(control_nodes)
    nodes = np.asarray(nodes, dtype=float)
    antiderivatives = [polynomial.integ() for polynomial in slope_basis]
    A = np.column_stack([p(nodes) - p(0.0) for p in antiderivatives])
    b = np.array([p(1.0) - p(0.0) for p in antiderivatives])
    B = np.column_stack([p(nodes) for p in control_basis])
    return CollocationRule(
        nodes=nodes,
        control_nodes=np.asarray(control_nodes, dtype=float),
        A=A,
        b=b,
        B=B,
        control_left=np.array([p(0.0) for p in control_basis]),
        control_right=np.array([p(1.0) for p in control_basis]),
    )


def transcribed_problem(
    rule, mesh, x_mesh, x_stage, u_support,
    dynamics, running_cost, terminal_cost, boundary, path=None,
    *, continuous_control=False,
):
    """Return objective F, inequalities G <= 0, and equalities H = 0.

    x_mesh: (N+1, n); x_stage: (N, s, n); u_support: (N, d_u+1, m).
    dynamics(x,u,t) returns n slopes; running_cost(x,u,t) returns a scalar.
    terminal_cost(x_final,t_final) returns a scalar.
    boundary(x_initial,x_final,t_final) returns equality residuals, including
    the prescribed initial condition. path(x,u,t), if supplied, returns
    inequality residuals sampled at the collocation stages only.
    """
    mesh = np.asarray(mesh, dtype=float)
    x_mesh, x_stage, u_support = map(
        lambda a: np.asarray(a, dtype=float), (x_mesh, x_stage, u_support)
    )
    if mesh.ndim != 1 or len(mesh) < 2 or not np.all(np.isfinite(mesh)):
        raise ValueError("mesh must be a finite one-dimensional array")
    widths = np.diff(mesh)
    if np.any(widths <= 0):
        raise ValueError("mesh times must be strictly increasing")
    N, s, r = len(widths), len(rule.nodes), len(rule.control_nodes)
    if x_mesh.ndim != 2 or x_mesh.shape[0] != N + 1:
        raise ValueError("x_mesh must have shape (N+1, n)")
    if x_stage.shape != (N, s, x_mesh.shape[1]):
        raise ValueError("x_stage must have shape (N, s, n)")
    if u_support.ndim != 3 or u_support.shape[:2] != (N, r):
        raise ValueError("u_support must have shape (N, d_u+1, m)")

    objective = float(terminal_cost(x_mesh[-1], mesh[-1]))
    equalities = [np.asarray(boundary(x_mesh[0], x_mesh[-1], mesh[-1])).ravel()]
    inequalities = []
    for k, width in enumerate(widths):
        stage_times = mesh[k] + width * rule.nodes
        stage_controls = rule.B @ u_support[k]
        slopes = np.stack([
            dynamics(x, u, t)
            for x, u, t in zip(x_stage[k], stage_controls, stage_times)
        ])
        rates = np.array([
            running_cost(x, u, t)
            for x, u, t in zip(x_stage[k], stage_controls, stage_times)
        ])
        equalities.append((x_stage[k] - x_mesh[k] - width * rule.A @ slopes).ravel())
        equalities.append(x_mesh[k + 1] - x_mesh[k] - width * rule.b @ slopes)
        objective += width * rule.b @ rates
        if path is not None:
            inequalities.extend(
                np.asarray(path(x, u, t)).ravel()
                for x, u, t in zip(x_stage[k], stage_controls, stage_times)
            )
        if continuous_control and k + 1 < N:
            equalities.append(
                rule.control_right @ u_support[k]
                - rule.control_left @ u_support[k + 1]
            )
    G = np.concatenate(inequalities) if inequalities else np.empty(0)
    return float(objective), G, np.concatenate(equalities)


@dataclass
class DifferentiationRule:
    nodes: np.ndarray
    state_nodes: np.ndarray
    control_nodes: np.ndarray
    E: np.ndarray
    D: np.ndarray
    state_left: np.ndarray
    state_right: np.ndarray
    b: np.ndarray
    B: np.ndarray
    control_left: np.ndarray
    control_right: np.ndarray


def make_differentiation_rule(nodes, state_nodes, control_nodes):
    """Precompute the state-value form of a polynomial collocation rule.

    With s collocation nodes, use s+1 state nodes for a degree-s state.
    E[i,r] = state basis r evaluated at collocation node i.
    D[i,r] = its derivative with respect to local time, evaluated at node i.
    b integrates cost rates at collocation nodes, not state-support nodes.
    """
    state_basis = cardinal_polynomials(state_nodes)
    cost_basis = cardinal_polynomials(nodes)
    control_basis = cardinal_polynomials(control_nodes)
    nodes = np.asarray(nodes, dtype=float)
    if len(state_basis) != len(nodes) + 1:
        raise ValueError("s collocation nodes require s+1 state-support nodes")
    E = np.column_stack([p(nodes) for p in state_basis])
    D = np.column_stack([p.deriv()(nodes) for p in state_basis])
    antiderivatives = [p.integ() for p in cost_basis]
    b = np.array([p(1.0) - p(0.0) for p in antiderivatives])
    B = np.column_stack([p(nodes) for p in control_basis])
    return DifferentiationRule(
        nodes=nodes,
        state_nodes=np.asarray(state_nodes, dtype=float),
        control_nodes=np.asarray(control_nodes, dtype=float),
        E=E, D=D,
        state_left=np.array([p(0.0) for p in state_basis]),
        state_right=np.array([p(1.0) for p in state_basis]),
        b=b, B=B,
        control_left=np.array([p(0.0) for p in control_basis]),
        control_right=np.array([p(1.0) for p in control_basis]),
    )


def differentiation_problem(
    rule, mesh, x_mesh, x_support, u_support,
    dynamics, running_cost, terminal_cost, boundary, path=None,
    *, continuous_control=False,
):
    """Return F, G <= 0, H = 0 for the state-value formulation.

    x_mesh: (N+1, n); x_support: (N, s+1, n); u_support: (N, d_u+1, m).
    Stage states are computed from x_support, not separate NLP variables.
    This uneliminated form keeps mesh and support states as separate slots
    and connects both polynomial endpoints to the mesh with equalities.
    Callback signatures and the path-sampling convention match
    transcribed_problem. Residual order is boundary, then each interval's
    slope, left endpoint, right endpoint, and optional control continuity.
    """
    mesh = np.asarray(mesh, dtype=float)
    x_mesh, x_support, u_support = map(
        lambda a: np.asarray(a, dtype=float), (x_mesh, x_support, u_support)
    )
    if mesh.ndim != 1 or len(mesh) < 2 or not np.all(np.isfinite(mesh)):
        raise ValueError("mesh must be a finite one-dimensional array")
    widths = np.diff(mesh)
    if np.any(widths <= 0):
        raise ValueError("mesh times must be strictly increasing")
    N, s, r = len(widths), len(rule.nodes), len(rule.control_nodes)
    if x_mesh.ndim != 2 or x_mesh.shape[0] != N + 1:
        raise ValueError("x_mesh must have shape (N+1, n)")
    if x_support.shape != (N, s + 1, x_mesh.shape[1]):
        raise ValueError("x_support must have shape (N, s+1, n)")
    if u_support.ndim != 3 or u_support.shape[:2] != (N, r):
        raise ValueError("u_support must have shape (N, d_u+1, m)")

    objective = float(terminal_cost(x_mesh[-1], mesh[-1]))
    equalities = [np.asarray(boundary(x_mesh[0], x_mesh[-1], mesh[-1])).ravel()]
    inequalities = []
    for k, width in enumerate(widths):
        stage_times = mesh[k] + width * rule.nodes
        stage_states = rule.E @ x_support[k]
        stage_controls = rule.B @ u_support[k]
        slopes = np.stack([
            dynamics(x, u, t)
            for x, u, t in zip(stage_states, stage_controls, stage_times)
        ])
        rates = np.array([
            running_cost(x, u, t)
            for x, u, t in zip(stage_states, stage_controls, stage_times)
        ])
        equalities.append((rule.D @ x_support[k] - width * slopes).ravel())
        equalities.append(rule.state_left @ x_support[k] - x_mesh[k])
        equalities.append(rule.state_right @ x_support[k] - x_mesh[k + 1])
        objective += width * rule.b @ rates
        if path is not None:
            inequalities.extend(
                np.asarray(path(x, u, t)).ravel()
                for x, u, t in zip(stage_states, stage_controls, stage_times)
            )
        if continuous_control and k + 1 < N:
            equalities.append(
                rule.control_right @ u_support[k]
                - rule.control_left @ u_support[k + 1]
            )
    G = np.concatenate(inequalities) if inequalities else np.empty(0)
    return float(objective), G, np.concatenate(equalities)

The generator’s .deriv() differentiates a known basis polynomial with respect to local time τ\tau. An NLP solver also needs derivatives, but with respect to the decision vector z\mathbf z: those differentiate the assembled objective and constraints, including the dynamics function. Automatic differentiation can compute the latter in a compatible implementation; it does not replace or change the fixed matrix DD.

Checking the continuous trajectory

A small NLP residual confirms that the stored values satisfy the finite constraints. To check the approximation between nodes, first evaluate the state polynomial and its derivative at additional times. On interval kk, their ODE residual is

rk(τ)=1hkpk′(τ)−f ⁣(pk(τ),uh(tk+hkτ),tk+hkτ).\mathbf r_k(\tau)= \frac{1}{h_k}\mathbf p_k'(\tau) -\mathbf f\!\left( \mathbf p_k(\tau),\mathbf u_h(t_k+h_k\tau),t_k+h_k\tau \right).

This compares the slope of the reconstructed curve with the slope prescribed by the model at the same state, control, and time. The collocation equations make it vanish at the stages, up to solver tolerance. A large value elsewhere identifies an interval whose polynomial does not resolve the dynamics. Evaluate path constraints on this curve as well. This use of the method’s own interpolant for error assessment is described by Kelly (2017).

A separate check fixes the reconstructed control uh(t)\mathbf u_h(t) and integrates the original ODE from the prescribed initial state with an accurate time integrator. Compare this simulated state with xh(t)\mathbf x_h(t), and check its terminal conditions and path constraints. The simulation measures what the chosen control produces in the model; rk\mathbf r_k measures how well the polynomial itself satisfies that model. Step 5 of the pendulum demo performs this replay and separates the solver’s residual from the discretization error.

If the two trajectories disagree or the polynomial residual is large, subdivide the affected intervals or increase their polynomial degree, then solve again. Compare the objective and trajectories across refinements. A dense check supplies evidence about resolution but does not prove that every point satisfies a bound. Tightening an already small NLP tolerance alone cannot remove the error introduced by a coarse transcription.

Gauss--Legendre and Radau collocation

The coefficient generator accepts any distinct nodes, but their placement affects the accuracy of the resulting integration rule. To see what node selection can accomplish, retain two slope values per interval, as in the trapezoidal construction, and allow their locations to move. The slope interpolant remains linear and its integral remains a quadratic state piece. The node choice changes where the ODE is enforced and the weights used to combine its slopes.

For two-stage Gauss--Legendre collocation, choose the interior nodes

τ1=12−36,τ2=12+36.\tau_1=\frac12-\frac{\sqrt3}{6},\qquad \tau_2=\frac12+\frac{\sqrt3}{6}.

These are approximately 0.2113 and 0.7887. Constructing the two Lagrange basis functions at these nodes and integrating them with make_rule gives

A=[1414−3614+3614],b=[1212].A=\begin{bmatrix} \tfrac14&\tfrac14-\tfrac{\sqrt3}{6}\\ \tfrac14+\tfrac{\sqrt3}{6}&\tfrac14 \end{bmatrix},\qquad b=\begin{bmatrix}\tfrac12\\\tfrac12\end{bmatrix}.

The rows of AA determine the two interior stage states. The weights bb then give the endpoint constraint

xk+1−xk−hk2(fk,1+fk,2)=0.\mathbf x_{k+1}-\mathbf x_k -\frac{h_k}{2}(\mathbf f_{k,1}+\mathbf f_{k,2})=0.

The weights resemble the trapezoidal rule, but the slopes are evaluated at the two interior stages. This relocation improves quadrature accuracy: for a scalar integrand q(τ)=τ2q(\tau)=\tau^2, averaging endpoint values gives 1/21/2, whereas these nodes give

τ12+τ222=13=∫01τ2 dτ.\frac{\tau_1^2+\tau_2^2}{2}=\frac13 =\int_0^1\tau^2\,d\tau.

The same two-node Gauss rule integrates every polynomial of degree at most three exactly. This does not make the state piece cubic: its degree is still at most two. Quadrature exactness describes which integrands a weighted sum integrates exactly; the state degree describes the curve represented on an interval.

The name comes from the Legendre polynomials PsP_s on [−1,1][-1,1], which are orthogonal to all lower-degree polynomials under integration on that interval and are normalized by Ps(1)=1P_s(1)=1. For example, P0(ξ)=1P_0(\xi)=1, P1(ξ)=ξP_1(\xi)=\xi, and P2(ξ)=(3ξ2−1)/2P_2(\xi)=(3\xi^2-1)/2. The roots of P2P_2 are ±1/3\pm1/\sqrt3; mapping them to [0,1][0,1] by τ=(1+ξ)/2\tau=(1+\xi)/2 gives the two nodes above. With ss stages, the same construction uses the ss roots of PsP_s and gives a quadrature rule exact through degree 2s−12s-1. Gauss--Legendre quadrature supplies these node and exactness properties. The trajectory is still represented in a Lagrange basis: Legendre polynomials determine the nodes, and the Lagrange basis functions reconstruct values from those nodes.

For right Radau collocation, require the last node to lie at 1. With two stages, the nodes are τ1=1/3\tau_1=1/3 and τ2=1\tau_2=1. Their Lagrange basis functions are ℓ1(τ)=32(1−τ)\ell_1(\tau)=\tfrac32(1-\tau) and ℓ2(τ)=32τ−12\ell_2(\tau)=\tfrac32\tau-\tfrac12. Integrating them gives

A=[512−1123414],b=[3414].A=\begin{bmatrix} \tfrac5{12}&-\tfrac1{12}\\ \tfrac34&\tfrac14 \end{bmatrix},\qquad b=\begin{bmatrix}\tfrac34\\\tfrac14\end{bmatrix}.

The last row of AA equals b⊤b^\top because that stage is the right endpoint. Thus the stage and endpoint equations imply xk,2=xk+1\mathbf x_{k,2}=\mathbf x_{k+1}; these variables can be identified and the duplicate equation removed. The rule integrates quadratics exactly, since 34(13)2+14=13\tfrac34(\tfrac13)^2+\tfrac14=\tfrac13, but it does not integrate every cubic exactly. It trades some quadrature exactness for including the endpoint. For ss stages, the right Radau nodes are the shifted roots of Ps(ξ)−Ps−1(ξ)P_s(\xi)-P_{s-1}(\xi), including ξ=1\xi=1. With one stage, Gauss--Legendre recovers implicit midpoint, and right Radau recovers implicit Euler. The pendulum demo uses these two-stage Radau arrays; its Step 1 computes them by hand and in code and draws each entry as a signed area under a basis function.

CasADi supplies both node families through collocation_points. They can be passed directly to the slope-based coefficient generator:

import casadi as ca
from collocation_transcription import make_rule

s = 2
control_nodes = [0.0, 1.0]  # Linear control, chosen separately.
gauss_rule = make_rule(ca.collocation_points(s, "legendre"), control_nodes)
radau_rule = make_rule(ca.collocation_points(s, "radau"), control_nodes)

Here ss counts slope stages, giving a state degree of at most ss. Both calls retain a linear control, with stage values uk,j=(1−τj)u^k,0+τju^k,1\mathbf u_{k,j}=(1-\tau_j)\widehat{\mathbf u}_{k,0} +\tau_j\widehat{\mathbf u}_{k,1}. The same transcribed_problem function then assembles the stage constraints, endpoint constraints, and cost using either rule. No extra node at zero is needed by this generator: the left state already enters as the integration constant xk\mathbf x_k. CasADi’s direct-collocation example instead interpolates state values, so it includes the left endpoint among the state support nodes before differentiating that polynomial.

Gauss nodes include neither endpoint, right Radau includes the right one, and Lobatto includes both, as in the trapezoidal and Hermite--Simpson examples. For every family, adjacent state pieces must still share an endpoint state or be connected by an equality constraint. Constraints required at endpoints must also be imposed there even when those endpoints are not collocation stages. More accurate quadrature alone does not guarantee that the full trajectory optimization is well resolved; the control representation, mesh, and checks between nodes remain part of the construction.

Worked Example: Moving an Overhead Crane While Limiting Residual Sway

How do the nodal variables and trapezoidal defects behave in a constrained motion problem whose terminal state must suppress residual oscillation?

An overhead crane moves a trolley while a payload hangs from a cable. A precomputed trolley command can complete the move and still leave the payload swinging. The comparison uses three precomputed acceleration commands. The unshaped baseline moves the trolley without accounting for the payload. A zero-vibration input shaper modifies that baseline to cancel the nominal oscillation. Direct collocation instead chooses a command using the nonlinear payload dynamics and the motion constraints. All three commands are open loop: they are fixed before the move and do not respond to measurements during execution.

The state is x=(p,v,θ,ω)⊤\mathbf{x}=(p,v,\theta,\omega)^\top, where pp and vv are trolley position and velocity, and θ\theta and ω\omega are payload angle and angular velocity. The scalar control is the commanded trolley acceleration u=au=a. It enters the nonlinear dynamics as

p˙=v,v˙=a,θ˙=ω,ω˙=−gℓsin⁡θ−aℓcos⁡θ−γω.\dot p=v,\qquad \dot v=a,\qquad \dot\theta=\omega,\qquad \dot\omega=-\frac{g}{\ell}\sin\theta-\frac{a}{\ell}\cos\theta-\gamma\omega.

Here gg is gravitational acceleration, ℓ\ell is cable length, and γ\gamma is the damping coefficient. The first two equations describe trolley motion, while the last two describe a damped pendulum driven at its suspension point. Positive trolley acceleration makes the load lag behind, which accounts for the minus sign multiplying aa. The model treats the cable as a rigid, massless link and assumes that the trolley acceleration can be commanded directly.

The trolley and payload start at rest. The goal is to move the trolley 4 m, stop it, and leave the payload hanging vertically without swinging. The nominal cable length is ℓ=1.20\ell=1.20 m, with g=9.81g=9.81 m/s2^2 and γ=0.035\gamma=0.035 s−1^{-1}. Every command is limited to ∣a∣≤1.60|a|\leq 1.60 m/s2^2, and every command is replayed on the same nonlinear continuous-time plant with a 0.02 s sampling interval. A second replay increases the cable length by 10%10\% without redesigning any command. This second plant tests sensitivity to a simple model mismatch.

Two open-loop baselines

The unshaped baseline uses a symmetric trapezoidal velocity profile, produced by acceleration at 0.80 m/s2^2 up to 1.00 m/s, cruising, and symmetric deceleration. These phases last 1.25, 2.75, and 1.25 s, respectively. Its acceleration and deceleration phases excite the payload oscillation because their timing ignores the pendulum period.

An input shaper filters a command into weighted, delayed copies. The delays are chosen so that vibrations excited by the copies cancel one another. For a zero-vibration (ZV) shaper, begin by approximating sin⁡θ≈θ\sin\theta\approx\theta and cos⁡θ≈1\cos\theta\approx1 near the hanging equilibrium. The payload equation then becomes

θ¨+2ζωnθ˙+ωn2θ=−aℓ,ωn=gℓ,ζ=γ2ωn.\ddot\theta+2\zeta\omega_n\dot\theta+\omega_n^2\theta=-\frac{a}{\ell}, \qquad \omega_n=\sqrt{\frac{g}{\ell}}, \qquad \zeta=\frac{\gamma}{2\omega_n}.

Here ωn\omega_n is the undamped natural frequency and ζ\zeta is the damping ratio. For the nominal parameters, ωn=2.86\omega_n=2.86 rad/s and ζ=0.0061\zeta=0.0061. The shaper splits the baseline command a0a_0 into two copies separated by half of the damped oscillation period:

aZV(t)=A1a0(t)+A2a0(t−Td),Td=πωn1−ζ2 .a_{\mathrm{ZV}}(t)=A_1a_0(t)+A_2a_0(t-T_d), \qquad T_d=\frac{\pi}{\omega_n\sqrt{1-\zeta^2}}\,.

The delay makes the vibration caused by the second copy oppose the vibration remaining from the first. Accounting for decay over the delay gives A1=1/(1+K)A_1=1/(1+K), A2=K/(1+K)A_2=K/(1+K), and K=exp⁡[−ζπ/1−ζ2]K=\exp[-\zeta\pi/\sqrt{1-\zeta^2}]. Here Td=1.10T_d=1.10 s and the two weights are approximately 0.505 and 0.495.

Nodal decision variables and trapezoidal defects

The shaped command finishes after 5.25+Td≈6.355.25+T_d\approx6.35 s. Give the collocation command the same fixed horizon, and evaluate residual sway over the following two seconds with zero commanded acceleration.

The third command is found by solving the trajectory-optimization problem. Divide [0,5.25+Td][0,5.25+T_d] into N=28N=28 equal intervals, so h=(5.25+Td)/28h=(5.25+T_d)/28. The NLP decision vector contains the state

xk=(pk,vk,θk,ωk)⊤\mathbf{x}_k=(p_k,v_k,\theta_k,\omega_k)^\top

and acceleration aka_k at every mesh node. These are polynomial values, not monomial coefficients. Each interval contributes the trapezoidal defect

xk+1−xk−h2[f(xk,ak)+f(xk+1,ak+1)]=0.\mathbf{x}_{k+1}-\mathbf{x}_k -\frac{h}{2}\left[ \mathbf{f}(\mathbf{x}_k,a_k)+\mathbf{f}(\mathbf{x}_{k+1},a_{k+1}) \right]=\mathbf0.

The objective trades payload motion against acceleration magnitude and rapid changes in acceleration. The state and acceleration penalties are collected in the nodal quantity

ckrate=6θk2+0.15ωk2+0.035ak2.c_k^{\mathrm{rate}}=6\theta_k^2+0.15\omega_k^2+0.035a_k^2.

The coefficient on θk2\theta_k^2 penalizes sway most strongly, while the smaller coefficients penalize angular velocity and acceleration. The smooth state-and-control cost uses trapezoidal endpoint weights. The final term penalizes acceleration slew, the rate at which the acceleration command changes. Because the control is piecewise linear, this rate is constant on each interval, giving

J=h(12c0rate+∑k=1N−1ckrate+12cNrate)+0.002h∑k=0N−1(ak+1−akh)2.\mathcal J =h\left(\frac12c_0^{\mathrm{rate}}+\sum_{k=1}^{N-1}c_k^{\mathrm{rate}}+\frac12c_N^{\mathrm{rate}}\right) +0.002h\sum_{k=0}^{N-1} \left(\frac{a_{k+1}-a_k}{h}\right)^2.

The boundary conditions impose x0=(0,0,0,0)⊤\mathbf{x}_0=(0,0,0,0)^\top and xN=(4,0,0,0)⊤\mathbf{x}_N=(4,0,0,0)^\top, so both the trolley and payload finish at rest. The nodal bounds impose ∣ak∣≤1.60|a_k|\leq1.60 m/s2^2, ∣vk∣≤1.50|v_k|\leq1.50 m/s, and ∣θk∣≤15∘|\theta_k|\leq15^\circ. Linear interpolation preserves the acceleration bound between its endpoint values. The state bounds still require checks between nodes and on the independently simulated trajectory.

The ZV shaper is tailored to one frequency, so it should leave the least residual sway when the cable length matches its design model. The collocation solution uses the full nonlinear nominal model and handles all constraints at once, but it is also open loop and has no explicit robustness guarantee. The cable-length test therefore measures sensitivity to one model mismatch; it does not establish that either method is robust in general.

Figure 13:All three commands move the trolley through the same four-metre task on the nonlinear plant. The unshaped command leaves a large oscillation. The ZV shaper nearly cancels the nominal mode, while direct collocation reaches the terminal state with a smoother, lower-effort command. The lower panel replays the unchanged commands after increasing cable length by 10 percent. Hatched bars denote the mismatched plant.

<Figure size 825x750 with 3 Axes>

The ZV shaper produces the smallest nominal residual sway because the simulated plant closely matches the single oscillatory mode used to design it. The collocation command uses less squared acceleration than either baseline and keeps residual sway below one degree in both replays. The unshaped command completes the trolley move but leaves several degrees of oscillation. Cable-length mismatch increases the ZV residual substantially, while the collocation command degrades more gradually for this particular perturbation.

The table reports continuous-plant measurements rather than node values from the nonlinear program. Residual sway is the largest absolute angle after the common command horizon.

Loading...

The animation uses the same high-accuracy nonlinear validation trajectories as the figure. It does not replay the collocation polynomial itself.

Figure 14:Continuous nonlinear replay of the unshaped, zero-vibration-shaped, and direct-collocation commands. All panels use the same spatial and temporal scales.

Loading...
Inspect the direct-transcription implementation
crane_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def solve_direct_collocation(
    params: CraneParameters | None = None,
    *,
    intervals: int = 28,
    horizon: float | None = None,
    max_iterations: int = 350,
) -> CollocationSolution:
    """Solve a constrained nonlinear point-to-point transcription.

    States and accelerations at every node are decision variables.  Trapezoidal
    defects enforce the nonlinear dynamics, while boundary and path constraints
    are imposed directly on the node values.
    """

    params = params or CraneParameters()
    params.validate()
    if intervals < 8:
        raise ValueError("intervals must be at least eight")

    move = TrapezoidalMove(
        params.target_position,
        params.profile_acceleration,
        params.profile_max_speed,
    )
    shaper = ZeroVibrationShaper.from_parameters(params)
    if horizon is None:
        horizon = move.duration + shaper.delay
    if horizon <= 0.0:
        raise ValueError("horizon must be positive")

    node_time = np.linspace(0.0, horizon, intervals + 1)
    step = horizon / intervals
    state_guess, acceleration_guess = _initial_guess(params, node_time, move, shaper)
    initial = _pack(state_guess, acceleration_guess)

    initial_state = np.zeros(4)
    terminal_state = np.array([params.target_position, 0.0, 0.0, 0.0])

    def objective(vector: np.ndarray) -> float:
        state, acceleration = _unpack(vector, intervals)
        return _transcription_objective(state, acceleration, step)

    def equality(vector: np.ndarray) -> np.ndarray:
        state, acceleration = _unpack(vector, intervals)
        defects = np.empty((intervals, 4))
        for k in range(intervals):
            left = crane_dynamics(state[k], acceleration[k], params)
            right = crane_dynamics(state[k + 1], acceleration[k + 1], params)
            defects[k] = state[k + 1] - state[k] - 0.5 * step * (left + right)
        return np.concatenate(
            [state[0] - initial_state, defects.ravel(), state[-1] - terminal_state]
        )

    nodes = intervals + 1
    lower_state = np.tile(
        np.array([-0.25, -params.velocity_limit, -params.sway_limit, -2.0]),
        (nodes, 1),
    )
    upper_state = np.tile(
        np.array(
            [params.target_position + 0.25, params.velocity_limit, params.sway_limit, 2.0]
        ),
        (nodes, 1),
    )
    lower = _pack(lower_state, np.full(nodes, -params.acceleration_limit))
    upper = _pack(upper_state, np.full(nodes, params.acceleration_limit))

    result = minimize(
        objective,
        initial,
        method="SLSQP",
        bounds=list(zip(lower, upper)),
        constraints={"type": "eq", "fun": equality},
        options={"maxiter": max_iterations, "ftol": 2e-10, "disp": False},
    )
    state, acceleration = _unpack(result.x, intervals)
    max_defect = float(np.max(np.abs(equality(result.x))))
    success = bool(result.success and max_defect < 2e-5)
    if not success:
        raise RuntimeError(
            "crane direct collocation failed: "
            f"{result.message}; maximum equality residual {max_defect:.3e}"
        )
    return CollocationSolution(
        time=node_time,
        state=state,
        acceleration=acceleration,
        objective=float(result.fun),
        max_defect=max_defect,
        success=success,
        message=str(result.message),
    )

Download the complete crane experiment

The optimization checks algebraic defects, bounds, and endpoint conditions at the nodes. The separate nonlinear replay checks what happens between those nodes. A small nodal defect is evidence that the discrete NLP was solved accurately; it is not, by itself, evidence that the mesh resolves the continuous dynamics.

The comparison does not establish that collocation always outperforms input shaping. The ZV calculation is inexpensive and can nearly cancel residual vibration when a lightly damped mode is accurately known. Direct collocation becomes useful when several state and actuator constraints must be handled together. All three commands remain open loop here. Feedback or receding-horizon replanning would be needed to react to unmeasured disturbances during the move.

Exercises

Lagrange interpolation practice

The worked example and first four exercises form a classroom sequence of about 30--40 minutes. They progress from constructing a polynomial through prescribed values to integrating a control on one physical time interval. The final exercise introduces barycentric evaluation as an optional extension. Each solution is collapsed so that the problems can be attempted first.

Worked example: recovering a quadratic

Suppose we know the values of f(t)=t2f(t)=t^2 only at t=1,2,3t=1,2,3 and want a polynomial of degree at most two passing through them. These are physical coordinates for this example; they need not lie in the normalized interval [0,1][0,1]. The nodes and values are

(σ0,σ1,σ2)=(1,2,3),(y0,y1,y2)=(1,4,9).(\sigma_0,\sigma_1,\sigma_2)=(1,2,3),\qquad (y_0,y_1,y_2)=(1,4,9).

To attach a basis function to y0y_0, put zeros at the other two nodes and normalize its value at σ0=1\sigma_0=1. Repeat for the remaining nodes:

ℓ0(t)=(t−2)(t−3)(1−2)(1−3)=(t−2)(t−3)2,ℓ1(t)=(t−1)(t−3)(2−1)(2−3)=−(t−1)(t−3),ℓ2(t)=(t−1)(t−2)(3−1)(3−2)=(t−1)(t−2)2.\begin{aligned} \ell_0(t)&=\frac{(t-2)(t-3)}{(1-2)(1-3)} =\frac{(t-2)(t-3)}2,\\ \ell_1(t)&=\frac{(t-1)(t-3)}{(2-1)(2-3)} =-(t-1)(t-3),\\ \ell_2(t)&=\frac{(t-1)(t-2)}{(3-1)(3-2)} =\frac{(t-1)(t-2)}2. \end{aligned}

For example, at t=2t=2 the three basis values are (0,1,0)(0,1,0), so their weighted sum returns y1=4y_1=4. At any other time, evaluate the same functions and combine them with the same stored values. Expanding gives

p(t)=ℓ0(t)+4ℓ1(t)+9ℓ2(t)=(12t2−52t+3)+(−4t2+16t−12)+(92t2−272t+9)=t2.\begin{aligned} p(t)&=\ell_0(t)+4\ell_1(t)+9\ell_2(t)\\ &=\left(\tfrac12t^2-\tfrac52t+3\right) +\left(-4t^2+16t-12\right) +\left(\tfrac92t^2-\tfrac{27}2t+9\right)\\ &=t^2. \end{aligned}

The reconstruction agrees with ff everywhere because ff itself is a polynomial of degree two. Three samples of a general function would determine its quadratic interpolant, but would not determine that function between samples. Here, for instance, t2+(t−1)(t−2)(t−3)t^2+(t-1)(t-2)(t-3) has the same three sampled values and differs between them.

Solution to Exercise 2

The basis functions are ℓ0(τ)=(τ−1)/(0−1)=1−τ\ell_0(\tau)=(\tau-1)/(0-1)=1-\tau and ℓ1(τ)=(τ−0)/(1−0)=τ\ell_1(\tau)=(\tau-0)/(1-0)=\tau. Combining the stored values gives

p(τ)=2(1−τ)−τ=2−3τ,p(1/4)=5/4.p(\tau)=2(1-\tau)-\tau=2-3\tau,\qquad p(1/4)=5/4.

Its Lagrange coefficients are (2,−1)(2,-1), the endpoint values. Its monomial coefficients are (2,−3)(2,-3), the intercept and slope. The polynomial 2−3τ+τ(τ−1)2-3\tau+\tau(\tau-1) has the same endpoint values because the added term vanishes at both endpoints. It is quadratic, so it lies outside the original degree restriction.

Solution to Exercise 3

Putting zeros at the other two nodes and normalizing at the selected node gives

ℓ0(τ)=2(τ−12)(τ−1)=2τ2−3τ+1,ℓm(τ)=−4τ(τ−1)=4τ−4τ2,ℓ1(τ)=2τ(τ−12)=2τ2−τ.\begin{aligned} \ell_0(\tau)&=2(\tau-\tfrac12)(\tau-1)=2\tau^2-3\tau+1,\\ \ell_m(\tau)&=-4\tau(\tau-1)=4\tau-4\tau^2,\\ \ell_1(\tau)&=2\tau(\tau-\tfrac12)=2\tau^2-\tau. \end{aligned}

The rows of their values at 0,12,10,\tfrac12,1 are (1,0,0)(1,0,0), (0,1,0)(0,1,0), and (0,0,1)(0,0,1). Adding the expanded expressions cancels the quadratic and linear terms and leaves one. The interpolant is

p(τ)=ℓ0(τ)+2ℓm(τ)=1+5τ−6τ2,p(1/4)=15/8.p(\tau)=\ell_0(\tau)+2\ell_m(\tau) =1+5\tau-6\tau^2,\qquad p(1/4)=15/8.

At 1/41/4, the basis values are (3/8,3/4,−1/8)(3/8,3/4,-1/8). They sum to one but include a negative weight. The interpolant therefore need not stay within the range of its nodal values.

Solution to Exercise 4

Only the coefficient of ℓm\ell_m changes, so

p~(τ)=p(τ)+ℓm(τ)=1+9τ−10τ2.\widetilde p(\tau)=p(\tau)+\ell_m(\tau)=1+9\tau-10\tau^2.

The change at 1/41/4 is ℓm(1/4)=3/4\ell_m(1/4)=3/4, giving p~(1/4)=21/8\widetilde p(1/4)=21/8. The endpoints are unchanged because ℓm\ell_m vanishes there.

For the original polynomial, p′(τ)=5−12τp'(\tau)=5-12\tau vanishes at 5/125/12. Since p′′=−12<0p''=-12<0, this point is its maximum, with

p(5/12)=4924=2+124>2.p(5/12)=\frac{49}{24}=2+\frac1{24}>2.

Changing a nodal decision variable changes the curve between nodes through its basis function. Likewise, bounds on the nodal variables alone do not ensure bounds on a quadratic state trajectory between nodes. The transcription needs additional checks there, as described in Checking the continuous trajectory.

Solution to Exercise 5

The only nonzero control value is at the midpoint, so u(2+2τ)=ℓm(τ)=4τ(1−τ)u(2+2\tau)=\ell_m(\tau)=4\tau(1-\tau). Integrating the expanded basis functions gives

w0=23−32+1=16,wm=2−43=23,w1=23−12=16.\begin{aligned} w_0&=\tfrac23-\tfrac32+1=\tfrac16,\\ w_m&=2-\tfrac43=\tfrac23,\\ w_1&=\tfrac23-\tfrac12=\tfrac16. \end{aligned}

Because dt=2 dτdt=2\,d\tau, the displacement is 2(w0⋅0+wm⋅1+w1⋅0)=4/32(w_0\cdot0+w_m\cdot1+w_1\cdot0)=4/3. Integrating only as far as the current τ\tau gives the state piece

p(τ)=3+2∫0τ4η(1−η) dη=3+4τ2−83τ3.p(\tau)=3+2\int_0^\tau4\eta(1-\eta)\,d\eta =3+4\tau^2-\frac83\tau^3.

It is cubic, with p′(τ)/2=4τ−4τ2p'(\tau)/2=4\tau-4\tau^2, as required. The endpoint constraint for arbitrary stored values is

xk+1−xk−2(16u0+23um+16u1)=0.x_{k+1}-x_k-2\left(\frac16u_0+\frac23u_m+\frac16u_1\right)=0.

For xk=3x_k=3 and the prescribed controls, it requires xk+1=13/3x_{k+1}=13/3. This integration is exact for the chosen quadratic control and the dynamics x˙=u\dot x=u; no claim of exactness for general nonlinear dynamics is needed.

Solution to Exercise 6

The products of node differences give (β0,β1,β2)=(1/2,−1,1/2)(\beta_0,\beta_1,\beta_2) =(1/2,-1,1/2). At t=3/2t=3/2, the numerator and denominator are

1/21/2+−4−1/2+9/2−3/2=6,1/21/2+−1−1/2+1/2−3/2=83.\frac{1/2}{1/2}+\frac{-4}{-1/2}+\frac{9/2}{-3/2}=6, \qquad \frac{1/2}{1/2}+\frac{-1}{-1/2}+\frac{1/2}{-3/2}=\frac83.

Their ratio is 9/49/4, agreeing with (3/2)2(3/2)^2. Multiplying every weight by the same nonzero number scales both sums equally and leaves their ratio unchanged. At a query that equals a node, return the stored value directly: p(2)=4p(2)=4. The polynomial is defined there, but the individual quotients in this evaluation formula divide by zero.

Further exercises

The following exercises revisit polynomial coordinates and connect them to the complete collocation schemes and trajectory checks developed in the chapter.

Solution to Exercise 7

The nodal values are

y=[p(0)p(12)p(1)]=[223].\mathbf{y}= \begin{bmatrix} p(0)\\p(\tfrac12)\\p(1) \end{bmatrix} = \begin{bmatrix} 2\\2\\3 \end{bmatrix}.

For the monomial basis,

V=[10011214111].V= \begin{bmatrix} 1&0&0\\ 1&\tfrac12&\tfrac14\\ 1&1&1 \end{bmatrix}.

Solving Va=yV\mathbf{a}=\mathbf{y} returns a=(2,−1,2)T\mathbf{a}=(2,-1,2)^\mathsf T. The solve changes coordinates; it does not construct a different polynomial.

Solution to Exercise 8

Problems 1 and 4 are interpolation problems; the least-squares formulation in problem 4 has a zero-residual interpolating solution. Problem 2 is regression. Problem 3 is neither statistical regression nor interpolation of observations: the unknown nodal values define an interpolating polynomial, and collocation adds equality constraints that select those values.

Solution to Exercise 9

The cardinal functions are

ℓ0=2τ2−3τ+1,ℓm=4τ(1−τ),ℓ1=2τ2−τ.\ell_0=2\tau^2-3\tau+1,\qquad \ell_m=4\tau(1-\tau),\qquad \ell_1=2\tau^2-\tau.

They give

D=[−34−1−1011−43],w=16[141].D= \begin{bmatrix} -3&4&-1\\ -1&0&1\\ 1&-4&3 \end{bmatrix}, \qquad w=\frac16 \begin{bmatrix}1\\4\\1\end{bmatrix}.

For y=(1,74,2)T\mathbf{y}=(1,\tfrac74,2)^\mathsf T, Dy=(2,1,0)TD\mathbf{y}=(2,1,0)^\mathsf T, which equals p′(τ)=2−2τp'(\tau)=2-2\tau at the three nodes.

Solution to Exercise 10

One node gives the constant derivative interpolant fk\mathbf{f}_k or fk+1\mathbf{f}_{k+1}. Integration yields the explicit or implicit Euler defect and a degree-one state. With endpoint nodes, x˙h=(1−τ)fk+τfk+1\dot{\mathbf{x}}_h=(1-\tau)\mathbf{f}_k+\tau \mathbf{f}_{k+1}. Its integral at τ=1\tau=1 is 12(fk+fk+1)\tfrac12(\mathbf{f}_k+\mathbf{f}_{k+1}), which gives the trapezoidal defect. The linear derivative integrates to a degree-two state.

Solution to Exercise 11

Three distinct slope values define a quadratic derivative interpolant. Integrating that quadratic adds one degree, so the state is cubic. Integrating the cardinal functions over [0,1][0,1] gives (1,4,1)/6(1,4,1)/6 and hence the Simpson defect. Integrating to τ=12\tau=\tfrac12 gives (5,8,−1)/24(5,8,-1)/24; eliminating the midpoint slope with the endpoint defect gives

xk+12=12(xk+xk+1)+hk8(fk−fk+1).\mathbf{x}_{k+\frac12} =\frac12(\mathbf{x}_k+\mathbf{x}_{k+1}) +\frac{h_k}{8}(\mathbf{f}_k-\mathbf{f}_{k+1}).
Solution to Exercise 12

The cardinal function is ℓ1=1\ell_1=1, so A=[1/2]A=[1/2], b=[1]b=[1], and B=[1]B=[1]. The stage equation gives xk,1=xk+(hk/2)fk,1\mathbf x_{k,1}=\mathbf x_k+(h_k/2)\mathbf f_{k,1}, while the endpoint equation gives xk+1=xk+hkfk,1\mathbf x_{k+1}=\mathbf x_k+h_k\mathbf f_{k,1}. Eliminating the slope between these equations gives xk,1=(xk+xk+1)/2\mathbf x_{k,1}=(\mathbf x_k+\mathbf x_{k+1})/2 and hence

xk+1−xk−hkf ⁣(xk+xk+12,uk,1,tk+hk2)=0.\mathbf x_{k+1}-\mathbf x_k -h_k\mathbf f\!\left( \frac{\mathbf x_k+\mathbf x_{k+1}}2,\mathbf u_{k,1},t_k+\frac{h_k}2 \right)=\mathbf0.

The unknown right state appears inside the dynamics evaluation, which makes the rule implicit. The state polynomial is linear and the control constant. For the scalar problem with two half-unit intervals, the complete NLP is

min⁡x0,x1,x2,u0,1,u1,112(u0,12+u1,12)subject toxk+1−xk−12uk,1=0,k=0,1,−2≤uk,1≤2,k=0,1,x0=0,x2=1.\begin{aligned} \min_{x_0,x_1,x_2,u_{0,1},u_{1,1}}\quad& \frac12(u_{0,1}^2+u_{1,1}^2)\\ \text{subject to}\quad& x_{k+1}-x_k-\frac12u_{k,1}=0,\quad k=0,1,\\ &-2\leq u_{k,1}\leq2,\quad k=0,1,\\ &x_0=0,\qquad x_2=1. \end{aligned}

The defects imply u0,1+u1,1=2u_{0,1}+u_{1,1}=2. Substitution into the objective gives (u0,1−1)2+1(u_{0,1}-1)^2+1, so both controls are one, x1=1/2x_1=1/2, and the minimum cost is one. This recovers the opening trajectory on a finer mesh.

Solution to Exercise 13

Both controls are zero at both endpoints. The trapezoidal defect therefore predicts x(1)−x(0)=0x(1)-x(0)=0 for either command. Their actual displacements are zero and

∫014t(1−t) dt=4(12−13)=23.\int_0^1 4t(1-t)\,dt =4\left(\frac12-\frac13\right)=\frac23.

Endpoint samples cannot detect the quadratic control’s interior variation. Restricting the control to a line makes two zero endpoint values determine the zero control. Alternatively, a midpoint stage samples the second control at value one, so the two commands produce different stage slopes. With all three stages, Simpson weights integrate this quadratic control exactly.

Solution to Exercise 14

The NLP defect measures its equality constraints at the selected nodes. A coarse polynomial can satisfy those constraints while failing to resolve behavior between them. Evaluate rk\mathbf r_k and path constraints on the polynomial at additional times. Separately, integrate the original ODE using the chosen control and compare the simulated states with the polynomial. Refine the offending intervals or raise the local degree, then solve and validate again. Tightening an already small NLP tolerance does not fix representation error.

Summary and Outlook

Direct collocation turns an optimization over whole functions into an optimization over finitely many state and control values. These nodal values define polynomial segments. Algebraic defect constraints then require each segment to agree with the ODE at selected points.

Each polynomial has two equivalent descriptions: basis coefficients or values at nodes,

p(τ)=∑jajϕj(τ)=∑jyjℓj(τ),y=Va.p(\tau)=\sum_j a_j\phi_j(\tau) =\sum_j y_j\ell_j(\tau), \qquad \mathbf y=V\mathbf a.

Direct collocation uses the nodal description because the stored values have an immediate physical meaning. Lagrange cardinal functions turn them into fixed operators for computing derivatives, endpoints, and integrals. Although this construction resembles polynomial fitting, the nodal values are optimization variables rather than noisy observations, and the ODE residual is an equality constraint rather than a regression loss.

Both integration and differentiation forms impose collocation at selected nodes. The integration form interpolates evaluated ODE slopes and uses state-matching residuals to make their integral agree with the stored states. The differentiation form interpolates state values and uses slope-matching residuals to make their derivative agree with the ODE. With matching node choices, degree, and control space, both describe the same feasible polynomial trajectories.

Interpolating one left or right slope gives explicit or implicit Euler and a linear state approximation. Interpolating both endpoint slopes gives the trapezoidal defect and a quadratic state approximation. Adding the midpoint slope gives Simpson weights, the Hermite--Simpson midpoint relation, and a cubic state approximation.

The control degree is chosen separately. Its support values determine the stage controls through the evaluation matrix BB. A program can construct AA, bb, and BB from the selected nodes, then assemble the same stage, endpoint, and cost expressions for all these schemes. Changing the nodes or control representation changes the finite approximation without requiring a new derivation of those expressions.

Across many intervals, each defect involves only neighboring endpoints and local stage values. Most entries in the constraint Jacobian are therefore zero, which allows an NLP solver to exploit a sparse, block-banded structure. The overhead-crane example also shows why solving the NLP is not the final check: constraints that hold at the nodes may still be violated between them. Polynomial residuals assess the local approximation, and independent simulation checks the trajectory produced by the chosen control.

How can we improve a control sequence when a simulator evaluates its cost but does not provide derivatives? Model Predictive Path Integral Control samples candidate control sequences and combines them using cost-dependent weights. When physical disturbances make the rollout uncertain, several simulated futures estimate each candidate’s expected cost. The following receding-horizon control chapter studies how replanning from measurements supplies feedback to a finite-horizon optimizer.

References
  1. Kelly, M. (2017). An Introduction to Trajectory Optimization: How to Do Your Own Direct Collocation. SIAM Review, 59(4), 849–904. 10.1137/16M1062569
  2. Andersson, J. A. E., Gillis, J., Horn, G., Rawlings, J. B., & Diehl, M. (2019). CasADi: A Software Framework for Nonlinear Optimization and Optimal Control. Mathematical Programming Computation, 11(1), 1–36. 10.1007/s12532-018-0139-4