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.

Numerical Trajectory Optimization

The Pontryagin recursion exposes the temporal structure of first-order optimality, but a solver still needs a finite vector of variables and constraints. Should the states remain explicit, be eliminated by simulation, or appear only at selected segment boundaries?

Each choice recasts the discrete-time optimal control problem as a standard nonlinear program (NLP). Collect all decision variables (states, controls, and any auxiliary variables) into a single vector z∈Rnz\mathbf{z}\in\mathbb{R}^{n_z} and write

min⁡z∈RnzF(z)s.t.H(z)=0,G(z)≤0,\begin{aligned} \min_{\mathbf{z}\in\mathbb{R}^{n_z}} \quad & F(\mathbf{z}) \\ \text{s.t.} \quad & H(\mathbf{z}) = 0, \\ & G(\mathbf{z}) \le 0, \end{aligned}

with maps F:Rnz→RF:\mathbb{R}^{n_z}\to\mathbb{R}, H:Rnz→RreH:\mathbb{R}^{n_z}\to\mathbb{R}^{r_e}, and G:Rnz→RriG:\mathbb{R}^{n_z}\to\mathbb{R}^{r_i}. In optimal control, HH typically encodes dynamics and boundary conditions, while GG captures path and box constraints.

Thus uppercase GG stacks the inequalities gi≤0g_i\leq0, and uppercase HH stacks the equalities hi=0h_i=0, following the conventions of the optimal-control formulation and the nonlinear-programming appendix.

There are multiple ways to arrive at (and benefit from) this NLP:

The next sections work through these formulations, starting with simultaneous methods, then sequential methods, and finally multiple shooting, before discussing how generic NLP solvers and specialized algorithms leverage the resulting structure in practice.

Simultaneous Methods

What numerical structure appears when every state and action remains an optimization variable and each transition becomes an equality constraint?

In the simultaneous (also called direct transcription or full discretization) approach, we keep the entire trajectory explicit and enforce the dynamics as equality constraints. Starting from the Bolza DOCP,

min⁡{xt,ut}  cT(xT)+∑t=1T−1ct(xt,ut)s.t.xt+1−ft(xt,ut)=0,  t=1,…,T−1,\min_{\{\mathbf{x}_t,\mathbf{u}_t\}}\; c_T(\mathbf{x}_T) + \sum_{t=1}^{T-1} c_t(\mathbf{x}_t,\mathbf{u}_t) \quad\text{s.t.}\quad \mathbf{x}_{t+1} - \mathbf{f}_t(\mathbf{x}_t,\mathbf{u}_t) = 0,\; t=1,\dots,T-1,

collect all variables into a single vector

z:=[x1⊤⋯xT⊤u1⊤⋯uT−1⊤]⊤∈Rnz.\mathbf{z} := \begin{bmatrix} \mathbf{x}_1^\top & \cdots & \mathbf{x}_T^\top & \mathbf{u}_1^\top & \cdots & \mathbf{u}_{T-1}^\top \end{bmatrix}^\top \in \mathbb{R}^{n_z}.

The dynamics residual is written here as xt+1−ft\mathbf{x}_{t+1}-\mathbf f_t. Its multiplier is the negative of the Pontryagin costate, which multiplies ft−xt+1\mathbf f_t-\mathbf{x}_{t+1}; reversing an equality residual changes its multiplier sign but leaves the feasible trajectories unchanged.

Path constraints typically apply only at selected times. Let E\mathscr{E} index additional equality constraints hih_i and I\mathscr{I} index inequality constraints gig_i. For each constraint ii, define the set of time indices Ki⊆{1,…,T}K_i \subseteq \{1,\dots,T\} where it is enforced (e.g., terminal constraints use Ki={T}K_i = \{T\}). The simultaneous transcription is the NLP

