Part of World Models Handbook
Explains how sampling-based planning and model predictive control turn black-box world models into controllers, using random shooting, CEM.
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Sampling-Based Planning and Model Predictive Control
Imagine a delivery drone waiting at the edge of a windy courtyard. Its world model is a neural network trained on logs from previous flights. The network accepts a state and an action and returns the next state, but it is a black box. With piecewise-linear activations, its input-output map may have derivative discontinuities; its gradients may also be noisy or uninformative in regions poorly covered by training data. More fundamentally, it was trained to predict, not to be optimized. A next-state prediction loss has only an indirect relationship to the outcome we care about: whether the drone reaches the landing pad. How do you use the model to pick what to do?
Learned world models often make this problem more visible than hand-derived dynamics. Some simulators expose analytic derivatives and structure that an optimizer can exploit; others are black boxes too. A differentiable learned model supplies derivatives through backpropagation, but derivatives evaluated on out-of-distribution states can be technically well-defined and still unreliable for planning. A gradient-based planner inherits that weakness.
The core insight of this chapter is that you do not need to differentiate the model to plan with it. You only need to be able to run it forward. Given a candidate sequence of controls, you can roll the model out step by step and ask, "what would happen if I did this?" Then you can compare candidate sequences by the cost of the trajectories they produce and pick the best one. This is sampling-based planning: convert control into search, and search over action sequences by simulation.
The idea is old and appears in several related forms. Evolutionary optimization searches over populations of candidate policies or plans. Shooting methods in optimal control generate trajectories by propagating candidate control sequences through the dynamics. Path-integral methods form a more specific stochastic-control family with additional assumptions and importance-weighted updates. Their shared practical trade is narrower than an equivalence: derivative-free search can query a forward model without requiring analytic gradients, but it offers no general guarantee of finding a global optimum. When model rollouts can be batched efficiently, that trade is often useful for learned dynamics.
A common way to deploy this idea is model predictive control (MPC). Instead of committing to a long plan produced once, MPC plans over a finite horizon, executes the first action or a short block, observes the result, and replans from the new state. This closes the loop between the model and the environment, allowing feedback to correct some disturbances and model mismatch. Receding-horizon control long predates learned models and is widely used in industrial process control, as documented in Qin and Badgwell's industrial survey. Learned-model MPC changes the model inside the loop while retaining the same basic feedback structure.
In this chapter we build up this family of methods from the ground floor. We start with the simplest possible planner, random shooting, which samples many action sequences uniformly and keeps the best. We then introduce the cross-entropy method (CEM), which iteratively narrows a sampling distribution around the actions that worked. From there we assemble a full model predictive controller and see how replanning can compensate for some model error and disturbance. We then look at the design decisions that decide whether an MPC stack works in practice: how long the planning horizon should be, how often to replan, how to enforce constraints, how to warm-start the optimizer from the previous step's plan, and how to fit all of this inside a real-time control budget.
Building on the optimal-control foundations from Part II, this chapter treats MPC as a practical, sampling-based realization of the receding-horizon principle. It sits at the boundary between the pure predictive objectives of Part VI and the learned-policy imagination methods we will meet in Part VII, Chapter 4 and the decision-centric research lineages of Part VIII. Everything here assumes you have a forward model, either a hand-written simulator or a learned one. The chapter is about how to wield it.
It is worth being explicit about how this material lines up with a classical model-based control pipeline. System identification estimates dynamics from input-output data; state estimation enters when the state is not directly measured; and an optimal-control method chooses actions against that model and estimate. Under linear dynamics, quadratic objectives, and convex constraints, an MPC subproblem is often a quadratic program. Learned-model variants retain the decomposition but may replace physical states with latent states and structured dynamics with flexible function approximators. These are tendencies, not a clean classical-versus-learned dichotomy: classical models can be nonlinear and high-dimensional, while learned models can be smooth and structured. The relevant solver depends on the assumptions that the particular model, objective, and constraints satisfy.
One clarification helps before we start. When we say a model is "good," we mean it is good for a specific purpose, and it is worth separating four properties that are often blurred together.
- Predictive fidelity is how accurately the model's one-step or multi-step rollout matches the true system on states and actions that the training distribution covers. This is what a supervised loss measures, and it is the property most papers report.
- Representation quality is whether the model's internal state carries the information a planner needs, even if that representation is not a physically meaningful state vector. A latent model can have excellent representation quality while its decoded states look strange or discontinuous.
- Uncertainty quality is whether the model knows what it does not know, meaning that regions of state and action space far from the training data are flagged as uncertain instead of being confidently extrapolated.
- Decision usefulness is whether the plans that optimize against the model, once executed, make progress on the task. This is the property MPC needs, and it is not implied by the other three.
A model can have high average predictive fidelity and still be poor for planning, because optimization may select action sequences in regions where the model is systematically optimistic. Conversely, a model with mediocre average fidelity but accurate task-relevant local predictions can still support useful control. Sampling-based MPC exposes this separation directly: its decisive test is closed-loop performance under the planner's induced state-action distribution, not prediction loss alone.
Random Shooting and Cross-Entropy Methods
Random shooting is the most stripped-down planner you can write. You fix a planning horizon , which is the number of future steps you intend to consider. You fix a control distribution, usually uniform over the admissible action set . You draw independent action sequences,
roll each sequence through the model to obtain a predicted trajectory , evaluate a cost , and return the sequence with the lowest cost. An open-loop controller may execute the whole sequence. When random shooting is used inside MPC, only the first action or a short block is executed before replanning. That is the entire search algorithm.
Notice what is absent from this recipe. There is no gradient, no Hessian, no linearization, no convexity assumption, and no requirement that the model be differentiable at all. The planner treats the dynamics as an opaque function mapping a state and an action to a next state. It queries that function only in the forward direction. In optimization language this is zeroth-order or derivative-free optimization: it uses function values alone to search. This single property is what makes random shooting compatible with trained neural networks, with simulators that contain lookup tables or branch logic, and with physical systems that can only be probed by experiment. It is also why the method scales so well with parallel hardware: every one of the rollouts is independent, so the whole batch can be evaluated on a GPU at once.
The reason this works at all is that a model rollout may be cheap relative to acting in the real world. The limitation is the curse of dimensionality of action-sequence space: a regular grid with resolution along each coordinate contains length- sequences for an action dimension . A 10-step horizon with two action coordinates and 100 levels per coordinate would contain grid points. Random sampling avoids enumerating that grid but does not remove the dimensional dependence. For a regular grid with points in a -dimensional unit box, the per-coordinate spacing scales as . Random samples do not form a regular grid, so this curve is a resolution heuristic rather than an exact nearest-neighbor law; it still exposes the exponential dependence on sequence-space dimension.
The shape of that heuristic is worth seeing. The cell below evaluates over five decades of point budget for three sequence-space dimensions and plots the result on log-log axes.
import numpy as np
# Regular-grid spacing along one coordinate scales as N^(-1/d), where
# d = H * d_a is the dimension of action-sequence space. These are analytical
# heuristic curves, not sampled measurements.
sample_budgets = np.unique(np.round(np.logspace(1, 5, 80)).astype(int))
sequence_dims = [2, 10, 20] # (H=1, d_a=2), (H=5, d_a=2), (H=10, d_a=2)
spacing_curves = {
dim: sample_budgets.astype(float) ** (-1.0 / dim) for dim in sequence_dims
}
Multiplying the grid-point budget by reduces this spacing by a factor of 100 in two dimensions, but only by a factor of about 1.58 in twenty dimensions. The calculation is the argument: holding coordinate resolution fixed requires a budget exponential in dimension.
The practical consequence is that uniform sampling becomes inefficient as horizons and action dimensions grow. Whether it remains adequate depends on task structure, action parameterization, and compute budget.
CEM replaces the fixed proposal with a parametric distribution refitted to the lowest-cost samples in each current batch. In the diagonal-Gaussian form used here, every time-action coordinate has a mean and standard deviation and is sampled independently before clipping. The refitted mean moves toward the current elites, while the spread may contract or expand coordinate by coordinate. In the version implemented below:
- Initialize and .
- For iteration :
- Draw samples , clipped to .
- Evaluate each sample's cost .
- Select the top samples by lowest cost.
- Set and to the mean and standard deviation of those elites.
- Return as the plan.
The reason this works is the general elite-set idea of evolutionary methods. The elite samples identify a region with low observed cost, and refitting narrows the proposal distribution toward that region. Because the standard deviation usually shrinks, subsequent samples concentrate more finely on promising actions. The name "cross-entropy" comes from choosing a sampling distribution that is close in Kullback-Leibler divergence to an ideal rare-event or elite distribution, as developed in de Boer et al.'s tutorial. In the finite-sample Gaussian version used here, maximum-likelihood fitting to elites produces their sample mean and standard deviation. This is iterative distribution fitting; it should not be treated as literal projected gradient ascent on the original objective.
This iterative refitting distinguishes CEM from one larger random batch. Random shooting spends its budget on independent samples from a fixed proposal; CEM uses earlier evaluations to change later proposals. This can help when a representable low-cost region occupies little of the original proposal. It can hurt when elites span unrelated modes, because a single fitted Gaussian may average them into a poor region. A smaller elite fraction, explicit smoothing, covariance control, or a mixture proposal can change that behavior, but each must be tuned empirically.
Model Predictive Path Integral control (MPPI) is a related sampling controller that perturbs a nominal control sequence and forms a cost-weighted update. In a simplified presentation, lower-cost rollouts receive weights proportional to ; the full derivation also contains sampling-correction terms. Williams, Aldrich, and Theodorou derive MPPI under stochastic-control and generalized importance-sampling assumptions. CEM instead fits its proposal to a hard elite set. Their relative performance depends on dynamics, objective, proposal, hyperparameters, and budget; neither update is uniformly more robust to a "rugged" cost surface.
Let us set up the running example. Our system is a 2D double integrator, a simplified model for a point mass commanded by acceleration in two Cartesian axes. The state is and the action is an acceleration command, bounded by . The discrete-time dynamics are
and identically in . The cost encodes our intent. To reach a goal we penalize squared distance from over the trajectory plus a small penalty on control effort:
A few remarks on why this example is worth the space. Position-only feedback does not directly damp the double integrator's velocity, so reaching a target smoothly requires accelerating and then decelerating. The displayed update is the exact one-step solution of continuous constant-acceleration dynamics when the action is held fixed over each interval. Because the nominal dynamics are available in closed form, we can use them as the planner's model and separately perturb the simulated plant. That isolates the effect of replanning under a controlled model-plant mismatch. The effort term with weight discourages unnecessarily large or oscillatory controls; it does more than merely break exact ties.
Let us implement this system and the two planners.
import time
import matplotlib.pyplot as plt
import numpy as np
DT = 0.1
U_MAX = 1.0
EFFORT_WEIGHT = 0.05
def batched_rollout(states, actions, dt=DT):
"""states: (B, 4), actions: (B, T, 2) -> trajectories (B, T+1, 4)."""
B, T, _ = actions.shape
s = states.astype(float).copy()
out = np.empty((B, T + 1, 4))
out[:, 0] = s
for t in range(T):
a = actions[:, t]
x, y, vx, vy = s[:, 0], s[:, 1], s[:, 2], s[:, 3]
s = np.stack(
[
x + vx * dt + 0.5 * a[:, 0] * dt**2,
y + vy * dt + 0.5 * a[:, 1] * dt**2,
vx + a[:, 0] * dt,
vy + a[:, 1] * dt,
],
axis=1,
)
out[:, t + 1] = s
return out
def batched_cost(states, actions, goal):
"""Trajectory cost, evaluated in parallel over a batch of candidate plans."""
traj = batched_rollout(states, actions)
pos_err = np.sum(
(traj[:, 1:, :2] - np.asarray(goal)[None, None, :]) ** 2, axis=(1, 2)
)
effort = EFFORT_WEIGHT * np.sum(actions**2, axis=(1, 2))
return pos_err + effort, trajTwo small comments on the helpers. First, we batch everything. A planner that evaluates thousands of candidate sequences per control tick benefits strongly from vectorized rollout, so we operate on arrays of shape (B, T, 2). A Python loop over candidate plans would add interpreter overhead and would make the later timing comparison measure implementation style as much as planner structure. Second, the cost is defined on the equally spaced trajectory returned by batched_rollout. In a learned system, this function could call a neural transition model instead of the analytic equations. A replacement model can use the same planner interface when it matches the expected batch shapes, state semantics, time step, and output convention.
Now the two planners. Random shooting is a one-liner wrapped in a function.
def random_shooting(
state, goal, horizon, n_samples=1000, u_max=U_MAX, rng=None
):
if rng is None:
rng = np.random.default_rng(0)
actions = rng.uniform(-u_max, u_max, size=(n_samples, horizon, 2))
states = np.tile(np.asarray(state, dtype=float)[None, :], (n_samples, 1))
values, _ = batched_cost(states, actions, goal)
best_idx = int(np.argmin(values))
return actions[best_idx], float(values[best_idx]), valuesRead the function from top to bottom and you see the whole algorithm in four lines: sample, roll out, evaluate, return the argmin. The returned cost array supports diagnostics, but its shape needs context. A flat high-cost batch suggests the proposal found no useful distinction; a flat low-cost batch may mean that many plans work. A single low outlier can be a better plan or a noisy or model-exploiting anomaly, so it should be checked rather than trusted automatically.
The cross-entropy method shares the same evaluation loop but keeps the sample distribution as explicit state. The keyword mu_init lets a caller provide a warm-start mean; we will use it later for MPC.
def cem(
state,
goal,
horizon,
n_samples=500,
n_elite=50,
n_iters=8,
u_max=U_MAX,
rng=None,
mu_init=None,
sigma_init=None,
cost_fn=None,
):
if rng is None:
rng = np.random.default_rng(0)
if cost_fn is None:
def cost_fn(states, actions):
return batched_cost(states, actions, goal)
mu = (
np.zeros((horizon, 2))
if mu_init is None
else np.array(mu_init, dtype=float)
)
sigma = (
np.full((horizon, 2), u_max)
if sigma_init is None
else np.array(sigma_init, dtype=float)
)
states = np.tile(np.asarray(state, dtype=float)[None, :], (n_samples, 1))
history = []
for _ in range(n_iters):
actions = rng.normal(
mu[None, :, :], sigma[None, :, :], size=(n_samples, horizon, 2)
)
actions = np.clip(actions, -u_max, u_max)
values, _ = cost_fn(states, actions)
elite_idx = np.lexsort((np.arange(n_samples), values))[:n_elite]
elites = actions[elite_idx]
mu = elites.mean(axis=0)
sigma = elites.std(axis=0) + 1e-4
history.append(float(values.min()))
best = np.clip(mu, -u_max, u_max)
return best, mu, np.array(history)There are three implementation details worth dwelling on because they are easy to get wrong. The first is the cost_fn indirection. Passing the cost as a callable, rather than hard-coding the goal-distance cost, is what lets us later bolt on obstacle penalties and other constraint terms without touching the planner. The planner's job is to minimize whatever scalar the cost function returns; it has no opinion about what that scalar means.
The second is the np.clip inside the sample loop. Without it, the elite set could contain inadmissible actions. Clipping keeps evaluated controls inside the box but creates point mass at its boundaries: the elite moments are fitted to clipped samples while the next batch comes from an unclipped normal. When substantial proposal mass lies outside the action box, a truncated distribution or bounded reparameterization avoids that mismatch.
The third is the tiny constant 1e-4 added to the standard deviation. It prevents the distribution from collapsing to a point, which would make every subsequent sample identical and freeze the search, and it also prevents division-by-zero type pathologies in any downstream code that uses sigma. The value is a floor on exploration; if the planner needs to keep searching aggressively, it should be larger, and if the planner should commit to the plan it has found, it should be smaller.
The concentration that the mean and standard deviation updates produce is worth seeing directly. The cell below runs a short CEM with a fixed seed and records the first-step action samples at the first and fourth iterations, together with the elite mean fitted at each. Ties inside the elite set are broken by lowest cost, then lowest sample index.
# A short CEM run recorded online so the narrowing of the sampling distribution
# is visible. State, horizon, seed, sample budget, and elite count are fixed; the
# elite set is the lowest-cost fraction with ties broken by lowest sample index.
cem_demo_state = np.zeros(4)
cem_demo_goal = np.array([3.0, 2.0])
cem_demo_horizon = 10
cem_demo_n = 400
cem_demo_elite = 40
cem_demo_rng = np.random.default_rng(5)
cem_demo_mu = np.zeros((cem_demo_horizon, 2))
cem_demo_sigma = np.full((cem_demo_horizon, 2), U_MAX)
cem_demo_rounds = []
for _ in range(4):
cem_demo_samples = cem_demo_rng.normal(
cem_demo_mu[None, :, :],
cem_demo_sigma[None, :, :],
size=(cem_demo_n, cem_demo_horizon, 2),
)
cem_demo_samples = np.clip(cem_demo_samples, -U_MAX, U_MAX)
cem_demo_states = np.tile(cem_demo_state[None, :], (cem_demo_n, 1))
cem_demo_vals, _ = batched_cost(
cem_demo_states, cem_demo_samples, cem_demo_goal
)
cem_demo_elite_idx = np.lexsort((np.arange(cem_demo_n), cem_demo_vals))[
:cem_demo_elite
]
cem_demo_rounds.append(
{
"samples": cem_demo_samples[:, 0, :].copy(),
"fitted_mu": cem_demo_samples[cem_demo_elite_idx]
.mean(axis=0)[0]
.copy(),
}
)
cem_demo_mu = cem_demo_samples[cem_demo_elite_idx].mean(axis=0)
cem_demo_sigma = cem_demo_samples[cem_demo_elite_idx].std(axis=0) + 1e-4
Let us now compare the two planners on a single starting state. The start is the origin, the goal is , and the horizon is 25 steps, which at s is 2.5 seconds of simulated time.
state0 = np.array([0.0, 0.0, 0.0, 0.0])
goal = np.array([3.0, 2.0])
PLAN_HORIZON = 25
RS_SAMPLES = 500
CEM_SAMPLES = 800
CEM_ELITE = 80
CEM_ITERS = 15
rs_actions, rs_best, _ = random_shooting(
state0,
goal,
PLAN_HORIZON,
n_samples=RS_SAMPLES,
rng=np.random.default_rng(0),
)
cem_actions, _, cem_history = cem(
state0,
goal,
PLAN_HORIZON,
n_samples=CEM_SAMPLES,
n_elite=CEM_ELITE,
n_iters=CEM_ITERS,
rng=np.random.default_rng(1),
)
rs_traj = batched_rollout(state0[None, :], rs_actions[None, :, :])[0]
cem_traj = batched_rollout(state0[None, :], cem_actions[None, :, :])[0]
rs_cost_val = float(
batched_cost(state0[None, :], rs_actions[None, :, :], goal)[0][0]
)
cem_cost_val = float(
batched_cost(state0[None, :], cem_actions[None, :, :], goal)[0][0]
)Random shooting best cost after 500 samples: 250.931 CEM (15 iterations, 800 samples each) returned mean-plan cost: 158.677 Ratio of costs (CEM / RS): 0.632
In this fixed-seed run, CEM achieves a lower objective value than random shooting. The accounting matters: random shooting evaluates 500 sequences, while CEM evaluates , so this is a mechanism demonstration rather than an efficiency benchmark. A fair efficiency comparison would equalize wall-clock time or model evaluations and repeat across seeds. Here the paths show the plans selected under the stated, unequal budgets; the objective values establish only which plan scores lower under this chapter's cost function.


