Getting Started
Installation
CTBase.jl is typically installed as a dependency of another package in the ecosystem (e.g. OptimalControl.jl). To install it directly:
import Pkg
Pkg.add("CTBase")Requires Julia ≥ 1.10.
Mental Model
CTBase is the base layer of the control-toolbox ecosystem. It provides infrastructure shared by every package above it.
Three things to keep in mind:
- No top-level exports.
using CTBaseloads the package but brings no symbols into scope. Every symbol is accessed via its qualified path:
CTBase.Descriptions.add # ✓ always works
CTBase.Exceptions.NotImplemented- Submodule-first API. The public API lives in named submodules (
Core,Exceptions,Traits,Data,Descriptions,Options,Strategies,Orchestration,Differentiation,Interpolation,DevTools,Unicode, …). You can bring a submodule's exports into scope explicitly:
using CTBase.Exceptions # brings IncorrectArgument, NotImplemented, … into scope
using CTBase.Traits # brings Autonomous, NonAutonomous, is_autonomous, … into scope- Extension-backed features.
run_tests,postprocess_coverage, andautomatic_reference_documentationrequire loading the matching weak dependency (Test,Coverage,Documenterrespectively) before they become active. Likewise, the differentiation primitives ofCTBase.Differentiationbecome active only onceDifferentiationInterfaceand an AD package (e.g.ForwardDiff) are loaded.
5-Minute Walkthrough
Working with Descriptions
A description is a Tuple of Symbols that declaratively identifies an algorithm or configuration. Catalogues collect known descriptions; complete resolves a partial description to an exact one.
julia> using CTBase
julia> # Build a catalogue
descs = CTBase.Descriptions.add((), (:euler, :explicit))
(:euler, :explicit)
julia> descs = CTBase.Descriptions.add(descs, (:euler, :implicit))
(:euler, :explicit)
(:euler, :implicit)
julia> descs = CTBase.Descriptions.add(descs, (:runge_kutta, :explicit))
(:euler, :explicit)
(:euler, :implicit)
(:runge_kutta, :explicit)
julia> # Partial completion: find the unique entry containing :implicit
CTBase.Descriptions.complete(:implicit; descriptions=descs)
(:euler, :implicit)
julia> # :euler matches two entries; priority (first in catalog) resolves the tie
CTBase.Descriptions.complete(:euler; descriptions=descs)
(:euler, :explicit)
julia> # No entry contains both :runge_kutta and :implicit → raises AmbiguousDescription
CTBase.Descriptions.complete(:runge_kutta, :implicit; descriptions=descs)
AmbiguousDescription → top-level scope, REPL[7]:2
│
│ cannot find matching description
│
│ Diagnostic No complete match — no description contains all symbols
│ Requested (:runge_kutta, :implicit)
│ Available (:euler, :explicit)
│ (:euler, :implicit)
│ (:runge_kutta, :explicit)
│
│ Context description completion
│ Hint Try one of the closest matches:
└─For more, see the Descriptions guide.
Working with Exceptions
CTBase defines a typed exception hierarchy rooted at CTBase.Exceptions.CTException. Each type carries structured context fields for actionable error messages.
julia> # IncorrectArgument — invalid input value
throw(CTBase.Exceptions.IncorrectArgument(
"state dimension must be positive";
got="0",
expected="n > 0",
suggestion="Pass a positive integer for the state dimension",
))
IncorrectArgument → top-level scope, REPL[1]:2
│
│ state dimension must be positive
│
│ Got 0
│ Expected n > 0
│
│ Hint Pass a positive integer for the state dimension
└─
julia> # NotImplemented — interface stub
throw(CTBase.Exceptions.NotImplemented(
"solve! is not implemented";
required_method="solve!(::MyStrategy, ocp)",
suggestion="Import the package that provides this strategy",
))
NotImplemented → top-level scope, REPL[2]:2
│
│ solve! is not implemented
│
│ Method solve!(::MyStrategy, ocp)
│
│ Hint Import the package that provides this strategy
└─For more, see the Exceptions guide.
Working with Data
The CTBase.Data submodule wraps a Julia function together with its trait metadata (time, variable, and mutability dependence). The wrapper picks the right call path at compile time, and the traits can be recovered from the type.
julia> # Autonomous, fixed, out-of-place vector field: X(x)
vf = CTBase.Data.VectorField(x -> -x)
VectorField: autonomous, fixed (no variable), out-of-place
natural call: f(x)
uniform call: f(t, x, v)
julia> CTBase.Traits.time_dependence(vf) # Autonomous
CTBase.Traits.Autonomous
julia> CTBase.Traits.mutability(vf) # OutOfPlace (auto-detected from arity)
CTBase.Traits.OutOfPlace
julia> CTBase.Traits.dynamics_trait(vf) # StateDynamics
CTBase.Traits.StateDynamics
julia> vf([1.0, 2.0]) # natural call
2-element Vector{Float64}:
-1.0
-2.0
julia> vf(0.0, [1.0, 2.0], nothing) # uniform call (ignores t and v)
2-element Vector{Float64}:
-1.0
-2.0For more, see the Data guide.
Next Steps
| Topic | Guide |
|---|---|
| Exception hierarchy and best practices | Exceptions |
| Compile-time traits and dispatch | Traits |
| Trait-carrying vector fields and Hamiltonians | Data |
| Descriptions catalogue and completion | Descriptions |
| Option schema, validation, and aliases | Options System |
| Strategy contract and registration | Implementing a Strategy |
| Routing options to strategies | Orchestration & Routing |
| AD backends and differentiation primitives | Differentiation |
| Modular test runner setup | Test Runner |
| Coverage report generation | Coverage |
| Auto-generated API reference | API Documentation |
| Semantic color roles and themes | Color System |
| Linear and piecewise-constant interpolation | Interpolation |
| Unicode subscript/superscript helpers | Unicode Helpers |
| Backend-agnostic plotting IR and render contract | Plotting Engine |
| Full API reference | API Reference (left sidebar) |