Skip to content

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:

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

  1. No top-level exports. using CTBase loads the package but brings no symbols into scope. Every symbol is accessed via its qualified path:
julia
CTBase.Descriptions.add # ✓ always works
CTBase.Exceptions.NotImplemented
  1. 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:
julia
using CTBase.Exceptions # brings IncorrectArgument, NotImplemented, … into scope
using CTBase.Traits     # brings Autonomous, NonAutonomous, is_autonomous, … into scope
  1. Extension-backed features. run_tests, postprocess_coverage, and automatic_reference_documentation require loading the matching weak dependency (Test, Coverage, Documenter respectively) before they become active. Likewise, the differentiation primitives of CTBase.Differentiation become active only once DifferentiationInterface and 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
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
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
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.0

For more, see the Data guide.

Next Steps

TopicGuide
Exception hierarchy and best practicesExceptions
Compile-time traits and dispatchTraits
Trait-carrying vector fields and HamiltoniansData
Descriptions catalogue and completionDescriptions
Option schema, validation, and aliasesOptions System
Strategy contract and registrationImplementing a Strategy
Routing options to strategiesOrchestration & Routing
AD backends and differentiation primitivesDifferentiation
Modular test runner setupTest Runner
Coverage report generationCoverage
Auto-generated API referenceAPI Documentation
Semantic color roles and themesColor System
Linear and piecewise-constant interpolationInterpolation
Unicode subscript/superscript helpersUnicode Helpers
Backend-agnostic plotting IR and render contractPlotting Engine
Full API referenceAPI Reference (left sidebar)