Skip to content

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.

KindFormKeywords
:pathnonlinear   f, lb, ub, label
:boundarynonlinear   f, lb, ub, label
:statebox on state componentsrg, lb, ub, label
:controlbox on control componentsrg, lb, ub, label
:variablebox on variable componentsrg, 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.

julia
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
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)
2

The raw constraint tuples can also be retrieved:

julia
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:

julia
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
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
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,...]
└─