min⁡zF(z):=cT(xT)+∑t=1T−1ct(xt,ut)s.t.H(z)=[[ hi(xk,uk)]i∈E, k∈Ki[ xt+1−ft(xt,ut)]t=1:T−1x1−xinit]=0,G(z)=[ gi(xk,uk)]i∈I, k∈Ki  ≤  0,\begin{aligned} \min_{\mathbf{z}}\quad & F(\mathbf{z}) := c_T(\mathbf{x}_T) + \sum_{t=1}^{T-1} c_t(\mathbf{x}_t,\mathbf{u}_t) \\ \text{s.t.}\quad & H(\mathbf{z}) = \begin{bmatrix} \big[\, h_i(\mathbf{x}_k,\mathbf{u}_k) \big]_{i\in\mathscr{E},\, k\in K_i} \\ \big[\, \mathbf{x}_{t+1} - \mathbf{f}_t(\mathbf{x}_t,\mathbf{u}_t) \big]_{t=1: T-1} \\ \mathbf{x}_1 - \mathbf{x}_\mathrm{init} \end{bmatrix} = \mathbf{0}, \\ & G(\mathbf{z}) = \big[\, g_i(\mathbf{x}_k,\mathbf{u}_k) \big]_{i\in\mathscr{I},\, k\in K_i} \; \le \; \mathbf{0}, \end{aligned}

optionally with simple bounds xlb≤xt≤xub\mathbf{x}_{\mathrm{lb}} \le \mathbf{x}_t \le \mathbf{x}_{\mathrm{ub}} and ulb≤ut≤uub\mathbf{u}_{\mathrm{lb}} \le \mathbf{u}_t \le \mathbf{u}_{\mathrm{ub}} folded into GG or provided to the solver separately. For notational convenience, some constraints may not depend on uk\mathbf{u}_k at times in KiK_i; the indexing still helps specify when each condition is active.

This direct transcription is attractive because it is faithful to the model and exposes sparsity. The Jacobian of HH has a block bi-diagonal structure induced by the dynamics, and the KKT matrix is sparse and structured. These properties are exploited by interior-point and SQP methods. The trade-off is size: with state dimension nn and control dimension mm, the decision vector has (T ⁣⋅ ⁣n)+((T ⁣−1)⋅m)(T\!\cdot\!n) + ((T\!- 1)\cdot m) entries, and there are roughly (T ⁣−1)⋅n(T\!- 1)\cdot n dynamic equalities plus any path and boundary conditions. Techniques such as partial or full condensing eliminate state variables to reduce the equality set (at the cost of denser matrices), while keeping states explicit preserves sparsity and often improves robustness on long horizons and in the presence of state constraints.

Compared to alternatives, simultaneous methods avoid the long nonlinear dependency chains of single shooting and make it easier to impose state/path constraints. They can, however, demand more memory and per-iteration linear algebra, so practical performance hinges on exploiting sparsity and good initialization.

The same logic applies when selecting an optimizer. For small-scale problems, it is common to rely on general-purpose routines such as those in scipy.optimize.minimize. Derivative-free methods like Nelder–Mead require no gradients but scale poorly as dimensionality increases. Quasi-Newton schemes such as BFGS work well for moderate dimensions and can approximate gradients by finite differences, while large-scale trajectory optimization often calls for gradient-based constrained solvers such as interior-point or sequential quadratic programming methods that can exploit sparse Jacobians and benefit from automatic differentiation. Stochastic techniques, including genetic algorithms, simulated annealing, or particle swarm optimization, occasionally appear when gradients are unavailable, but their cost grows rapidly with dimension and they are rarely competitive for structured optimal control problems.

Example: Nonlinear Cart-Pole Swing-Up

A cart carries a rigid pendulum whose angle is measured from the upright vertical. The cart can accelerate horizontally, but no actuator applies torque directly at the pendulum joint. Starting from the stable downward configuration, the task is to move the base so that the pendulum arrives upright while the cart returns near the center of a finite rail.

Let the state be x=(p,v,θ,ω)\mathbf{x}=(p,v,\theta,\omega), where pp and vv are the cart position and velocity, and θ\theta and ω\omega are the pendulum angle and angular velocity. A commanded horizontal acceleration uu produces the nonlinear dynamics

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

The factor −ucos⁡θ/ℓ-u\cos\theta/\ell identifies the action channel. Horizontal base motion couples into angular acceleration, and its sign and magnitude depend on the current configuration. A black-box optimizer could evaluate these equations without inspecting that term, but the term explains why the cart must first move away from its eventual resting position to build pendulum energy.

The numerical experiment uses a 4.5 s horizon with N=30N=30 zero-order-hold controls and a step size h=0.15h=0.15 s. Fourth-order Runge--Kutta integration defines the discrete map xk+1=Fh(xk,uk)\mathbf{x}_{k+1}=F_h(\mathbf{x}_k,u_k). Both numerical formulations solve the same problem:

min⁡x0:N,u0:N−1h∑k=0N−1[0.05pk2+0.01vk2+0.25(1−cos⁡θk)+0.01ωk2+0.004uk2]+20pN2+5vN2+120(1−cos⁡θN)+12ωN2subject toxk+1=Fh(xk,uk),x0=(0,0,π,0),∣pk∣≤2.4,∣vk∣≤4,∣ωk∣≤12,∣uk∣≤8.\begin{aligned} \min_{\mathbf{x}_{0:N},u_{0:N-1}}\quad & h\sum_{k=0}^{N-1}\Bigl[ 0.05p_k^2+0.01v_k^2+0.25(1-\cos\theta_k) +0.01\omega_k^2+0.004u_k^2\Bigr] \\ & {}+20p_N^2+5v_N^2+120(1-\cos\theta_N)+12\omega_N^2 \\ \text{subject to}\quad & \mathbf{x}_{k+1}=F_h(\mathbf{x}_k,u_k), \\ & \mathbf{x}_0=(0,0,\pi,0), \\ & |p_k|\leq 2.4,\quad |v_k|\leq 4,\quad |\omega_k|\leq 12,\quad |u_k|\leq 8. \end{aligned}

The periodic penalty 1−cos⁡θ1-\cos\theta assigns the same terminal cost to angles that differ by a full revolution. Position and velocity penalties still require the cart to finish near rest, so rotating the pole through the top is not enough by itself.

Direct transcription retains all 31 states and 30 controls. It therefore optimizes over 154 scalar variables and imposes 124 scalar equalities, including the initial condition and one four-dimensional dynamics equation per step. The equality Jacobian is block banded because the residual at step kk depends only on (xk,uk,xk+1)(\mathbf{x}_k,u_k,\mathbf{x}_{k+1}).

The small demonstration below passes that Jacobian to SLSQP as a dense array. A large-scale direct solver would instead store and factor the same block-banded pattern sparsely. The formulation exposes sparsity, but exploiting it is a separate implementation choice.

Sequential Methods

Can eliminating the states reduce the nonlinear program without making the resulting long simulation chain too sensitive to early actions?

The previous section showed how a discrete-time optimal control problem can be solved by treating all states and controls as decision variables and enforcing the dynamics as equality constraints. This produces a nonlinear program that can be passed to solvers such as scipy.optimize.minimize with the SLSQP method. For short horizons, this approach is straightforward and works well; the code stays close to the mathematical formulation.

It also has a real advantage: by keeping the states explicit and imposing the dynamics through constraints, we anchor the trajectory at multiple points. This extra structure helps stabilize the optimization, especially for long horizons where small deviations in early steps can otherwise propagate and cause the optimizer to drift or diverge. In that sense, this formulation is better conditioned and more robust than approaches that treat the dynamics implicitly.

The drawback is scale. As the horizon grows, the number of variables and constraints grows with it, and all are coupled by the dynamics. Each iteration of a sequential quadratic programming (SQP) or interior-point method requires building and factorizing large Jacobians and Hessians. These methods have been embedded in reinforcement learning and differentiable programming pipelines, through implicit layers or differentiable convex solvers, but the cost is significant. They remain serial, rely on repeated linear algebra factorizations, and are difficult to parallelize efficiently. When thousands of such problems must be solved inside a learning loop, the overhead becomes prohibitive.

This motivates an alternative that aligns with the computational model of machine learning. For deterministic dynamics, the equality constraints can be eliminated by making the states implicit. Instead of solving for both states and controls, we fix the initial state and roll the system forward under a candidate control sequence. State constraints can remain, but they become nonlinear functions of the entire preceding control sequence. This is the essence of single shooting.

The term “shooting” comes from the idea of aiming and firing a trajectory from the initial state: you pick a control sequence, integrate (or step) the system forward, and see where it lands. If the final state misses the target, you adjust the controls and try again: like adjusting the angle of a shot until it hits the mark. It is called single shooting because we compute the entire trajectory in one pass from the starting point, without breaking it into segments. Later, we will contrast this with multiple shooting, where the horizon is divided into smaller arcs that are optimized jointly to improve stability and conditioning.

The analogy with deep learning is also immediate: the control sequence plays the role of parameters, the rollout is a forward pass, and the cost is a scalar loss. Gradients can be obtained with reverse-mode automatic differentiation. In the single shooting formulation of the DOCP, the constrained program

