Constraints

Constraints restrict the feasible region beyond dynamics. DirectTrajOpt supports bounds, boundary conditions, and nonlinear path constraints.

using DirectTrajOpt
using NamedTrajectories
using LinearAlgebra

N = 50
traj = NamedTrajectory(
    (x = randn(2, N), u = randn(1, N), Δt = fill(0.1, N));
    timestep=:Δt, controls=:u
)
N = 50, (x = 1:2, u = 3:3, → Δt = 4:4)

Bounds (Cheapest)

Box constraints on variables:

traj_bounds = NamedTrajectory(
    (x = randn(2, N), u = randn(2, N), Δt = fill(0.1, N));
    timestep=:Δt, controls=:u,
    bounds=(
        x = 5.0,                          # -5 ≤ x ≤ 5
        u = (-1.0, 2.0),                  # -1 ≤ u ≤ 2
        Δt = (0.01, 0.5)                  # 0.01 ≤ Δt ≤ 0.5
    )
)
N = 50, (x = 1:2, u = 3:4, → Δt = 5:5)

Per-component bounds:

traj_component_bounds = NamedTrajectory(
    (x = randn(2, N), u = randn(2, N), Δt = fill(0.1, N));
    timestep=:Δt, controls=:u,
    bounds=(u = ([-1.0, -2.0], [1.0, 3.0]),)  # Different bounds per component
)
N = 50, (x = 1:2, u = 3:4, → Δt = 5:5)

Nonlinear Constraints

Inequality: c(x, u) ≥ 0 (preferred - easier to satisfy)