The figure records a cost advantage for this run, and the right panel shows the lowest sampled cost in each CEM iteration. A steep early drop followed by a plateau is consistent with the proposal locating and refining one low-cost region. A curve still falling at the budget boundary suggests that extra iterations are worth testing. A flat, high curve is less diagnostic: poor initialization, inadequate proposal variance, too few samples, an unsuitable elite fraction, or a flat objective can all produce it.
The elite fraction is one knob behind that diagnostic. The sweep below holds the start state, horizon, sample budget, iteration count, and pseudorandom seed fixed, changing the number of elites. Within this deterministic implementation, the configured independent variable is the fraction; the result is still one optimizer seed on one task. Ties are ordered by cost and then sample index.
# Elite-fraction sweep on the single-state planning problem. Start state,
# horizon, sample budget, iteration count, and seed are fixed so that any change
# in plan cost is attributable to the elite fraction alone.
elite_fractions = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5]
ELITE_SWEEP_SAMPLES = 2000
ELITE_SWEEP_ITERS = 15
elite_sweep_costs = []
for frac in elite_fractions:
n_elite = max(1, int(round(frac * ELITE_SWEEP_SAMPLES)))
plan_frac, _, _ = cem(
state0,
goal,
PLAN_HORIZON,
n_samples=ELITE_SWEEP_SAMPLES,
n_elite=n_elite,
n_iters=ELITE_SWEEP_ITERS,
rng=np.random.default_rng(4),
)
cost_frac = float(
batched_cost(state0[None, :], plan_frac[None, :, :], goal)[0][0]
)
elite_sweep_costs.append(cost_frac)
elite_fractions = np.array(elite_fractions)
elite_sweep_costs = np.array(elite_sweep_costs)
Two practical notes before we move on. First, the split between samples and CEM iterations matters, not just their product. Iterations let the proposal migrate, whereas samples improve each empirical elite estimate; too few samples can make that estimate unstable. Which split is better therefore depends on dimension, elite fraction, objective, and overhead. Second, in a deterministic open-loop setting, both planners produce a plan calibrated to one starting state. That plan is not a feedback controller: it does not react to disturbances, model error, or unexpected observations. To close the loop, we need MPC.
Model Predictive Control
Model predictive control is what you get when you take the horizon-restricted planner we just built and run it repeatedly in a closed loop. The formal problem statement is the following constrained, finite-horizon optimal control problem, solved from the measured state at each control tick:
The pieces are:
- Stage cost : the running penalty for being at state and applying control . In our example, .
- Terminal cost : an additional penalty on the last state, which shapes the behavior of the plan's tail.
- Dynamics constraint : the model. This is the only place the model enters the optimization.
- Control set : allowed control values.
- State set : allowed states. In the double integrator, this could be a velocity limit or a region of the plane the robot must stay in.
Each piece deserves a sentence of motivation, because the design of these terms is where most MPC engineering happens. The stage cost is the only term that expresses what it means to do the task well at every intermediate moment; if it is misspecified, the controller optimizes the wrong thing confidently. The terminal cost matters more than its small footprint suggests, because it is the sole representation of everything that happens after the horizon. A well-chosen terminal cost can substitute for a long horizon, and a poorly chosen one can bias the controller into a bad habit, for example a terminal cost that rewards speed at the end of the plan produces controllers that arrive fast and overshoot. The dynamics constraint is where the model lives, and it is written as an equality because the plan must be consistent with the model; a plan that violates the dynamics is not a plan at all, it is a wish. The control set bounds what actuators can physically do. The state set encodes safety and feasibility requirements, such as keeping a drone inside a geofence or a velocity below a structural limit.
Written this way, the problem has control variables in a single-shooting formulation; a multiple-shooting formulation also represents states explicitly and enforces dynamics with equality constraints. Linear dynamics, a convex quadratic objective, and linear constraints produce a convex quadratic program. Stability guarantees for MPC require additional conditions, such as suitable terminal ingredients, invariant sets, or sufficiently long horizons; they do not follow merely from using a quadratic program. Mayne et al.'s review develops these conditions for constrained MPC. With a learned nonlinear or non-differentiable model, sampling-based single shooting enforces the dynamics by rollout rather than by placing state-transition equalities in the numerical optimizer.
The MPC algorithm is:
- Observe or estimate the current state .
- Approximately solve the finite-horizon problem for a candidate action sequence .
- Apply only the first action to the real system.
- Advance time by one step, observe the new state , and go back to step 2.
Applying the first action and solving again at the next state is the receding-horizon principle. This is the same structure introduced in classical optimal control (see Part II, Chapter 4), here implemented with a sampling-based solver. The horizon slides forward with time, so each optimization covers a fixed number of future steps from the current state estimate.
Receding-horizon feedback limits how long the controller follows an uncorrected prediction. Once the actual or estimated state after is available, the next optimization starts from that updated condition rather than the previous predicted state. This can reject some disturbances and model mismatch, but it does not make an inaccurate model harmless. Closed-loop stability and constraint satisfaction depend on the system, estimator, horizon, objective, terminal design, solver quality, and mismatch bounds. Frequent replanning is feedback, not by itself a stability proof.
Open-loop planning applies a sequence without incorporating new state information, whereas MPC observes or estimates the state and replans after the first action or a short block. That feedback can reduce sensitivity to disturbances represented in the measurement and model. It does not automatically confer robustness: unobserved disturbances, estimator error, actuator delay, infeasible replans, or large model mismatch can still defeat the controller. How conservative the resulting policy should be depends on the uncertainty model and constraints, not simply on whether replanning is present.
There is an important connection here to state estimation. When the state is not directly measured, step 1 requires an estimate or belief constructed from observations. Under the standard linear-Gaussian assumptions of LQG control, the separation principle permits an optimal controller to combine a Kalman state estimate with the full-state LQR law. That result does not justify replacing a filter by an arbitrary learned encoder. For nonlinear learned systems, feeding a point estimate or latent state to MPC is an approximation unless the estimator and controller satisfy stronger problem-specific conditions. A confidently wrong estimate can therefore drive a precisely solved plan toward the wrong state. Part VII, Chapter 3 develops planning from beliefs rather than treating an estimate as truth.
Common inner solvers include random shooting, CEM, and MPPI. For a fixed time budget, iterative proposal fitting can outperform uniform random shooting when low-cost controls occupy a region that the chosen proposal can represent. Multi-modal or discontinuous objectives can instead cause a single Gaussian CEM proposal to average incompatible modes or collapse prematurely. Random shooting retains broad coverage but spends no evaluations refining a promising region. A useful mental model is that CEM performs adaptive local search through its proposal, while random shooting preserves a fixed global proposal. The outcome is empirical, so wall-clock budgets and seeds should be matched when choosing between them.
We now assemble the whole thing into a closed-loop controller on our double integrator, with a small amount of process noise to represent the environment not matching the model exactly. The controller will plan, execute one step, replan, and repeat.
def step_with_noise(state, action, noise, dt=DT):
nxt = batched_rollout(state[None, :], action[None, None, :], dt)[0, 1]
return nxt + noise
def mpc_rollout(
state0,
goal,
total_steps,
horizon,
noise_seq,
planner="cem",
cost_fn=None,
rng=None,
warm_start=True,
):
if rng is None:
rng = np.random.default_rng(0)
s = np.array(state0, dtype=float)
traj = [s.copy()]
controls = []
plan = np.zeros((horizon, 2))
for t in range(total_steps):
if planner == "cem":
init = plan if warm_start else None
plan, _, _ = cem(
s, goal, horizon, rng=rng, mu_init=init, cost_fn=cost_fn
)
else:
plan, _, _ = random_shooting(s, goal, horizon, rng=rng)
a = plan[0]
controls.append(a.copy())
s = step_with_noise(s, a, noise_seq[t])
traj.append(s.copy())
plan = np.vstack([plan[1:], np.zeros((1, 2))])
return np.array(traj), np.array(controls)
def run_open_loop(
state0, goal, total_steps, horizon, noise_seq, cost_fn=None, rng=None
):
if rng is None:
rng = np.random.default_rng(0)
s = np.array(state0, dtype=float)
plan, _, _ = cem(s, goal, horizon, rng=rng, cost_fn=cost_fn)
traj = [s.copy()]
for t in range(total_steps):
a = plan[t] if t < horizon else np.zeros(2)
s = step_with_noise(s, a, noise_seq[t])
traj.append(s.copy())
return np.array(traj)The inner loop of mpc_rollout contains the MPC pattern: plan from the current simulated plant state, execute the first action, update the state, shift the proposal, and repeat. The state s is updated by the noisy plant step rather than the planner's nominal prediction. The plan persists only as a CEM proposal mean. Shifting aligns its remaining actions with the new time index; without the shift, the proposal is stale and can slow or misdirect finite-budget optimization, although CEM may still recover.
The step_with_noise function is the ground-truth system, not the model. The model (inside cem) still thinks it is driving a clean double integrator. The gap between them is what makes the comparison meaningful. Separating "model" and "plant" is a useful debugging experiment: if behavior changes only after mismatch is introduced, that implicates model error or the controller's sensitivity to it; failure even with matched dynamics instead directs attention toward the planner, objective, implementation, or state representation. This comparison narrows the diagnosis but does not prove a unique cause.
Let us run both strategies with the same noise sequence.
MPC_HORIZON = 40
COMPARISON_STEPS = MPC_HORIZON
EVAL_STEPS = 40
noise_rng = np.random.default_rng(17)
noise_seq = noise_rng.normal(0.0, 0.06, size=(EVAL_STEPS, 4))
mpc_traj, mpc_controls = mpc_rollout(
state0,
goal,
COMPARISON_STEPS,
MPC_HORIZON,
noise_seq[:COMPARISON_STEPS],
planner="cem",
rng=np.random.default_rng(3),
)
ol_traj = run_open_loop(
state0,
goal,
COMPARISON_STEPS,
MPC_HORIZON,
noise_seq[:COMPARISON_STEPS],
rng=np.random.default_rng(3),
)
mpc_final_err = float(np.linalg.norm(mpc_traj[-1, :2] - goal))
ol_final_err = float(np.linalg.norm(ol_traj[-1, :2] - goal))
mpc_dist = np.linalg.norm(mpc_traj[:, :2] - goal[None, :], axis=1)
ol_dist = np.linalg.norm(ol_traj[:, :2] - goal[None, :], axis=1)MPC final position error to goal: 0.2688 Open-loop final position error: 1.4817 Improvement factor: 5.5x
The numbers illustrate the mechanism in this fixed-seed example. Open-loop planning accumulates the injected process perturbations and cannot respond after the initial plan. MPC incorporates the perturbed state into the next optimization. Both runs use the same noise sequence; within this paired implementation, the operational difference is replanning. The example demonstrates disturbance correction, not a population-level performance guarantee.
The comparison applies independent perturbations with standard deviation 0.06 to each of the four discrete state components at every step for 40 steps, matching the open-loop plan length. Direct position perturbations accumulate like a random walk, while velocity perturbations also propagate into later positions through the dynamics. The open-loop plan cannot react to either source. MPC observes the resulting state each step and replans. Feedback explains the mechanism, but the printed improvement factor is specific to this seed, planner budget, noise model, and objective.