min⁡x1:T, u1:T−1J(x1:T,u1:T−1)s.t.xt+1=ft(xt,ut)\min_{\mathbf{x}_{1:T},\,\mathbf{u}_{1:T-1}} J(\mathbf{x}_{1:T},\mathbf{u}_{1:T-1}) \quad\text{s.t.}\quad \mathbf{x}_{t+1}=\mathbf{f}_t(\mathbf{x}_t,\mathbf{u}_t)

collapses to

min⁡u1:T−1  cT ⁣(ϕT(u,x1))+∑t=1T−1ct ⁣(ϕt(u,x1),ut),s.t.gt ⁣(ϕt(u,x1),ut)≤0,ulb≤ut≤uub.\min_{\mathbf{u}_{1:T-1}}\; c_T\!\bigl(\boldsymbol{\phi}_{T}(\mathbf{u}, \mathbf{x}_1)\bigr) +\sum_{t=1}^{T-1} c_t\!\bigl(\boldsymbol{\phi}_{t}(\mathbf{u}, \mathbf{x}_1), \mathbf{u}_t\bigr), \quad\text{s.t.}\quad \mathbf{g}_t\!\bigl(\boldsymbol{\phi}_{t}(\mathbf{u},\mathbf{x}_1),\mathbf{u}_t\bigr)\leq 0, \quad \mathbf{u}_{\mathrm{lb}}\le\mathbf{u}_{t}\le\mathbf{u}_{\mathrm{ub}}.

Here ϕt\boldsymbol{\phi}_t denotes the state reached at time tt by recursively applying the dynamics to the previous state and current control. This recursion can be written as

ϕt+1(u,x1)=ft ⁣(ϕt(u,x1),ut),ϕ1=x1.\boldsymbol{\phi}_{t+1}(\mathbf{u},\mathbf{x}_1)= \mathbf{f}_{t}\!\bigl(\boldsymbol{\phi}_{t}(\mathbf{u},\mathbf{x}_1),\mathbf{u}_t\bigr),\qquad \boldsymbol{\phi}_{1}=\mathbf{x}_1.

Concretely, here is JAX-style pseudocode for defining phi(u, x_0, t) using jax.lax.scan with a zero-based time index:

def phi(u_seq, x0, t):
    """Return \phi_t(u, x0) with 0-based t (\phi_0 = x0).

    u_seq: controls of length T (or T-1); only first t entries are used
    x0: initial state at time 0
    t: integer >= 0
    """
    if t <= 0:
        return x0

    def step(carry, u):
        x, t_idx = carry
        x_next = f(x, u, t_idx)
        return (x_next, t_idx + 1), None

    (x_t, _), _ = lax.scan(step, (x0, 0), u_seq[:t])
    return x_t

The pattern mirrors an RNN unroll: starting from an initial state (x1⋆\mathbf{x}^\star_1) and a sequence of controls (u1:T−1∗\mathbf{u}^*_{1:T-1}), we propagate forward through the dynamics, updating the state at each step and accumulating cost along the way. This structural similarity is why single shooting often feels natural to practitioners with a deep learning background: the rollout is a forward pass, and gradients propagate backward through time exactly as in backpropagation through an RNN.

Algorithmically:

In JAX or PyTorch, this loop can be compiled and differentiated automatically. The control sequence plays the role of trainable parameters, while the simulated trajectory is the forward computation. Reverse-mode differentiation of that computation gives ∇J(u)\nabla J(\mathbf{u}).

Single shooting is attractive for its simplicity and compatibility with differentiable programming, but it has limitations. Early controls influence every later state through a long product of dynamics Jacobians. This can make gradients poorly conditioned over long horizons. State constraints also lose their local sparse representation because each constrained state depends on all earlier controls. Formulations that keep selected states explicit, such as multiple shooting or collocation, shorten these dependency chains.

Matched Swing-Up Comparison

The direct-transcription and single-shooting implementations below use the cart-pole problem stated above without changing the model, cost, limits, horizon, initial control guess, or nonlinear-programming solver. Only the decision variables and the representation of the dynamics differ.

Figure 1:Direct transcription and single shooting solve the same nonlinear cart-pole problem from the same initialization. Both reach the upright configuration and respect the matched limits, but they converge to different local solutions. Direct transcription retains 154 scalar variables and 124 local dynamics equalities; single shooting retains only the 30 controls and reconstructs every state by forward simulation.

