Barrier States Python includes one discrete-time DDP-family solver that supports both iLQR and full DDP. The same solver is also used online by DDPMPC for receding-horizon control.
The solver operates on a discrete model
with a finite-horizon cost
When the model is an EmbeddedDiscrete system,
from barrier_states.barriers import reciprocal
from barrier_states.bas import DynamicDiscrete
from barrier_states.constraints import obstacles_2d
from barrier_states.embedding import EmbeddedDiscrete
from barrier_states.models import DoubleIntegrator, discretize
model = DoubleIntegrator(dimension=2)
discrete_model = discretize(model, dt=0.1, method="euler")
constraint = obstacles_2d(centers, radii, state_dim=model.n, state_indices=(0, 1))
barrier = reciprocal(constraint, alpha=1.0, aggregate="barrier_sum")
dbas = DynamicDiscrete(discrete_model, barrier, rho=0.05, reference_state=x_goal)
embedded = EmbeddedDiscrete(discrete_model, dbas)
xbar0 = embedded.augment_state(x0)
xbar_goal = embedded.augment_state(x_goal)The built-in quadratic cost is model-independent:
from barrier_states.costs import quadratic_discrete
cost = quadratic_discrete(Q, R, Qf, x_ref=xbar_goal, u_ref=np.zeros(embedded.m), x_final_ref=xbar_goal)Dimensions are inferred from Q and R; the solver checks that they match the model.
For augmented systems, the weight matrices include both physical and Barrier State coordinates. For example:
Q = np.diag([0.1, 0.1, 0.03, 0.03, 1.0])
Qf = np.diag([80.0, 80.0, 8.0, 8.0, 1.0])
R = 0.03 * np.eye(embedded.m)The final entries above correspond to one aggregated Barrier State.
from barrier_states.solvers import ddp
horizon = 50
initial_controls = np.zeros((horizon, embedded.m))
solution = ddp(embedded, cost, xbar0, initial_controls, method="ilqr", max_iters=100, verbose=True)method="ilqr" uses first-order dynamics derivatives:
model.step_x(x, u)
model.step_u(x, u)This is the recommended default for the current safety-embedded robotics examples.
solution = ddp(model, cost, x0, initial_controls, method="ddp")Full DDP additionally requires
step_xx (n, n, n)
step_uu (m, m, n)
step_ux (m, n, n)
with the output component indexed by the last tensor axis.
Use full DDP only when the model provides reliable second-order discrete dynamics. The library does not require every built-in model or embedding to expose those tensors.
The solver shares the same core machinery across iLQR and DDP:
- nominal trajectory rollout;
- backward pass;
- adaptive diagonal regularization of the control Hessian;
- Cholesky-based linear solves rather than explicit matrix inversion;
- forward line search;
- optional input saturation;
- cost and gradient convergence tests;
- optional storage of accepted iteration trajectories.
solution = ddp(embedded, cost, xbar0, initial_controls, input_lower=-2.5, input_upper=2.5)These bounds clip controls during the initial and trial forward rollouts. This is deliberately a simple saturation mechanism, not an active-set box-constrained DDP method.
The initial control guess must produce a valid finite rollout. If the model exposes safety checks and safety_check=True, an unsafe initial rollout is rejected before optimization.
For difficult obstacle problems, choose an initial guess that is physically meaningful. A zero control sequence is often a good first choice for systems that start at rest in a safe state:
initial_controls = np.zeros((horizon, embedded.m))The returned solution exposes the optimized trajectory and solver diagnostics:
solution.states
solution.controls
solution.feedback_gains
solution.feedforward_gains
solution.cost
solution.iterations
solution.status
solution.method
solution.regularization
solution.history
solution.input_lower
solution.input_upperConvenience properties include
solution.horizon
solution.n
solution.m
solution.converged
solution.feedback_availableThe principal trajectory shapes are
states (N + 1, n)
controls (N, m)
feedback_gains (N, m, n)
feedforward_gains (N, m)
solution.history stores convergence information such as
solution.history.costs
solution.history.regularization
solution.history.alphas
solution.history.gradient_norms
solution.history.acceptedWhen
store_trajectories=Trueis enabled, accepted state and control trajectories are also retained. This is useful for iteration-by-iteration animation but increases memory usage.
solution = ddp(embedded, cost, xbar0, initial_controls, method="ilqr", store_trajectories=True)from barrier_states.controllers import trajectory_policy
from barrier_states.simulation import simulate_discrete
policy = trajectory_policy(solution, use_feedback=True)
simulation = simulate_discrete(embedded, policy, steps=solution.horizon, x0=xbar0)The feedback law uses the optimized nominal trajectory:
This is preferable to blindly replaying the open-loop controls when feedback gains are available.
Offline trajectory optimization plans once. MPC repeatedly replans from the current state.
from barrier_states.controllers import DDPMPC
controller = DDPMPC(
embedded,
cost,
horizon=35,
initial_controls=np.zeros((35, embedded.m)),
method="ilqr",
input_lower=-2.5,
input_upper=2.5,
max_iters=5,
tol_cost=1e-3,
)Then run it exactly like any other discrete controller:
simulation = simulate_discrete(embedded, controller, steps=90, x0=xbar0)At each simulation step:
DDPMPCsolves a finite-horizon DDP/iLQR problem from the current state.- It retains the full predicted trajectory.
- It returns only the first optimized control.
simulate_discreteapplies that input and advances the embedded plant one step.- The optimized controls are shifted left and the last optimized control is repeated as the next warm-start tail.
- The process repeats from the new state.
This is a thin controller layer around the same ddp() solver; no second DDP implementation exists inside the MPC class.
Useful diagnostics include:
from barrier_states.plotting import (
animate_ddp_trajectory_iterations,
animate_mpc_trajectory_2d,
plot_ddp_history,
plot_mpc_trajectory_2d,
)plot_ddp_history is useful for convergence and regularization behavior. animate_ddp_trajectory_iterations visualizes the progression of accepted optimizer trajectories. For MPC, plot_mpc_trajectory_2d and animate_mpc_trajectory_2d compare executed motion with the online finite-horizon predictions.
Before increasing horizons or iteration counts, verify the small problem first:
✓ initial and reference states are strictly safe
✓ Q, R, and Qf dimensions match the embedded model
✓ initial_controls has shape (N, m)
✓ the initial rollout is finite and safe
✓ first-order derivatives pass finite-difference tests
✓ input limits are physically sensible
✓ iLQR converges before trying full DDP
✓ Barrier State and physical-state histories remain finite
The large examples are demonstrations, not recommended unit-test workloads. Keep CI smoke problems short and use full examples for manual validation and visualization.