For this paired run, MPC ends closer to the goal and its distance-to-goal curve recovers after disturbances, while the open-loop run levels off. The distance curve shows the trajectory of the error rather than only its final value. Neither MPC nor its expected distance is generally monotone without additional assumptions; the point here is simply that the feedback controller retains a mechanism for correcting observed deviation.
Receding Horizons and Replanning
The horizon length and the replanning policy (how often the controller solves a fresh problem) are the two knobs that decide MPC's behavior in practice. Neither is free: both trade off computational cost against the quality of the closed-loop behavior, and the right settings depend on the system, the model quality, and the time budget. It helps to think of them as two different ways of spending the same resource. A longer horizon spends compute to see further ahead; a shorter replanning interval spends compute to correct more often. The two are not interchangeable, because foresight and feedback do different jobs, but they compete for the same clock.
The horizon length defines what "long" means in the phrase "long-horizon planning." A short horizon omits consequences beyond its boundary and may fail to value a maneuver needed for later success. In this double-integrator task, exposes only 0.2 seconds of nominal motion. Under the stated cost and finite sampling budget, that limited lookahead produces a more myopic closed-loop result; the subsequent sweep reports what happens rather than assuming a particular overshoot pattern. A longer horizon can expose acceleration and deceleration together, but raises search dimension and gives model error more time to accumulate.
There is a second subtlety. A longer horizon raises the search dimension and gives model error more opportunities to accumulate. Much of its tail will also be replanned before execution, although it still influences the selected first action through the objective. The useful horizon must expose delayed consequences and allow the relevant maneuver to appear in the rollout. Our undamped double integrator has no finite settling time, so is only ten discretization steps, not a physical time constant. We therefore treat 15 to 25 steps as an empirical range for this particular target, action bound, cost, and controller budget rather than derive it from "settling." For stable physical modes, time constants can guide an initial horizon; in every case, closed-loop sweeps should test sensitivity.
Let us verify this empirically. We sweep the horizon from 2 to 40 and record the final distance to the goal after 40 control steps.
horizons = [2, 5, 10, 20, 40]
sweep = []
for h in horizons:
traj, _ = mpc_rollout(
state0,
goal,
EVAL_STEPS,
h,
noise_seq,
planner="cem",
rng=np.random.default_rng(11),
)
sweep.append((h, float(np.linalg.norm(traj[-1, :2] - goal))))
sweep_h = np.array([s[0] for s in sweep])
sweep_err = np.array([s[1] for s in sweep])This sweep uses the same noise sequence and initializes each planner with the same seed. That removes between-setting variation in the injected disturbances. It does not isolate horizon perfectly: changing the horizon also changes the action-sequence dimension and therefore how the finite CEM budget searches. The five-setting, one-seed result is illustrative; a performance claim would require repeated seeds and uncertainty intervals.
Now the replanning frequency. Instead of replanning every tick, we solve one MPC problem and execute steps from it before replanning. Larger reduces compute per unit time but reopens the door to open-loop drift. We implement a block MPC loop.
def block_mpc_rollout(
state0, goal, total_steps, horizon, block, noise_seq, rng
):
s = np.array(state0, dtype=float)
traj = [s.copy()]
plan = np.zeros((horizon, 2))
t = 0
while t < total_steps:
plan, _, _ = cem(s, goal, horizon, rng=rng, mu_init=plan)
executed = 0
for k in range(block):
if t >= total_steps:
break
a = plan[min(k, horizon - 1)]
s = step_with_noise(s, a, noise_seq[t])
traj.append(s.copy())
t += 1
executed += 1
if executed < horizon:
plan = np.vstack([plan[executed:], np.zeros((executed, 2))])
else:
plan = np.zeros((horizon, 2))
return np.array(traj)
blocks = [1, 2, 5, 10, 20]
block_errors = []
for b in blocks:
traj = block_mpc_rollout(
state0,
goal,
EVAL_STEPS,
MPC_HORIZON,
b,
noise_seq,
np.random.default_rng(13),
)
block_errors.append(float(np.linalg.norm(traj[-1, :2] - goal)))
block_errors = np.array(block_errors)In this fixed-seed experiment, the final error varies non-monotonically with block size. The comparison keeps the injected noise sequence fixed, but different block sizes make different numbers of stochastic optimizer calls, so it does not isolate feedback frequency from finite-sample planning variation. Treat the curve as an illustration of a feedback-compute tuning experiment, not evidence that error must rise with block size. A deployment study should repeat planner seeds and choose a block whose closed-loop error and constraint metrics remain acceptable under the available compute budget.