constraint_ineq = NonlinearKnotPointConstraint(
    u -> [1.0 - norm(u)],  # ||u|| ≤ 1
    :u, traj; times=1:N, equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#2#3"}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#2#3"}(Main.var"#2#3"()), [:u], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 1, 50)

Equality: c(x, u) = 0 (more restrictive)

constraint_eq = NonlinearKnotPointConstraint(
    x -> [x[1] - 0.5],  # x₁ = 0.5
    :x, traj; times=[25], equality=true
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#5#6"}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#5#6"}(Main.var"#5#6"()), [:x], true, [25], [nothing], 1, 2, 1)

Multiple variables:

constraint_multi = NonlinearKnotPointConstraint(
    (x, u) -> [x[1]^2 + x[2]^2 - u[1]],
    [:x, :u], traj; equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#39#40"{Main.var"#8#9", Vector{UnitRange{Int64}}}}(DirectTrajOpt.Constraints.var"#39#40"{Main.var"#8#9", Vector{UnitRange{Int64}}}(Main.var"#8#9"(), UnitRange{Int64}[1:2, 3:3]), [:x, :u], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 3, 50)

Common Patterns

Obstacle avoidance:

obs_center, obs_radius = [0.5, 0.5], 0.2
constraint_obstacle = NonlinearKnotPointConstraint(
    x -> [norm(x - obs_center)^2 - obs_radius^2],
    :x, traj; times=1:N, equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#11#12"}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#11#12"}(Main.var"#11#12"()), [:x], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 2, 50)

Multiple obstacles:

constraints_obstacles = [
    NonlinearKnotPointConstraint(
        x -> [norm(x - center)^2 - radius^2],
        :x, traj; equality=false
    )
    for (center, radius) in [([0.3, 0.3], 0.15), ([0.7, 0.7], 0.15)]
]
2-element Vector{NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#17#18"{Float64, Vector{Float64}}}}}:
 NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#17#18"{Float64, Vector{Float64}}}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#17#18"{Float64, Vector{Float64}}}(Main.var"#17#18"{Float64, Vector{Float64}}(0.15, [0.3, 0.3])), [:x], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 2, 50)
 NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#17#18"{Float64, Vector{Float64}}}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#17#18"{Float64, Vector{Float64}}}(Main.var"#17#18"{Float64, Vector{Float64}}(0.15, [0.7, 0.7])), [:x], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 2, 50)

State-dependent control limits:

constraint_state_dep = NonlinearKnotPointConstraint(
    (x, u) -> [1.0 - u[1] / (1.0 + abs(x[1]))],
    [:x, :u], traj; equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#39#40"{Main.var"#20#21", Vector{UnitRange{Int64}}}}(DirectTrajOpt.Constraints.var"#39#40"{Main.var"#20#21", Vector{UnitRange{Int64}}}(Main.var"#20#21"(), UnitRange{Int64}[1:2, 3:3]), [:x, :u], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 3, 50)

Energy constraints:

E_max = 2.0
constraint_energy = NonlinearKnotPointConstraint(
    (x, u) -> [E_max - (0.5 * norm(x)^2 + 0.5 * norm(u)^2)],
    [:x, :u], traj; equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#39#40"{Main.var"#23#24", Vector{UnitRange{Int64}}}}(DirectTrajOpt.Constraints.var"#39#40"{Main.var"#23#24", Vector{UnitRange{Int64}}}(Main.var"#23#24"(), UnitRange{Int64}[1:2, 3:3]), [:x, :u], false, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10  …  41, 42, 43, 44, 45, 46, 47, 48, 49, 50], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 3, 50)

Time Selection

All times, specific times, or ranges:

constraint_all = NonlinearKnotPointConstraint(
    u -> [1.0 - norm(u)], :u, traj; times=1:N, equality=false
)

constraint_specific = NonlinearKnotPointConstraint(
    x -> [x[1]^2 + x[2]^2 - 1.0], :x, traj;
    times=[1, 10, 20, 30, 40, 50], equality=false
)

constraint_range = NonlinearKnotPointConstraint(
    u -> [0.5 - norm(u)], :u, traj; times=10:40, equality=false
)
NonlinearKnotPointConstraint{DirectTrajOpt.Constraints.var"#31#32"{Main.var"#32#33"}}(DirectTrajOpt.Constraints.var"#31#32"{Main.var"#32#33"}(Main.var"#32#33"()), [:u], false, [10, 11, 12, 13, 14, 15, 16, 17, 18, 19  …  31, 32, 33, 34, 35, 36, 37, 38, 39, 40], [nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing  …  nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing, nothing], 1, 1, 31)

Creating a Problem

G_drift = [-0.1 1.0; -1.0 -0.1]
G_drives = [[0.0 1.0; 1.0 0.0]]
G = u -> G_drift + sum(u .* G_drives)
integrator = BilinearIntegrator(G, traj, :x, :u)
obj = QuadraticRegularizer(:u, traj, 1.0)

constraints = [constraint_obstacle, constraint_ineq]
prob = DirectTrajOptProblem(traj, obj, integrator; constraints=constraints)

Summary

Constraint TypeFormCostUse Case
Boundsl ≤ v ≤ uVery cheapPhysical limits
Dynamicsxₖ₊₁ = Φ(xₖ, uₖ)ModerateSystem evolution
Boundaryx₁ = x₀, xₖ = xfCheapInitial/final states
Nonlinear inequalityc(x, u) ≥ 0ModerateObstacles, limits
Nonlinear equalityc(x, u) = 0ExpensiveExact requirements

Performance Tips

RecommendationRationale
Use bounds over nonlinear constraintsMuch faster to evaluate
Prefer inequalities over equalitiesEasier to satisfy, larger feasible region
Scale constraint values to O(1)Better numerical conditioning
Add constraints incrementallyEasier to debug, avoids over-constraining
Check initial guess feasibilityPrevents infeasible starts

Troubleshooting

If optimizer struggles:

  • Infeasible start: Initial guess violates constraints → improve initial guess
  • Over-constrained: Too many/conflicting constraints → relax or remove some
  • Poorly scaled: Values span many orders of magnitude → rescale to O(1)
  • Tight constraints: Little feasible space → relax bounds or use soft constraints

This page was generated using Literate.jl.