Data: typed function wrappers
The CTBase.Data submodule provides typed wrappers that carry a Julia function together with its trait metadata (see the Traits guide). Each wrapper knows, at the type level, whether it depends on time, whether it depends on an extra variable, and whether it is evaluated in-place.
Overview
| Type | Mathematical object | Out-of-place call | In-place |
|---|---|---|---|
Data.VectorField | X([t, ]x[, v]) | ✓ | |
Data.Hamiltonian | H([t, ]x, p[, v]) | — | |
Data.PseudoHamiltonian | H̃([t, ]x, p, u[, v]) | — | |
Data.ComposedHamiltonian | H([t, ]x, p[, v]) | — | |
Data.HamiltonianVectorField | HVF([t, ]x, p[, v]) | ✓ | |
Data.ControlledVectorField | fc([t, ]x, u[, v]) | — | |
Data.ComposedVectorField | g([t, ]x[, v]) | — | |
Data.ControlLaw | u([t, ]⋯[, v])† | — | |
Data.PathConstraint | g([t, ]⋯[, v])† | — | |
Data.Multiplier | μ([t, ]x, p[, v]) | — |
The mathematical spaces are:
— the state space (state dimensions). — the control space (control dimensions). — the cotangent bundle (state + costate,dimensions). — the optimisation variable space ( variable dimensions; absent when fixed). — scalars; — constraint values ( constraint dimensions). — the domain depends on an additional trait (feedback or constraint-kind).
In the call patterns, brackets […] denote optional arguments controlled by traits:
tis present whenis_autonomous=false(non-autonomous).vis present whenis_variable=true(non-fixed).†
⋯depends on an additional trait: feedback forControlLaw(see below), constraint-kind forPathConstraint(see below).
All ten share the same time-dependence and variable-dependence trait axes. VectorField and HamiltonianVectorField also carry a mutability trait (in-place / out-of-place); ControlLaw carries a feedback trait; PathConstraint carries a constraint-kind trait (see Traits).
VectorField
A VectorField wraps a Julia function representing the dynamics
The natural out-of-place call is X([t, ]x[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call X(t, x, v) always works regardless of traits (unused arguments are ignored). In-place is supported: the derivative buffer dx is prepended, giving X!(dx, [t, ]x[, v]).
Construction
using CTBase.Data, CTBase.Traits
# Autonomous, fixed (default): X(x)
vf1 = Data.VectorField(x -> -x)
# Non-autonomous, fixed: X(t, x)
vf2 = Data.VectorField((t, x) -> t .* x; is_autonomous=false)
# Autonomous, non-fixed: X(x, v)
vf3 = Data.VectorField((x, v) -> x .* v; is_variable=true)
# Non-autonomous, non-fixed: X(t, x, v)
vf4 = Data.VectorField((t, x, v) -> t .* x .+ v; is_autonomous=false, is_variable=true)
vf1VectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x)
uniform call: f(t, x, v)The show output summarises the traits and both call signatures (natural and uniform).
In-place construction
Prefer out-of-place for clarity. Use in-place when avoiding allocations matters:
# In-place Autonomous/Fixed: f(dx, x) — mutability auto-detected
vf_ip = Data.VectorField((dx, x) -> (dx .= -x; nothing))
Traits.mutability(vf_ip)CTBase.Traits.InPlaceIf the function has multiple methods, auto-detection fails; pass is_inplace explicitly:
f_multi(x) = -x
f_multi(dx, x) = (dx .= -x; nothing)
vf_explicit = Data.VectorField(f_multi; is_inplace=true)VectorField: autonomous, fixed (no variable), in-place
natural call: f(dx, x)
uniform call: f(dx, t, x, v)Calling
x0 = [1.0, 0.5]
vf1(x0) # natural call
vf1(0.0, x0, nothing) # uniform call (ignores t and v)
vf2(0.5, x0) # non-autonomous natural call2-element Vector{Float64}:
0.5
0.25Hamiltonian
A Hamiltonian wraps a scalar function
The natural call is H([t, ]x, p[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call H(t, x, p, v) always works regardless of traits. In-place is not supported (scalar return value).
Construction
# Autonomous, fixed (default): H(x, p)
h1 = Data.Hamiltonian((x, p) -> dot(p, x))
# Non-autonomous, fixed: H(t, x, p)
h2 = Data.Hamiltonian((t, x, p) -> t * dot(p, x); is_autonomous=false)
# Autonomous, non-fixed: H(x, p, v)
h3 = Data.Hamiltonian((x, p, v) -> dot(p, x) * v[1]; is_variable=true)
h1Hamiltonian: autonomous, fixed (no variable)
natural call: h(x, p)
uniform call: h(t, x, p, v)Calling
x0, p0 = [1.0, 0.5], [0.3, 0.7]
h1(x0, p0) # natural call
h1(0.0, x0, p0, nothing) # uniform call
h2(0.5, x0, p0) # non-autonomous0.32499999999999996HamiltonianVectorField
A HamiltonianVectorField wraps the map
The natural out-of-place call is HVF([t, ]x, p[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call HVF(t, x, p, v) always works regardless of traits. In-place is supported: the derivative buffers dx, dp are prepended, giving HVF!(dx, dp, [t, ]x, p[, v]).
Use this when the Hamiltonian equations are known analytically. When only the scalar Hamiltonian is available and the derivatives must be obtained by automatic differentiation, use Data.Hamiltonian instead.
Construction
# Harmonic oscillator H = (x²+p²)/2: ẋ = p, ṗ = -x
hvf = Data.HamiltonianVectorField((x, p) -> (p, -x))
# With time dependence
hvf_na = Data.HamiltonianVectorField((t, x, p) -> (p, -x .* t); is_autonomous=false)
hvfHamiltonianVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x, p)
uniform call: f(t, x, p, v)Calling
dx, dp = hvf(x0, p0) # natural call: returns (ẋ, ṗ)
(dx, dp)([0.3, 0.7], [-1.0, -0.5])The variable_costate keyword
For non-fixed Hamiltonian vector fields, the call accepts an optional variable_costate keyword (default false).
When false (default), the out-of-place call returns (dx, dp); when true, it returns the extended tuple (dx, dp, dpv) where dpv = ∂ṗ/∂v is the derivative of the costate equations with respect to the variable v. For in-place fields, dpv is passed as an optional pre-allocated buffer (dpv=nothing skips the computation):
# Inner function that implements the variable_costate path.
# H(x, p, v) = v[1] * dot(p, x) → ẋ = v[1]*x, ṗ = -v[1]*p, ∂ṗ/∂v[1] = -p
function f_vc(x, p, v; variable_costate=false)
dx = v[1] .* x
dp = -v[1] .* p
variable_costate ? (dx, dp, -p) : (dx, dp)
end
hvf_nf = Data.HamiltonianVectorField(f_vc; is_variable=true)
v0 = [2.0]
dx, dp = hvf_nf(x0, p0, v0) # returns (ẋ, ṗ)
(dx, dp)([2.0, 1.0], [-0.6, -1.4])dx, dp, dpv = hvf_nf(x0, p0, v0; variable_costate=true) # returns (ẋ, ṗ, ∂ṗ/∂v)
(dx, dp, dpv)([2.0, 1.0], [-0.6, -1.4], [-0.3, -0.7])For in-place non-fixed fields the signature is:
dpv = zeros(length(p0))
hvf_ip_nf(dx, dp, x, p, v; dpv=dpv, variable_costate=true) # fills dx, dp, dpv in placeThis is only supported by inner functions that implement the variable_costate keyword path — typically those derived from a Data.Hamiltonian by automatic differentiation. For a plain user-supplied function that does not accept variable_costate, passing true raises a CTBase.Exceptions.PreconditionError (see the Exceptions guide).
PseudoHamiltonian
A PseudoHamiltonian wraps a scalar function
The natural call is H̃([t, ]x, p, u[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call H̃(t, x, p, u, v) always works regardless of traits. In-place is not supported (scalar return value).
Unlike Data.Hamiltonian, which encodes the control implicitly, a pseudo-Hamiltonian takes the control as an additional argument. This enables dynamic closed-loop flows where the control is computed from the pseudo-Hamiltonian's maximisation condition (PMP stationarity:
Construction
# Autonomous, fixed (default): H̃(x, p, u)
ph1 = Data.PseudoHamiltonian((x, p, u) -> dot(p, x) + u^2)
# Non-autonomous, fixed: H̃(t, x, p, u)
ph2 = Data.PseudoHamiltonian((t, x, p, u) -> t * dot(p, x) + u^2; is_autonomous=false)
# Autonomous, non-fixed: H̃(x, p, u, v)
ph3 = Data.PseudoHamiltonian((x, p, u, v) -> dot(p, x) + u^2 + v[1]; is_variable=true)
ph1PseudoHamiltonian: autonomous, fixed (no variable)
natural call: h̃(x, p, u)
uniform call: h̃(t, x, p, u, v)Calling
x0, p0, u0 = [1.0, 0.5], [0.3, 0.7], 2.0
ph1(x0, p0, u0) # natural call
ph1(0.0, x0, p0, u0, nothing) # uniform call
ph2(0.5, x0, p0, u0) # non-autonomous4.325The dynamics trait of a PseudoHamiltonian is always Traits.HamiltonianDynamics, since pseudo-Hamiltonians involve both state and costate.
ComposedHamiltonian
A ComposedHamiltonian is obtained by eliminating the control from a Data.PseudoHamiltonian with a dynamic closed-loop Data.ControlLaw:
The natural call is H([t, ]x, p[, v]), where the time/variable dependences are the join of the pseudo-Hamiltonian and the control law — NonAutonomous/NonFixed win. A uniform call H(t, x, p, v) always works regardless of traits. In-place is not supported (scalar return value).
It subtypes Data.AbstractHamiltonian, so it is a Hamiltonian and can be used anywhere one is expected.
Construction
# Pseudo-Hamiltonian H̃(x, p, u) = p⋅x + u² (scalar state/costate/control)
ph = Data.PseudoHamiltonian((x, p, u) -> p * x + u^2)
# Dynamic closed-loop law u(x, p) = -p
law = Data.DynClosedLoop((x, p) -> -p)
# Composed Hamiltonian: H(x, p) = H̃(x, p, -p) = p⋅x + p²
H = Data.ComposedHamiltonian(ph, law)
HComposedHamiltonian: autonomous, fixed (no variable)
natural call: h(x, p)
uniform call: h(t, x, p, v)The composed time/variable dependences are the join of the two inputs — NonAutonomous/NonFixed win. A time-varying feedback on an autonomous pseudo-Hamiltonian correctly yields a NonAutonomous composed Hamiltonian.
Calling
x0, p0 = 1.0, 0.5
H(x0, p0) # natural call
H(0.0, x0, p0, nothing) # uniform call0.75Getters
Data.pseudo_hamiltonian(H) # the underlying PseudoHamiltonian
Data.control_law(H) # the DynClosedLoop lawControlLaw: dyn-closed-loop, autonomous, fixed (no variable)
natural call: u(x, p)
uniform call: u(t, x, p, v)The constructor rejects non-DynClosedLoop laws (OpenLoop, ClosedLoop) with a MethodError — those are the state-space path (ComposedVectorField).
ControlLaw
A ControlLaw wraps a function t and v as for other data types.
Three user-facing constructors fix the feedback trait:
| Constructor | Feedback trait | Out-of-place call |
|---|---|---|
Data.OpenLoop | OpenLoopFeedback | u([t][, v]) |
Data.ClosedLoop | ClosedLoopFeedback | u([t, ]x[, v]) |
Data.DynClosedLoop | DynClosedLoopFeedback | u([t, ]x, p[, v]) |
Construction
# Open-loop, autonomous, fixed (default): u()
u_ol = Data.OpenLoop(() -> 1.0)
# Closed-loop, autonomous, fixed: u(x)
u_cl = Data.ClosedLoop(x -> -x)
# Dynamic closed-loop, autonomous, fixed: u(x, p)
u_dcl = Data.DynClosedLoop((x, p) -> -x - p)
u_olControlLaw: open-loop, autonomous, fixed (no variable)
natural call: u()
uniform call: u(t, v)Non-autonomous and variable variants are constructed with the same keywords as other data types:
u_ol_na = Data.OpenLoop((t, v) -> t * v; is_autonomous=false, is_variable=true)ControlLaw: open-loop, non-autonomous, variable
natural call: u(t, v)
uniform call: u(t, v)Calling
Every ControlLaw is callable via its natural signature (matching the traits) and via a uniform signature that depends on the feedback trait:
| Feedback | Natural | Uniform |
|---|---|---|
OpenLoop | u([t][, v]) | u(t, v) |
ClosedLoop | u([t, ]x[, v]) | u(t, x, v) |
DynClosedLoop | u([t, ]x, p[, v]) | u(t, x, p, v) |
u_ol() # natural call
u_ol(0.0, nothing) # uniform call
u_cl(x0) # natural call
u_cl(0.0, x0, nothing) # uniform call
u_dcl(x0, p0) # natural call
u_dcl(0.0, x0, p0, nothing) # uniform call-1.5The feedback trait determines the dynamics trait: open-loop and closed-loop control laws carry Traits.StateDynamics, while dynamic closed-loop control laws carry Traits.HamiltonianDynamics.
PathConstraint
A PathConstraint wraps a function t and v as for other data types.
Three user-facing constructors fix the constraint kind:
| Constructor | Constraint kind | Out-of-place call |
|---|---|---|
Data.StateConstraint | StateConstraintKind | g([t, ]x[, v]) |
Data.ControlConstraint | ControlConstraintKind | g([t, ]u[, v]) |
Data.MixedConstraint | MixedConstraintKind | g([t, ]x, u[, v]) |
Construction
# State constraint, autonomous, fixed (default): g(x)
g_state = Data.StateConstraint(x -> x[1])
# Control constraint, autonomous, fixed: g(u)
g_ctrl = Data.ControlConstraint(u -> u[1])
# Mixed constraint, autonomous, fixed: g(x, u)
g_mixed = Data.MixedConstraint((x, u) -> x[1] + u[1])
# Non-autonomous state constraint: g(t, x)
g_state_na = Data.StateConstraint((t, x) -> t * x[1]; is_autonomous=false)
g_statePathConstraint: state, autonomous, fixed (no variable)
natural call: g(x)
uniform call: g(t, x, u, v)Calling
Every PathConstraint is callable via its natural signature (matching the traits) and via a uniform signature g(t, x, u, v) that ignores unused arguments:
x0, u0 = [1.0, 0.5], [2.0]
g_state(x0) # natural call
g_state(0.0, x0, u0, nothing) # uniform call
g_ctrl(u0) # natural call
g_mixed(x0, u0) # natural call3.0The constraint kind can be queried with the predicate functions Traits.is_state_constraint, Traits.is_control_constraint, and Traits.is_mixed_constraint:
Traits.is_state_constraint(g_state), Traits.is_control_constraint(g_ctrl), Traits.is_mixed_constraint(g_mixed)(true, true, true)Multiplier
A Multiplier wraps a function
The natural call is μ([t, ]x, p[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call μ(t, x, p, v) always works regardless of traits. In-place is not supported.
It has the same call structure as a Data.Hamiltonian — it depends on the state and costate — but carries no dynamics semantics of its own.
Construction
# Autonomous, fixed (default): μ(x, p)
μ1 = Data.Multiplier((x, p) -> x[1])
# Non-autonomous, fixed: μ(t, x, p)
μ2 = Data.Multiplier((t, x, p) -> t * x[1]; is_autonomous=false)
μ1Multiplier: autonomous, fixed (no variable)
natural call: μ(x, p)
uniform call: μ(t, x, p, v)Calling
x0, p0 = [1.0, 0.5], [0.3, 0.7]
μ1(x0, p0) # natural call
μ1(0.0, x0, p0, nothing) # uniform call
μ2(0.5, x0, p0) # non-autonomous natural call0.5ControlledVectorField
A ControlledVectorField wraps a function Data.PseudoHamiltonian: where a pseudo-Hamiltonian carries the control alongside the costate, a controlled vector field carries the control alongside the state.
The natural call is fc([t, ]x, u[, v]), where t and v are optional arguments controlled by the time-dependence and variable-dependence traits. A uniform call fc(t, x, u, v) always works regardless of traits. In-place is not supported (always out-of-place).
It carries Traits.StateDynamics.
Construction
# Autonomous, fixed (default): fc(x, u)
fc1 = Data.ControlledVectorField((x, u) -> -x + u)
# Non-autonomous, fixed: fc(t, x, u)
fc2 = Data.ControlledVectorField((t, x, u) -> t * x + u; is_autonomous=false)
# Autonomous, non-fixed: fc(x, u, v)
fc3 = Data.ControlledVectorField((x, u, v) -> v * x + u; is_variable=true)
# Scalar state and control
x0, u0 = 1.0, 2.0
fc1ControlledVectorField: autonomous, fixed (no variable)
natural call: fc(x, u)
uniform call: fc(t, x, u, v)Calling
fc1(x0, u0) # natural call
fc1(0.0, x0, u0, nothing) # uniform call
fc2(0.5, x0, u0) # non-autonomous natural call2.5ComposedVectorField
A ComposedVectorField is obtained by eliminating the control from a Data.ControlledVectorField with an open-loop or closed-loop Data.ControlLaw:
where the control is u(t, v) for an open-loop law and u(t, x, v) for a closed-loop law. It is the state-space analogue of Data.ComposedHamiltonian.
The natural call is g([t, ]x[, v]), where the time/variable dependences are the join of the controlled vector field and the control law — NonAutonomous/NonFixed win. A uniform call g(t, x, v) always works regardless of traits. In-place is not supported (always out-of-place).
It subtypes Data.AbstractVectorField with OutOfPlace mutability, so it is a vector field usable anywhere one is expected.
Construction
# Controlled vector field fc(x, u) = -x + u (scalar state/control)
fc = Data.ControlledVectorField((x, u) -> -x + u)
# Closed-loop law u(x) = -x
law = Data.ClosedLoop(x -> -x)
# Composed vector field: g(x) = fc(x, -x) = -2x
g = Data.ComposedVectorField(fc, law)
gComposedVectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x)
uniform call: f(t, x, v)The composed time/variable dependences are the join of the two inputs — NonAutonomous/NonFixed win.
Calling
g(3.0) # natural call: g(x) = -2x
g(0.0, 3.0, nothing) # uniform call (t, x, v)-6.0Getters
Data.controlled_vector_field(g) # the underlying ControlledVectorField
Data.control_law(g) # the ClosedLoop lawControlLaw: closed-loop, autonomous, fixed (no variable)
natural call: u(x)
uniform call: u(t, x, v)The constructor rejects DynClosedLoop laws with a MethodError — that is the Hamiltonian path (ComposedHamiltonian).
Querying traits
Because the trait metadata lives in the type, it can be recovered from any data object through the Traits accessors — with no runtime cost:
Traits.time_dependence(vf2) # NonAutonomousCTBase.Traits.NonAutonomousTraits.variable_dependence(vf3) # NonFixedCTBase.Traits.NonFixedTraits.mutability(vf_ip) # InPlaceCTBase.Traits.InPlaceThe boolean predicates follow generically:
Traits.is_autonomous(vf1), Traits.is_variable(vf1), Traits.is_inplace(vf1)(true, false, false)Each data family also reports its dynamics trait via Traits.dynamics_trait:
Traits.dynamics_trait(vf1), Traits.dynamics_trait(h1), Traits.dynamics_trait(hvf)(CTBase.Traits.StateDynamics, CTBase.Traits.HamiltonianDynamics, CTBase.Traits.HamiltonianDynamics)A VectorField carries Traits.StateDynamics, while both Hamiltonian and HamiltonianVectorField carry Traits.HamiltonianDynamics.
Typed constructors
A lower-level constructor takes the trait types directly (no keyword inference). It is used internally by code that produces typed data objects programmatically:
vf_typed = Data.VectorField(x -> 2x, Traits.Autonomous, Traits.Fixed, Traits.OutOfPlace)
Traits.time_dependence(vf_typed)CTBase.Traits.AutonomousThe same positional form exists for Data.Hamiltonian (Hamiltonian(f, TD, VD)), Data.PseudoHamiltonian (PseudoHamiltonian(f, TD, VD)), Data.HamiltonianVectorField (HamiltonianVectorField(f, TD, VD, MD)), and Data.ControlLaw (ControlLaw(f, FB, TD, VD)), Data.ControlledVectorField (ControlledVectorField(f, TD, VD)), Data.PathConstraint (PathConstraint(f, K, TD, VD)), Data.Multiplier (Multiplier(f, TD, VD)).
See also
Data.VectorField,Data.Hamiltonian,Data.PseudoHamiltonian,Data.ComposedHamiltonian,Data.HamiltonianVectorField,Data.ControlledVectorField,Data.ComposedVectorField,Data.ControlLaw,Data.PathConstraint,Data.Multiplier— concrete data types.Data.AbstractVectorField,Data.AbstractHamiltonian,Data.AbstractPseudoHamiltonian,Data.AbstractHamiltonianVectorField,Data.AbstractControlledVectorField,Data.AbstractControlLaw,Data.AbstractPathConstraint,Data.AbstractMultiplier— abstract supertypes.Data.pseudo_hamiltonian,Data.control_law,Data.controlled_vector_field— getters for composed types.Traits — the three trait axes, the dynamics trait, and call-signature tables.
Exceptions —
PreconditionErrorandIncorrectArgument, raised by the constructors and thevariable_costatepath.