Skip to content

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  pre.state
control!control space  pre.control
variable!optimisation variable  pre.variable
time!the interval pre.times
julia
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.

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

julia> CTModels.dimension(pre.state)
2

julia> CTModels.name(pre.state)
"x"

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

julia> CTModels.components(pre.control)
1-element Vector{String}:
 "u"

The optimisation variable

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

julia
julia> CTModels.variable!(pre, 2, "v")

julia> CTModels.dimension(pre.variable)
2

julia> CTModels.components(pre.variable)
2-element Vector{String}:
 "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 , optimised by the solver).

julia
julia> CTModels.time!(pre; t0=0.0, tf=1.0)        # both ends fixed

julia> CTModels.time_name(pre.times)
"t"

julia> CTModels.has_fixed_initial_time(pre.times)
true

julia> CTModels.has_fixed_final_time(pre.times)
true

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

julia
pre2 = CTModels.PreModel()
CTModels.variable!(pre2, 2, "v")
CTModels.time!(pre2; t0=0.0, indf=2)
julia
julia> CTModels.has_free_final_time(pre2.times)
true

julia> CTModels.final_time(pre2.times, [0.0, 1.5])
1.5

For a free time the value is read from , hence final_time takes the variable vector. The companion accessor initial_time works the same way. See Types and traits for the Fixed/Free distinction.

Naming rules

Names must be unique across all components of the problem. The validation in CTModels.Building (see __validate_name_uniqueness) rejects empty names, duplicates within a declaration, and collisions with names already declared elsewhere:

julia
pre3 = CTModels.PreModel()
CTModels.state!(pre3, 2, "x", ["a", "b"])
julia
julia> CTModels.control!(pre3, 1, "a")   # "a" already names a state component
IncorrectArgument  __validate_name_uniqueness, name_validation.jl:203

│  control name conflicts with existing names

│  Got       name='a'
│  Expected  unique name not in: ["x", "a", "b"]

│  Context   control! global name validation
│  Hint      Choose a different name that doesn't conflict with existing components
└─

Each verb may be called at most once per PreModel. A second call raises a PreconditionError:

julia
pre4 = CTModels.PreModel()
CTModels.state!(pre4, 2)
julia
julia> CTModels.state!(pre4, 1)          # state already declared
PreconditionError  state!, state.jl:71

│  State already set

│  Reason   state has already been defined for this OCP

│  Context  state! function - duplicate definition check
│  Hint     Create a new OCP instance or use the existing state definition
└─

Dimensions must be strictly positive:

julia
julia> CTModels.state!(CTModels.PreModel(), 0)
IncorrectArgument  state!, state.jl:77

│  Invalid dimension: must be positive

│  Got       n=0
│  Expected  n > 0 (positive integer)

│  Context   state!(ocp, n=0, name="x") - validating dimension parameter
│  Hint      Use state!(ocp, n=3) with n > 0
└─

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

Symbolic definitions

A Definition wraps a Julia Expr that captures the original problem definition (e.g. from a macro-based DSL). The wrapped expression is retrieved with expression; an EmptyDefinition returns an empty block :(begin end). Use definition! on the PreModel to attach one before building.