Complete Example: Time-Optimal Bilinear Control
This example demonstrates solving a time-optimal trajectory optimization problem with:
- Multiple control inputs with bounds
- Free time steps (variable Δt)
- Combined objective (control effort + minimum time)
using DirectTrajOpt
using NamedTrajectories
using LinearAlgebra
using CairoMakieProblem Setup
System: 3D oscillator with 2 control inputs
\[\dot{x} = (G_0 + u_1 G_1 + u_2 G_2) x\]
Goal: Drive from [1, 0, 0] to [0, 0, 1] minimizing ∫ ||u||² dt + w·T
Constraints:-1 ≤ u ≤ 1, 0.05 ≤ Δt ≤ 0.3
Define System Dynamics
G_drift = [
0.0 1.0 0.0;
-1.0 0.0 0.0;
0.0 0.0 -0.1
]
G_drives = [
[
1.0 0.0 0.0;
0.0 0.0 0.0;
0.0 0.0 0.0
],
[
0.0 0.0 0.0;
0.0 0.0 1.0;
0.0 1.0 0.0
],
]
G = u -> G_drift + sum(u .* G_drives)#2 (generic function with 1 method)Create Trajectory
N = 50
x_init = [1.0, 0.0, 0.0]
x_goal = [0.0, 0.0, 1.0]
x_guess = hcat([x_init + (x_goal - x_init) * (k/(N-1)) for k = 0:(N-1)]...)
traj = NamedTrajectory(
(x = x_guess, u = 0.1 * randn(2, N), Δt = fill(0.15, N));
timestep = :Δt,
controls = (:u, :Δt),
initial = (x = x_init,),
final = (x = x_goal,),
bounds = (u = 1.0, Δt = (0.05, 0.3)),
)N = 50, (x = 1:3, u = 4:5, → Δt = 6:6)Build and Solve Problem
integrator = BilinearIntegrator(G, :x, :u, traj)
obj = (QuadraticRegularizer(:u, traj, 1.0) + 0.5 * MinimumTimeObjective(traj, 1.0))
prob = DirectTrajOptProblem(traj, obj, integrator)
probDirectTrajOptProblem
Trajectory
Timesteps: 50
Duration: 7.35
Knot dim: 6
Variables: x (3), u (2), Δt (1)
Controls: u, Δt
Objective (2 terms)
1.0 * QuadraticRegularizer on :u (R = [1.0, 1.0], all)
0.5 * MinimumTimeObjective (D = 1.0)
Dynamics (1 integrators)
BilinearIntegrator: :x = exp(Δt G(:u)) :x (dim = 3)
Constraints (4 total: 2 equality, 2 bounds)
EqualityConstraint: "initial value of x"
EqualityConstraint: "final value of x"
BoundsConstraint: "bounds on u"
BoundsConstraint: "bounds on Δt"solve!(prob; max_iter = 50) initializing optimizer...
building evaluator: 1 integrators, 0 nonlinear constraints
dynamics constraints: 147, nonlinear constraints: 0
integrator 1 jacobian structure: 0.385s
jacobian structure: 1764 nonzeros (0.407s)
integrator 1 hessian structure: 0.02s
computing objective hessian structure (CompositeObjective)...
sub-objective 1 (QuadraticRegularizer): 0.372s
sub-objective 2 (MinimumTimeObjective): 0.006s
objective hessian structure: 0.436s
hessian structure: 2814 nonzeros (0.456s)
linear index maps built (0.004s)
evaluator ready (total: 1.018s)
evaluator created (2.142s)
NL constraint bounds extracted (0.025s)
NLP block data built (0.0s)
Ipopt optimizer configured (0.009s)
variables set (0.205s)
applying constraint: initial value of x
applying constraint: final value of x
applying constraint: bounds on u
applying constraint: bounds on Δt
linear constraints added: 4 (0.603s)
optimizer initialization complete (total: 3.013s)
******************************************************************************
This program contains Ipopt, a library for large-scale nonlinear optimization.
Ipopt is released as open source code under the Eclipse Public License (EPL).
For more information visit https://github.com/coin-or/Ipopt
******************************************************************************
This is Ipopt version 3.14.19, running with linear solver MUMPS 5.8.2.
Number of nonzeros in equality constraint Jacobian...: 1746
Number of nonzeros in inequality constraint Jacobian.: 0
Number of nonzeros in Lagrangian Hessian.............: 2748
Total number of variables............................: 294
variables with only lower bounds: 0
variables with lower and upper bounds: 150
variables with only upper bounds: 0
Total number of equality constraints.................: 147
Total number of inequality constraints...............: 0
inequality constraints with only lower bounds: 0
inequality constraints with lower and upper bounds: 0
inequality constraints with only upper bounds: 0
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 3.6856358e+00 1.50e-01 8.28e-02 0.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 2.8462383e+00 1.33e-01 6.18e-01 -4.0 1.61e+00 - 1.43e-01 1.12e-01f 1
2 2.3115866e+00 1.18e-01 7.67e+00 -0.5 2.41e+00 - 4.08e-01 1.14e-01h 1
3 2.2663028e+00 1.10e-01 7.79e+00 -0.3 4.23e+00 0.0 2.54e-01 1.05e-01h 1
4 2.0953547e+00 9.48e-02 3.41e+01 -0.1 1.49e+00 0.4 6.37e-01 1.46e-01h 1
5 2.4277574e+00 7.70e-02 1.15e+02 0.4 3.56e+00 - 8.17e-01 2.21e-01h 1
6 2.2851456e+00 6.83e-02 1.60e+02 0.3 5.84e+00 - 1.71e-01 9.38e-02h 1
7 2.7261920e+00 7.36e-02 2.13e+02 1.5 2.68e+01 0.9 5.50e-02 4.63e-02f 1
8 2.7097054e+00 7.05e-02 2.13e+02 0.4 1.04e+00 2.2 1.45e-01 8.03e-02h 1
9 2.6987770e+00 6.77e-02 3.64e+02 0.4 7.44e-01 2.6 2.11e-01 6.55e-02h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
10 2.7559676e+00 7.72e-02 3.65e+02 0.4 1.52e+00 2.1 8.80e-02 9.96e-02h 1
11 2.8865737e+00 6.07e-02 1.08e+03 1.2 1.38e+00 2.6 8.70e-01 2.69e-01f 1
12 2.9645002e+00 5.14e-02 1.27e+03 1.6 1.31e+00 3.0 5.29e-01 1.49e-01f 1
13 3.0839528e+00 6.18e-02 1.65e+03 1.7 5.10e-01 3.4 1.00e+00 7.05e-01f 1
14 3.0038517e+00 6.36e-03 5.85e+02 1.4 9.51e-02 3.8 9.93e-01 1.00e+00f 1
15 3.0013620e+00 1.52e-05 1.70e+01 -0.1 7.65e-03 3.4 9.98e-01 1.00e+00f 1
16 3.0012812e+00 3.16e-08 1.28e-01 -2.0 1.60e-04 2.9 9.99e-01 1.00e+00h 1
17 3.0005918e+00 3.88e-07 1.39e-01 -3.7 5.49e-04 2.4 9.99e-01 1.00e+00f 1
18 2.9985101e+00 3.48e-06 1.39e-01 -4.0 1.65e-03 1.9 1.00e+00 1.00e+00f 1
19 2.9924099e+00 2.96e-05 1.34e-01 -4.0 4.78e-03 1.4 1.00e+00 1.00e+00f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
20 2.9753171e+00 2.25e-04 1.22e-01 -4.0 1.31e-02 1.0 1.00e+00 1.00e+00f 1
21 2.9540004e+00 4.32e-04 1.09e-01 -4.0 3.04e-02 0.5 1.00e+00 4.97e-01h 1
22 2.9110117e+00 1.27e-03 8.74e-02 -4.0 6.46e-02 0.0 1.00e+00 4.53e-01f 1
23 2.8834324e+00 1.30e-03 7.75e-02 -4.0 1.31e-01 -0.5 1.00e+00 1.60e-01h 1
24 2.8495275e+00 1.30e-03 6.22e-02 -4.0 5.88e-02 -0.0 1.00e+00 5.10e-01h 1
25 2.8058920e+00 1.44e-03 5.41e-02 -4.0 1.31e-01 -0.5 1.00e+00 3.40e-01h 1
26 2.7807752e+00 9.66e-04 4.68e-02 -4.0 5.04e-02 -0.1 1.00e+00 5.74e-01h 1
27 2.7267244e+00 1.26e-03 3.89e-02 -4.0 1.34e-01 -0.6 1.00e+00 5.30e-01h 1
28 2.6998983e+00 1.51e-03 1.32e-01 -2.9 5.77e-01 -1.0 1.00e+00 1.93e-01f 1
29 2.6230895e+00 2.33e-03 2.88e-02 -3.5 1.19e-01 -0.6 1.00e+00 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
30 2.5280766e+00 1.10e-02 5.15e-02 -3.2 3.61e-01 -1.1 1.00e+00 8.21e-01h 1
31 2.5101091e+00 5.77e-02 1.53e-01 -2.7 1.11e+00 -1.6 6.72e-01 7.30e-01f 1
32 2.4368097e+00 2.60e-02 6.95e-02 -2.8 1.11e+00 -2.0 7.45e-01 5.37e-01h 1
33 2.4099261e+00 1.87e-02 4.81e-02 -2.9 1.24e+00 -2.5 6.08e-01 2.81e-01h 1
34 2.3131201e+00 5.86e-03 1.28e-02 -3.5 3.28e-01 -2.1 1.00e+00 1.00e+00h 1
35 2.2953151e+00 1.39e-02 5.70e-03 -3.6 4.40e-01 -2.6 9.75e-01 7.03e-01h 1
36 2.2943761e+00 7.83e-03 1.86e-02 -3.3 1.25e+00 -3.1 6.16e-01 6.38e-01H 1
37 2.2827392e+00 1.37e-02 1.25e-02 -4.0 7.03e-01 -3.5 5.68e-01 7.21e-01h 1
38 2.2762513e+00 3.36e-03 2.32e-03 -4.0 1.53e-01 -2.2 1.00e+00 1.00e+00h 1
39 2.3056490e+00 3.37e-02 6.33e-02 -2.0 1.05e+01 -2.7 2.01e-01 4.29e-02f 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
40 2.3654439e+00 3.68e-02 9.09e-02 -2.8 1.02e+00 - 1.00e+00 1.00e+00h 1
41 2.3668734e+00 1.04e-02 1.67e-02 -2.8 1.08e-01 -1.3 1.00e+00 7.07e-01h 1
42 2.3670834e+00 9.18e-03 2.09e-02 -2.8 3.31e+00 - 2.25e-01 1.70e-01h 2
43 2.3665189e+00 8.87e-03 2.22e-01 -2.8 3.08e+00 -1.8 1.00e+00 4.56e-02h 3
44 2.3639550e+00 1.73e-03 9.62e-03 -2.8 2.93e-01 - 1.00e+00 1.00e+00h 1
45 2.2960410e+00 4.04e-03 6.75e-03 -3.5 2.02e-01 - 9.92e-01 1.00e+00h 1
46 2.2886377e+00 1.26e-03 8.20e-04 -3.6 8.33e-02 -2.3 1.00e+00 1.00e+00h 1
47 2.2742460e+00 3.67e-03 2.60e-03 -5.4 2.68e-01 -2.8 6.87e-01 7.46e-01h 1
48 2.2743437e+00 1.83e-03 9.92e-04 -4.1 1.11e-01 - 1.00e+00 9.79e-01h 1
49 2.2751397e+00 3.12e-04 1.80e-04 -4.0 3.78e-02 - 1.00e+00 1.00e+00h 1
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
50 2.2751282e+00 4.42e-06 3.45e-06 -4.0 4.78e-03 - 1.00e+00 1.00e+00h 1
Number of Iterations....: 50
(scaled) (unscaled)
Objective...............: 2.2751282192967963e+00 2.2751282192967963e+00
Dual infeasibility......: 3.4519203355481325e-06 3.4519203355481325e-06
Constraint violation....: 4.4248020154569190e-06 4.4248020154569190e-06
Variable bound violation: 0.0000000000000000e+00 0.0000000000000000e+00
Complementarity.........: 1.0000097934977663e-04 1.0000097934977663e-04
Overall NLP error.......: 4.4248020154569190e-06 4.4248020154569190e-06
Number of objective function evaluations = 59
Number of objective gradient evaluations = 51
Number of equality constraint evaluations = 59
Number of inequality constraint evaluations = 0
Number of equality constraint Jacobian evaluations = 51
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations = 50
Total seconds in IPOPT = 12.025
EXIT: Maximum Number of Iterations Exceeded.Visualize Solution
plot(prob.trajectory) # See NamedTrajectories.jl documentation for plotting options
Analyze Solution
x_sol = prob.trajectory.x
u_sol = prob.trajectory.u
Δt_sol = prob.trajectory.Δt
println("Solution found!")
println(" Total time: $(sum(Δt_sol)) seconds")
println(" Δt range: [$(minimum(Δt_sol)), $(maximum(Δt_sol))]")
println(" Max |u₁|: $(maximum(abs.(u_sol[1,:])))")
println(" Max |u₂|: $(maximum(abs.(u_sol[2,:])))")
println(" Final error: $(norm(x_sol[:,end] - x_goal))")Solution found!
Total time: 4.247068708894432 seconds
Δt range: [0.07224419052947219, 0.175]
Max |u₁|: 0.9966534906428555
Max |u₂|: 0.9978914085156653
Final error: 0.0Key Insights
Free time optimization: Variable Δt allows the optimizer to adjust trajectory speed, with shorter steps where control is needed and longer steps in smooth regions.
Control bounds: With time weight 0.5, controls don't fully saturate. Increase the weight to push toward bang-bang control.
Combined objectives: The + operator makes it easy to balance multiple goals.
Exercises
1. Bang-bang control: Set time weight to 5.0 - do controls saturate the bounds?
2. Fixed time: Remove Δt from controls and compare total time.
3. Add waypoint: Require passing through [0.5, 0, 0.5] at the midpoint:
constraint = NonlinearKnotPointConstraint(
x -> x - [0.5, 0, 0.5], :x, traj;
times=[div(N,2)], equality=true
)
prob = DirectTrajOptProblem(traj, obj, integrator; constraints=[constraint])4. Different goal: Try reaching [0, 1, 0] or [0.5, 0.5, 0.5]
5. Tighter bounds: Use bounds=(u = 0.5, Δt = (0.05, 0.3)) - how does time change?
This page was generated using Literate.jl.