method                variables  eqs  iterations  objective  final angle  defect
direct transcription    154  124         413      1.180      0.002 deg  1.3e-07
single shooting          30    0         430      2.675      0.130 deg  0.0e+00
<Figure size 1080x352.5 with 3 Axes>
<Figure size 1080x352.5 with 3 Axes>

Both solvers produce a successful open-loop swing-up. The direct formulation reaches a lower objective in this fixed run, while single shooting uses a much smaller decision vector. This numerical outcome does not establish that direct transcription always finds better solutions. It exposes a concrete trade-off: eliminating variables shortens the program but lengthens the dependency from an early control to the terminal cost and later constraints.

Figure 2:The two trajectories are generated by the same nonlinear RK4 plant. The pole starts downward and reaches the upright configuration while the cart remains inside the 2.4 m rail limits. Animation frames are computed from the optimized state trajectories; no browser-side simulator is used.

Loading...

The comparison also separates optimization from feedback. Each optimizer returns one fixed control sequence for one assumed initial state. To test what that object can and cannot do, the next replay applies the direct-transcription controls twice. One realization follows the nominal model. The other receives an additional cart acceleration of 1  m s−21\;\mathrm{m\,s^{-2}} for one 0.15 s step at t=2.1t=2.1 s, after which both realizations receive the same remaining commands.

Figure 3:A one-step unmodeled acceleration separates two realizations driven by the same open-loop controls. The nominal trajectory reaches normalized pole height cos⁡θ=1\cos\theta=1; the disturbed trajectory finishes below the horizontal. The optimizer has produced a plan, not a rule that reacts to the observed state.

<Figure size 1080x352.5 with 2 Axes>

Feedback changes the object being computed. A feedback controller maps the state observed after the disturbance to a new action. Model predictive control will obtain such a map by repeatedly solving trajectory problems, while dynamic programming will construct state-contingent decisions through the value function.

Inspect the shared nonlinear dynamics
cartpole_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def cartpole_dynamics(
    state: np.ndarray,
    acceleration: float,
    parameters: CartPoleParameters = CartPoleParameters(),
) -> np.ndarray:
    """Continuous nonlinear dynamics with angle measured from upright."""

    position, velocity, angle, angular_velocity = np.asarray(state, dtype=float)
    del position
    angular_acceleration = (
        parameters.gravity * np.sin(angle)
        - acceleration * np.cos(angle)
    ) / parameters.pole_length - parameters.angular_damping * angular_velocity
    return np.array(
        [velocity, acceleration, angular_velocity, angular_acceleration],
        dtype=float,
    )

Download the complete cart-pole trajectory-optimization and control source.

In Between Sequential and Simultaneous

Can selected boundary states shorten those sensitivity paths while preserving local simulation inside each segment?

The two formulations we have seen so far lie at opposite ends. The full discretization approach keeps every state explicit and enforces the dynamics through equality constraints, which makes the structure clear but leads to a large optimization problem. At the other end, single shooting removes these constraints by simulating forward from the initial state, leaving only the controls as decision variables. That makes the problem smaller, but it also introduces a long and highly nonlinear dependency from the first control to the last state.

Multiple shooting sits in between. Instead of simulating the entire horizon in one shot, we divide it into smaller segments. For each segment, we keep its starting state as a decision variable and propagate forward using the dynamics for that segment. At the end, we enforce continuity by requiring that the simulated end state of one segment matches the decision variable for the next.

Formally, suppose the horizon of TT steps is divided into KK segments of length LL (with T=K⋅LT = K \cdot L for simplicity). We introduce:

Given xk\mathbf{x}_k and the controls in its segment, we compute the predicted terminal state by simulating forward:

x^k+1=Φ(xk,usegment k),\hat{\mathbf{x}}_{k+1} = \Phi(\mathbf{x}_k,\mathbf{u}_{\text{segment }k}),

where Φ\Phi represents LL applications of the dynamics. Continuity constraints enforce:

xk+1−x^k+1=0,k=1,…,K−1.\mathbf{x}_{k+1} - \hat{\mathbf{x}}_{k+1} = 0, \qquad k=1,\dots,K-1.

The resulting nonlinear program looks like this:

min⁡{xk,ut}cT(xT)+∑t=1T−1ct(xt,ut)subject toxk+1−Φ(xk,usegment k)=0,k=1,…,K−1,ulb≤ut≤uub,boundary conditions on x1 and xK.\begin{aligned} \min_{\{\mathbf{x}_k,\mathbf{u}_t\}} \quad & c_T(\mathbf{x}_T) + \sum_{t=1}^{T-1} c_t(\mathbf{x}_t,\mathbf{u}_t) \\ \text{subject to} \quad & \mathbf{x}_{k+1} - \Phi(\mathbf{x}_k,\mathbf{u}_{\text{segment }k}) = 0,\quad k = 1,\dots,K-1, \\ & \mathbf{u}_{\mathrm{lb}} \le \mathbf{u}_t \le \mathbf{u}_{\mathrm{ub}}, \\ & \text{boundary conditions on } \mathbf{x}_1 \text{ and } \mathbf{x}_K. \end{aligned}

Compared to the full NLP, we no longer introduce every intermediate state as a variable, only the anchors at segment boundaries. Inside each segment, states are reconstructed by simulation. Compared to single shooting, these anchors break the long dependency chain that makes optimization unstable: gradients only have to travel across LL steps before they hit a decision variable, rather than the entire horizon. This is the same reason why exploding or vanishing gradients appear in deep recurrent networks: when the chain is too long, information either dies out or blows up. Multiple shooting shortens the chain and improves conditioning.

By adjusting the number of segments KK, we can interpolate between the two extremes: K=1K = 1 gives single shooting, while K=TK = T recovers the full direct NLP. In practice, a moderate number of segments often strikes a good balance between robustness and complexity.

Source
Loading...

Example: Hydro Cascade Scheduling with Physical Routing

The ballistic boundary-value problem couples consecutive segments of one trajectory. A hydroelectric cascade adds a second form of coupling: actions taken upstream alter the inflows seen downstream after a travel delay. Multiple shooting exposes both forms through local ODE integrations, temporal continuity defects, and inter-reach routing constraints.

The hydro-reservoir model in Finite-Horizon Dynamic Programming uses a discrete-time abstraction in which precipitation enters as a noisy inflow. That abstraction is useful for learning and control design, but it omits much of the physical behavior of rivers and dams. Here we use a more detailed setup inspired by Savorgnan et al., 2011. We consider a series of dams arranged in a cascade, where the actions taken upstream influence downstream levels with a delay. The amount of power produced depends on the water flow through the turbines and the head (the vertical distance between the reservoir surface and the turbine outlet). The larger the head, the more potential energy is available for conversion into electricity, and the higher the power output.

To capture these effects, we follow a modeling approach inspired by the Saint-Venant equations, which describe how water levels and flows evolve in open channels. Instead of solving the full PDEs, we use a reduced model that approximates each dammed section of river (called a reach) as a lumped system governed by an ordinary differential equation. The main variable of interest is the water level hr(t)h_r(t), which changes over time depending on how much water enters, how much is discharged through the turbines qr(t)q_r(t), and how much is spilled sr(t)s_r(t). The mass balance for reach rr is written as:

dhr(t)dt=1Ar(zr(t)−qr(t)−sr(t)),\frac{d h_r(t)}{dt} = \frac{1}{A_r} \left( z_r(t) - q_r(t) - s_r(t) \right),

where ArA_r is the surface area of the reservoir, assumed constant. The inflow zr(t)z_r(t) to a reach either comes from nature (for the first dam), or from the upstream turbine and spill discharge, delayed by a travel time τr−1\tau_{r-1}:

z1(t)=inflow(t),zr(t)=qr−1(t−τr−1)+sr−1(t−τr−1),for r>1.z_1(t) = \text{inflow}(t), \qquad z_r(t) = q_{r-1}(t - \tau_{r-1}) + s_{r-1}(t - \tau_{r-1}), \quad \text{for } r > 1.

Power generation at each reach depends on how much water is discharged and the available head:

Pr(t)=ρgη qr(t) Hr(hr(t)),P_r(t) = \rho g \eta \, q_r(t) \, H_r(h_r(t)),

where ρ\rho is water density, gg is gravitational acceleration, η\eta is turbine efficiency, and Hr(hr(t))H_r(h_r(t)) denotes the head as a function of the water level. In some models, the head is approximated as the difference between the current level and a fixed tailwater height (the water level downstream of the dam, after it has passed through the turbine).

The operator’s goal is to meet a target generation profile Pref(t)P^\text{ref}(t), such as one dictated by a market dispatch or load-following constraint. This leads to an objective that minimizes the deviation from the target over the full horizon:

min⁡{qr(t),sr(t)}∫0T(∑r=1RPr(t)−Pref(t))2dt.\min_{\{q_r(t), s_r(t)\}} \int_0^T \left( \sum_{r=1}^R P_r(t) - P^\text{ref}(t) \right)^2 dt.

In practice, this is combined with operational constraints: turbine capacity 0≤qr(t)≤qˉr0 \le q_r(t) \le \bar{q}_r, spillway limits 0≤sr(t)≤sˉr0 \le s_r(t) \le \bar{s}_r, and safe level bounds hrmin⁡≤hr(t)≤hrmax⁡h_r^{\min} \le h_r(t) \le h_r^{\max}. Depending on the use case, one may also penalize spill to encourage water conservation, or penalize fast changes in levels for ecological reasons.

The reaches are coupled across space and time. An upstream reach cannot simply act in isolation: if the operator wants reach rr to produce power at a specific time, the water must be released by reach r−1r-1 sufficiently in advance. This coordination is further complicated by delays, nonlinearities in head-dependent power, and limited storage capacity.

We solve the problem using multiple shooting. Each reach is divided into local simulation segments over short time windows. Within each segment, the dynamics are integrated forward using the ODEs, and continuity constraints are added to ensure that the water levels match across segment boundaries. At the same time, the inflows passed from upstream reaches must arrive at the right time and be consistent with previous decisions. In discrete time, this gives rise to a set of state-update equations:

hrk+1=hrk+Δt⋅1Ar(zrk−qrk−srk),h_r^{k+1} = h_r^k + \Delta t \cdot \frac{1}{A_r}(z_r^k - q_r^k - s_r^k),

with delays handled by shifting zrkz_r^k according to the appropriate travel time. These constraints are enforced as part of a nonlinear program, alongside the power tracking objective and control bounds.

Compared with a single-reservoir inflow-outflow model, the cascade adds delayed coupling constraints. Upstream reservoirs can store water in anticipation of future needs, while downstream dams adjust their output to match arrivals and avoid overflows. The resulting schedule coordinates the entire system against the demand profile.

Source

Figure 4:Multiple shooting coordinates reservoir levels, turbine discharges, routed inflows, and total generation across a three-reach hydroelectric cascade.

The figure shows the result of a multiple-shooting optimization applied to a three-reach hydroelectric cascade. The time horizon is discretized into 16 intervals, and SciPy’s trust-constr solver is used to find a feasible control sequence that satisfies mass balance, turbine and spillway limits, and Muskingum-style routing dynamics. Each reach integrates its own local ODE. Shooting defects link reservoir levels across time, while separate Muskingum constraints link routed flows between reaches.

The top-left panel shows the water levels in each reservoir. We observe that upstream reservoirs tend to increase their levels ahead of discharge events, building potential energy before releasing water downstream. The top-right panel shows turbine discharges for each reach. These vary smoothly and are temporally coordinated across the system. The bottom-right panel compares the total generation to a synthetic demand profile, which is generated by a sum of time-shifted sigmoids and normalized to be feasible given turbine capacities. The optimized schedule (orange) tracks this demand closely, while the initial guess (blue) lags behind. The bottom-left panel plots the routed inflows between reaches, which display the expected lag and smoothing effects from Muskingum routing. The interplay between these plots shows how the system anticipates, stores, and routes water to meet time-varying generation targets within physical and operational limits.

The ballistic and hydro examples use the same numerical structure at different scales: integrate locally, expose states at segment boundaries, and drive every continuity defect to zero. We now return to the first-order optimality conditions of the underlying discrete-time program.

Summary and Outlook

Direct transcription keeps states and controls explicit and exposes sparse dynamics constraints. Single shooting eliminates the states but couples early actions to every later quantity through one rollout. Multiple shooting keeps selected boundary states, trading additional variables for shorter sensitivity paths and sparse continuity defects.

The next chapter, iLQR and differential dynamic programming, uses this temporal structure to solve successive local quadratic problems by backward elimination. Its boat-docking example follows both the optimizer’s iterations and the vessel’s predicted motion along a selected plan.

Continuous-time transcription and collocation develops nodal polynomial representations directly from differential equations.

Exercises



Self-checks

References
  1. Savorgnan, C., Romani, C., Kozma, A., & Diehl, M. (2011). Multiple shooting for distributed systems with applications in hydro electricity production. Journal of Process Control, 21(5), 738–745. 10.1016/j.jprocont.2011.01.011