Components

The four declaration verbs populate the spaces of the problem on a PreModel. Each may be called once; calling it twice, or with inconsistent dimensions, raises a structured exception.

VerbDeclaresStores into pre.…
state!state space $x \in \mathbb{R}^n$pre.state
control!control space $u \in \mathbb{R}^m$pre.control
variable!optimisation variable $v \in \mathbb{R}^q$pre.variable
time!the interval $[t_0, t_f]$pre.times
using CTModels
import CTBase
pre = CTModels.PreModel()

Dimension, name and components

A space has a dimension, a display name, and per-component names. Omitted names are generated ("x", then "x₁", "x₂", …). Each declaration accepts String or Symbol.

CTModels.state!(pre, 2, "x", ["q", "w"])

(CTModels.dimension(pre.state),   # 2
 CTModels.name(pre.state),        # "x"
 CTModels.components(pre.state))  # ["q", "w"]
(2, "x", ["q", "w"])
CTModels.control!(pre, 1)                 # default name "u"
CTModels.components(pre.control)
1-element Vector{String}:
 "u"

The optimisation variable

variable! declares the time-independent decision variable $v$ (free final time, design parameters, …). A dimension of 0 means no variable: pre.variable then stays an EmptyVariableModel.

CTModels.variable!(pre, 2, "v")
(CTModels.dimension(pre.variable), CTModels.components(pre.variable))
(2, ["v₁", "v₂"])

Time: fixed and free ends

time! sets the interval. Each end is either fixed (a value t0=/tf=) or free (an index ind0=/indf= into $v$, optimised by the solver).

CTModels.time!(pre; t0=0.0, tf=1.0)        # both ends fixed
(CTModels.time_name(pre.times),
 CTModels.has_fixed_initial_time(pre.times),
 CTModels.has_fixed_final_time(pre.times))
("t", true, true)

A free final time stored at index 2 of $v$ (declared above with dimension 2):

pre2 = CTModels.PreModel()
CTModels.variable!(pre2, 2, "v")
CTModels.time!(pre2; t0=0.0, indf=2)
(CTModels.has_free_final_time(pre2.times), CTModels.final_time(pre2.times, [0.0, 1.5]))
(true, 1.5)

For a free time the value is read from $v$, hence final_time takes the variable vector. See Types and traits for the Fixed/Free distinction.

Naming rules

Names must be unique across all components of the problem. The validation (OCP/Validation/) rejects empty names, duplicates within a declaration, and collisions with names already declared elsewhere:

pre3 = CTModels.PreModel()
CTModels.state!(pre3, 2, "x", ["a", "b"])
try
    CTModels.control!(pre3, 1, "a")   # "a" already names a state component
catch e
    e isa CTBase.Exceptions.IncorrectArgument
end
true

These rules guarantee that a label like :a resolves unambiguously to one component when reading a solution or a constraint.