The figure shows two tuning sweeps from individual seeded experiments. Error falls as the horizon lengthens and then flattens in the first sweep, while the block-size outcome is non-monotonic in the second. A plateau or apparent optimum is a hypothesis to test, not a universal stopping rule: verify it across disturbances and seeds, and also check computation time, constraint violations, and task success before fixing either parameter.
A third design question is what to replan from. A measured or estimated point state is enough only when that representation is sufficient for the control problem. Under partial observability, belief-space planning propagates a distribution over latent states or hypotheses, as Part VII, Chapter 3 develops. The objective may then involve expected cost, risk, information gain, or constraints under that belief. Taking an expectation does not generally make the optimization landscape smoother, and minimizing expectation alone can hide low-probability, high-consequence outcomes.
Closely related is the question of what cost to optimize. Goal reaching may use stage and terminal distance terms; trajectory tracking may penalize deviation from a reference; stochastic objectives may average or otherwise aggregate costs over predicted futures. The objective, model, constraints, estimator, and replanning policy are conceptually separable, but they are not independent: changing one can alter feasibility, stability, and the best settings of the others.
Finally, terminal costs connect MPC to value-based planning: a terminal approximation summarizes consequences beyond the explicit horizon. If the terminal term equals the true optimal cost-to-go, the model is correct, and the same admissible actions and costs are used, a one-step Bellman minimization recovers an optimal action. Learned terminal values can therefore shorten the horizon needed in practice, but approximation error, model error, and constraints prevent an exact equivalence. Methods such as TD-MPC combine latent rollout optimization with learned value estimates, while MuZero uses a learned model inside tree search; later chapters distinguish those planning structures.
Constraints, Warm Starts, and Real-Time Budgets
We have so far treated the MPC problem as unconstrained except for the box on controls. Real systems often impose joint, velocity, geofence, or clearance constraints. Sampling-based planners have several options: reject infeasible samples, project or transform samples into a feasible parameterization, use a constrained optimizer, or add violation penalties to the objective. We use a finite penalty here. It converts a hard requirement into a soft preference; it neither diverges at the boundary nor guarantees feasibility.
Rejection preserves feasibility among retained samples, but its acceptance rate equals the proposal probability of the feasible set and can become very small in a narrow corridor. It still offers no guarantee that the best retained finite sample is globally optimal. A finite penalty always assigns a numerical cost, but the selected plan may remain infeasible. If violation is unacceptable, a soft-penalty planner needs a separate verified feasibility or safety mechanism rather than an assumption that a small violation is harmless.
Consider an obstacle at position with radius for a point-mass robot. A soft constraint for the obstacle is
where is a positive weight. Any evaluated point inside the disc contributes a quadratic penalty; points on or outside the boundary contribute zero. The squared hinge is continuously differentiable at the boundary, although its second derivative changes there. Because the terms are summed, several shallow violations can outweigh one deeper violation. The obstacle term itself does not reward extra clearance once outside, so the task and effort terms determine whether a plan grazes the boundary or takes a wider route.
Let us add an obstacle directly on the straight-line path from start to goal. The finite penalty makes routing around it compete with goal error and effort; feasibility is checked after optimization.
obstacles = [(np.array([1.5, 1.2]), 0.55)]
PENALTY_WEIGHT = 5000.0
def make_constrained_cost(goal, obstacles, weight=PENALTY_WEIGHT):
def _cost(states, actions):
base, traj = batched_cost(states, actions, goal)
pos = traj[:, 1:, :2]
viol = np.zeros(states.shape[0])
for c, r in obstacles:
d = np.linalg.norm(pos - c[None, None, :], axis=2)
viol += np.sum(np.maximum(0.0, r - d) ** 2, axis=1)
return base + weight * viol, traj
return _cost
constrained_cost = make_constrained_cost(goal, obstacles)
constrained_actions, _, _ = cem(
state0,
goal,
PLAN_HORIZON,
n_samples=1000,
n_elite=100,
n_iters=20,
rng=np.random.default_rng(7),
cost_fn=constrained_cost,
)
constrained_traj = batched_rollout(
state0[None, :], constrained_actions[None, :, :]
)[0]
obstacle_distances = np.linalg.norm(
constrained_traj[:, :2] - obstacles[0][0][None, :], axis=1
)
min_clearance = float(obstacle_distances.min() - obstacles[0][1])Notice the structure of make_constrained_cost. It wraps the base cost and adds one soft-penalty component; a truly hard constraint would require different handling. The obstacle penalty is also evaluated only at discrete rollout states. A segment can cross the disc between time steps even when both endpoints are outside, so tighter temporal discretization or segment-intersection checks are needed to rule that out. The numerical weight 5000 is large relative to this example's task term, but its physical units depend on how position and control costs are normalized. It is a tuning coefficient, not a dimensionless universal ratio of importance.
Obstacle radius: 0.55 m Minimum clearance along the plan: 0.003 m Negative clearance means the plan touches or passes through the obstacle.
For this discretized example, the squared-hinge term makes sampled states inside the obstacle expensive. Increasing tends to prioritize reducing penetration, while decreasing it allows more tradeoff against goal and effort costs. No finite weight guarantees that an infeasible trajectory is never preferred, especially when no feasible sample is drawn. A practical sweep should therefore report both task performance and violation, followed by an explicit feasibility check on the selected trajectory.
The weight is not a parameter that can be set to "large" and forgotten, because it controls a tradeoff between clearance and goal progress. The cell below re-solves the same configured problem at nine penalty weights, changing while holding the start state, horizon, sample budget, and pseudorandom seed fixed. Ties are ordered by cost and then sample index in cem.
penalty_weights = [0.01, 0.5, 1.0, 5.0, 10.0, 50.0, 250.0, 1000.0, 5000.0]
penalty_min_clearance = []
penalty_goal_error = []
for w in penalty_weights:
cost_w = make_constrained_cost(goal, obstacles, weight=w)
acts_w, _, _ = cem(
state0,
goal,
PLAN_HORIZON,
n_samples=500,
n_elite=50,
n_iters=10,
rng=np.random.default_rng(7),
cost_fn=cost_w,
)
traj_w = batched_rollout(state0[None, :], acts_w[None, :, :])[0]
d_w = np.linalg.norm(traj_w[:, :2] - obstacles[0][0][None, :], axis=1)
penalty_min_clearance.append(float(d_w.min() - obstacles[0][1]))
penalty_goal_error.append(float(np.linalg.norm(traj_w[-1, :2] - goal)))
penalty_weights = np.array(penalty_weights)
penalty_min_clearance = np.array(penalty_min_clearance)
penalty_goal_error = np.array(penalty_goal_error)

