Constraints
constraint! adds one constraint at a time to the pre-model. The first positional argument is the kind; the keywords give the bounds, the function or range, and a label used later to read back the constraint and its dual.
| Kind | Form | Keywords |
|---|---|---|
:path | nonlinear | f, lb, ub, label |
:boundary | nonlinear | f, lb, ub, label |
:state | box on state components | rg, lb, ub, label |
:control | box on control components | rg, lb, ub, label |
:variable | box on variable components | rg, lb, ub, label |
Nonlinear constraint functions are written in place like the dynamics: f(r, t, x, u, v) for :path, f(r, x0, xf, v) for :boundary.
using CTModels
pre = CTModels.PreModel()
CTModels.variable!(pre, 2, "v")
CTModels.time!(pre; t0=0.0, tf=1.0)
CTModels.state!(pre, 2)
CTModels.control!(pre, 1)
CTModels.dynamics!(pre, (r, t, x, u, v) -> (r[1] = x[2]; r[2] = u[1]; nothing))
CTModels.objective!(pre, :min; lagrange=(t, x, u, v) -> u[1]^2)
# Nonlinear path and boundary constraints
CTModels.constraint!(pre, :path;
f=(r, t, x, u, v) -> (r[1] = x[1] + u[1]; nothing), lb=[0.0], ub=[1.0], label=:p)
CTModels.constraint!(pre, :boundary;
f=(r, x0, xf, v) -> (r[1] = x0[1]; r[2] = xf[1]; nothing),
lb=[0.0, 0.0], ub=[0.0, 0.0], label=:bc)
# Box constraints on state, control and variable components
CTModels.constraint!(pre, :state; rg=1:1, lb=[-1.0], ub=[1.0], label=:x1)
CTModels.constraint!(pre, :control; rg=1:1, lb=[-10.0], ub=[10.0], label=:u)
CTModels.constraint!(pre, :variable; rg=1:2, lb=[0.0, 0.0], ub=[1.0, 1.0], label=:vbox)
CTModels.time_dependence!(pre; autonomous=true)
ocp = CTModels.build(pre)The (autonomous) optimal control problem is of the form:
minimize J(x, u, v) = ∫ f⁰(x(t), u(t), v) dt, over [0.0, 1.0]
subject to
ẋ(t) = f(x(t), u(t), v), t in [0.0, 1.0] a.e.,
ψ₋ ≤ ψ(x(t), u(t), v) ≤ ψ₊,
ϕ₋ ≤ ϕ(x(0.0), x(1.0), v) ≤ ϕ₊,
x₋ ≤ x(t) ≤ x₊,
u₋ ≤ u(t) ≤ u₊,
v₋ ≤ v ≤ v₊,
where x(t) ∈ R², u(t) ∈ R and v ∈ R².Reading constraints back
On the built Model, the constraints are grouped in a ConstraintsModel and queried by dimension or by label:
julia> CTModels.dim_path_constraints_nl(ocp)
1
julia> CTModels.dim_boundary_constraints_nl(ocp)
2
julia> CTModels.dim_state_constraints_box(ocp)
1
julia> CTModels.dim_control_constraints_box(ocp)
1
julia> CTModels.dim_variable_constraints_box(ocp)
2The raw constraint tuples can also be retrieved:
julia> CTModels.path_constraints_nl(ocp)
([0.0], Main.var"#8#9"(), [1.0], [:p])
julia> CTModels.boundary_constraints_nl(ocp)
([0.0, 0.0], Main.var"#11#12"(), [0.0, 0.0], [:bc, :bc])
julia> CTModels.state_constraints_box(ocp)
([-1.0], [1], [1.0], [:x1], [[:x1]])
julia> CTModels.control_constraints_box(ocp)
([-10.0], [1], [10.0], [:u], [[:u]])
julia> CTModels.variable_constraints_box(ocp)
([0.0, 0.0], [1, 2], [1.0, 1.0], [:vbox, :vbox], [[:vbox], [:vbox]])Use isempty_constraints to check whether the model has any constraints at all, and constraint to retrieve a single constraint by label.
Labels and aliases
Before building, constraints live in a ConstraintsDictType keyed by label. Box constraints obey a per-component uniqueness invariant: if several declarations touch the same component, their bounds are intersected and all their labels are kept as aliases. This is what lets constraint and dual resolve any of the original labels to the merged component — see the Duals guide.
One declaration, one label
Always pass an explicit label. It is the stable handle for the constraint across build, solution reconstruction, and dual extraction; auto-generated labels are harder to track in downstream packages.
Error cases
Passing the same label twice raises a PreconditionError immediately:
pre_dup = CTModels.PreModel()
CTModels.variable!(pre_dup, 0)
CTModels.time!(pre_dup; t0=0.0, tf=1.0)
CTModels.state!(pre_dup, 2)
CTModels.control!(pre_dup, 1)
CTModels.constraint!(pre_dup, :state; rg=1:1, lb=[-1.0], ub=[1.0], label=:x1)julia> CTModels.constraint!(pre_dup, :control; rg=1:1, lb=[-10.0], ub=[10.0], label=:x1) # duplicate
PreconditionError → #__constraint!#18, constraints.jl:68
│
│ Constraint already exists
│
│ Reason constraint with label 'x1' is already defined
│
│ Context constraint! function - duplicate label validation
│ Hint Use a different label or remove the existing constraint first
└─Bounds with lb > ub are rejected on the spot:
julia> CTModels.constraint!(pre_dup, :state; rg=2:2, lb=[1.0], ub=[0.0], label=:bad)
IncorrectArgument → #__constraint!#18, constraints.jl:106
│
│ Invalid bounds: lower > upper
│
│ Got some lb[i] > ub[i] violations
│ Expected lb[i] ≤ ub[i] for all i
│
│ Context constraint!(ocp, type=:state, lb=[...], ub=[...]) - validating bounds order
│ Hint Check bounds values: lb=[1.0,...] ≤ ub=[0.0,...]
└─