Read together, clearance and final goal error identify candidate weights that improve sampled-state feasibility without unacceptable task degradation in this run. They do not certify the continuous path. Weight selection should be repeated across seeds and starting conditions, followed by the safety checks appropriate to the deployment.

The figure shows the selected plan's sampled positions curving around the disc, while the plotted straight line intersects it. The detour is smooth under the acceleration dynamics and effort cost. Any visible margin is an outcome of this finite-sample run, not a robustness guarantee: because the obstacle penalty is zero outside the disc, it supplies no direct reward for additional clearance.
The second half of this section is warm starting. When the state, goal, and constraints change slowly between ticks, the shifted previous plan can be a useful initial proposal for the current problem:
We can pass this sequence as mu_init to CEM. When the prior remains relevant, it can improve finite-iteration plan quality or reduce the iterations needed to meet a target quality. It may also reduce between-tick variation by anchoring consecutive proposals. Abrupt goal, constraint, or dynamics changes can make the same prior harmful, so implementations should retain enough proposal variance or support a cold restart.
Let us demonstrate. We first solve a CEM problem at a previous state and apply its first action to obtain the next state. At that next state, we compare a one-iteration cold start against a one-iteration warm start initialized from the shifted prior plan.
previous_state = np.array([0.4, 0.3, 0.5, 0.2])
prior_plan, _, _ = cem(
previous_state,
goal,
PLAN_HORIZON,
n_samples=800,
n_elite=80,
n_iters=12,
rng=np.random.default_rng(21),
)
nearby_state = batched_rollout(
previous_state[None, :], prior_plan[None, :1, :]
)[0, 1]
shifted_prior = np.vstack([prior_plan[1:], np.zeros((1, 2))])
ONE_ITER = 1
cold_plan, _, _ = cem(
nearby_state,
goal,
PLAN_HORIZON,
n_samples=800,
n_elite=80,
n_iters=ONE_ITER,
rng=np.random.default_rng(99),
)
warm_plan, _, _ = cem(
nearby_state,
goal,
PLAN_HORIZON,
n_samples=800,
n_elite=80,
n_iters=ONE_ITER,
rng=np.random.default_rng(99),
mu_init=shifted_prior,
)
cold_cost = float(
batched_cost(nearby_state[None, :], cold_plan[None, :, :], goal)[0][0]
)
warm_cost = float(
batched_cost(nearby_state[None, :], warm_plan[None, :, :], goal)[0][0]
)With identical sample budgets and random-number seeds, the warm-started plan is lower-cost in this run after one iteration. The prior was computed at the previous state, shifted by the applied action, and evaluated at the resulting next state. The only configured difference between the two one-iteration calls is the initial proposal mean. This isolates the warm-start mechanism for the stated seed, but repeated disturbances and planner seeds are needed to estimate its typical benefit.
Warm starting can also reduce the number of refinement iterations needed for a target plan quality. A real-time controller must account for worst-case planner latency, because a missed deadline changes when an action is applied. Random shooting evaluates independent rollouts. CEM evaluates approximately rollouts plus elite selection and distribution updates. The product is therefore a first-order compute indicator, not a complete latency model. For a fixed product, the best split between iterations and samples depends on proposal dimension, elite fraction, hardware, and objective; more iterations do not win universally.
Let us measure the two planners and see the picture.
def timed_planner(fn, repeats=15):
t0 = time.perf_counter()
for _ in range(repeats):
fn()
return (time.perf_counter() - t0) / repeats
RS_SAMPLES_TIMING = 4000
CEM_SAMPLES_TIMING = 800
CEM_ELITE_TIMING = 80
CEM_ITERS_TIMING = 8
rs_time = timed_planner(
lambda: random_shooting(
nearby_state,
goal,
PLAN_HORIZON,
n_samples=RS_SAMPLES_TIMING,
rng=np.random.default_rng(0),
)
)
cem_time = timed_planner(
lambda: cem(
nearby_state,
goal,
PLAN_HORIZON,
n_samples=CEM_SAMPLES_TIMING,
n_elite=CEM_ELITE_TIMING,
n_iters=CEM_ITERS_TIMING,
rng=np.random.default_rng(0),
)
)
CONTROL_HZ = 100
TICK_MS = 1000.0 / CONTROL_HZRandom shooting (4000 samples, no iterations): 3.87 ms/plan CEM (8 iterations x 800 samples): 10.11 ms/plan Real-time control at 100 Hz costs 10.0 ms/tick.
The numbers should be read with the stated budgets: random shooting performs 4,000 rollouts, while CEM performs 6,400 plus eight elite-selection and refit steps. These wall-clock measurements depend on the current machine, Python and NumPy versions, load, and array sizes. Neither the absolute times nor their ratio is generally transferable. A deployment should measure objective value and worst-case latency under equal operational budgets; warm-started CEM or MPPI are candidate designs, not automatic winners.


In the fixed-seed left panel, the warm proposal yields a lower plan cost at the same one-iteration budget. The right panel reports local wall-clock measurements for the two stated workloads; it is a hardware-specific timing check, not an algorithmic ranking.
A few practical notes on budgets. At 100 Hz, one control period is 10 ms, and sensing, state estimation, communication, and actuation consume part of it. Possible overrun policies include returning the best candidate found so far, reducing samples or horizon, or running planning at a lower rate than the low-level controller. For example, a planner at 25 Hz may supply four actions or a local policy to a 100 Hz loop; its planning horizon is a separate design choice and need not equal four. Reusing a previous action can itself be unsafe, so the deadline fallback must be designed and verified for the plant rather than treated as a universal rule.
The deadline is part of the controller model rather than a service-level preference. The plant evolves while planning, so an overrun changes the state and time at which the computed action is applied. The effect need not grow monotonically with every delay, but the implemented input schedule is no longer the one assumed by a synchronous discrete-time design. Timing validation must therefore include tail latency and the specified fallback behavior.
Sampling-based MPC has recurring failure patterns: short horizons can cause myopia; long horizons raise compute and expose more model error; small sample budgets can make plans variable across ticks; clipping actions does not enforce state constraints; and an unbounded proposal can spend evaluations on controls the plant cannot apply.
Limitations and Impact
The limitations of sampling-based MPC fall into four buckets: sample complexity, model exploitation, constraint handling, and real-time discipline. Each of these is an obstacle rather than a footnote, and each corresponds to an active area of research. It is also worth keeping the four notions of model quality from the introduction in mind here, because different failure modes are failures of different qualities, and conflating them leads to fixing the wrong thing.
Sample complexity is fundamental. The difficulty of covering action-sequence space grows with action dimension and horizon. Our 2D example uses 800 samples over 15 CEM iterations at and produces a useful plan for one task and seed; it does not establish a general sample requirement. Applying the same independent, diagonal-Gaussian proposal to a 20-dimensional humanoid over would search a 2,000-dimensional sequence space and is unlikely to cover useful modes efficiently. Temporally correlated proposals, spline or skill parameterizations, and learned low-dimensional action representations restrict search to a smaller structured family. They reduce effective dimension only when that family still contains adequate plans.
Model exploitation is the second hazard. Optimization searches for low predicted cost and may select out-of-distribution actions where model error is systematically optimistic. A deterministic conditional-mean predictor also omits outcome dispersion unless uncertainty is modeled separately; that can make risky sequences look safer than they are. Possible mitigations include calibrated uncertainty penalties, ensembles or probabilistic models, conservative objectives, and shorter horizons. Each changes the planning objective or compute cost, and none substitutes for closed-loop evaluation on the planner-induced distribution.
Constraint handling depends on the method. The finite penalties used here trade task cost against violation and cannot guarantee feasibility. Rejection, projection, constrained optimization, robust tubes, invariant sets, barrier-based filters, and supervisory shutdown mechanisms provide different levels of assurance under different assumptions. A safety-critical design must state which layer owns the guarantee and validate that layer against timing, estimation, and model errors.
Real-time discipline is the fourth bucket. A controller is real-time only relative to a stated deadline on its target hardware. Average notebook timing is insufficient; the design needs worst-case or high-quantile latency, an overrun policy, and end-to-end accounting for sensing and actuation. Sampling and iteration budgets are therefore part of the controller specification, not merely performance-tuning details.
MPC is established in process control and widely studied in robotics and autonomous systems. Sampling-based inner solvers extend receding-horizon control to models and objectives for which derivatives or structured solvers are unavailable or inconvenient. Their minimal computational interface is a forward rollout, a scalar evaluation, an action proposal, and a time budget. Practical deployment adds less minimal requirements: state estimation, constraint handling, uncertainty treatment, and a deadline policy.
Looking ahead, differentiable planning (Part VII, Chapter 2) uses model gradients to optimize actions, while belief-space planning (Part VII, Chapter 3) represents uncertainty in the state used for planning. Part VIII then separates several decision-centric lineages rather than forcing them into one loop: PETS and PlaNet use online trajectory optimization, MuZero performs tree search, TD-MPC combines trajectory optimization with learned values, and Dreamer commonly executes a policy trained in imagination without online MPC.
Summary
Sampling-based planning and MPC are the workhorses that make a black-box world model useful for control. The key ideas:
- Random shooting samples action sequences, rolls each through the model, and returns the lowest-cost sampled plan. It requires no gradients and works with a forward-model interface, but finite-budget coverage deteriorates rapidly as horizon and action dimension grow.
- CEM iteratively refits a proposal, here a diagonal Gaussian, to elite low-cost samples. This concentrates later evaluations in regions found promising, but a single proposal can collapse early or average incompatible modes. Its advantage over random shooting must be measured under matched budgets.
- MPPI perturbs a nominal action sequence and forms an importance-weighted update under a stochastic-control derivation. CEM uses a hard elite set instead. Their relative performance is problem- and budget-dependent.
- Model predictive control solves a finite-horizon problem from the current state estimate, applies the first action or a short block, and replans. This receding-horizon feedback can correct some observed disturbances and mismatch, but stability and feasibility require additional conditions.
- Replanning closes the loop by incorporating a new state estimate before choosing later actions. In the paired experiment, the same injected noise produced a larger final error open loop than under MPC; the magnitude is specific to the seed and budgets.
- Horizon length trades omitted delayed consequences against search cost and accumulated model error. Maneuver timescales provide an initial guess, while repeated closed-loop sweeps should check performance, constraints, and timing.
- Finite penalties soften constraints and cannot guarantee feasibility. Rejection, feasible parameterizations, constrained optimizers, and independently verified safety layers provide different alternatives under different assumptions. Discrete rollout checks can also miss violations between time steps.
- Warm starting from the shifted previous plan often gives the optimizer a useful proposal at the next tick. Its benefit depends on how quickly the state, goal, constraints, and model change, so it should be compared with a cold start across matched seeds and budgets.
- Real-time discipline is as important as algorithmic choice. The controller must hit its deadline, degrade gracefully when it cannot, and treat plan quality and plan latency as two halves of the same design problem.
The next chapters continue the arc. Differentiable planning uses gradients through sufficiently smooth models to optimize actions, sometimes alongside sampling. Belief-space planning treats uncertainty over latent state as part of the planning problem. The decision-centric lineages of Part VIII then combine learned models with several distinct planning and policy-learning structures.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about sampling-based planning and model predictive control.
Sampling-Based Planning and Model Predictive Control
Reference
Citation details
Cite or share this article.
Continue with the full handbook
This chapter is part of World Models Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.
Explore World Models HandbookStay up to date
Get articles, book updates, and news delivered to your inbox.
No spam, unsubscribe anytime.
Join the community
Sign in to remove popups, track your reading progress, and join the discussion.

Comments
No comments yet. Be the first to share your thoughts!