CurrentModule = CTBase
CTBase defines a small hierarchy of domain-specific exceptions to make error handling explicit and consistent across the control-toolbox ecosystem.
All custom exceptions inherit from CTBase.Exceptions.CTException:
abstract type CTBase.Exceptions.CTException <: Exception endCTBase.Exceptions.CTException (abstract)
├── IncorrectArgument # Input validation errors
├── PreconditionError # Order of operations, state validation
├── NotImplemented # Unimplemented interface methods
├── ParsingError # Parsing errors
├── AmbiguousDescription # Ambiguous or incorrect descriptions
├── ExtensionError # Missing optional dependencies
└── SolverFailure # Solver/integrator failures
You should generally catch exceptions like this:
try
# call into CTBase or a package built on top of it
catch e
if e isa CTBase.Exceptions.CTException
# handle CTBase domain errors in a uniform way
@warn "CTBase error" exception=(e, catch_backtrace())
else
# non-CTBase error: rethrow so it is not hidden
rethrow()
end
endThis pattern avoids accidentally swallowing unrelated internal errors while still giving you a single place to handle all CTBase-specific problems.
CTBase.Exceptions.IncorrectArgument <: CTBase.Exceptions.CTExceptionWhen to use: Thrown when an individual argument is invalid or violates a constraint.
Fields:
msg::String: Error messagegot::Union{String,Nothing}: The invalid value received (optional)expected::Union{String,Nothing}: What was expected (optional)suggestion::Union{String,Nothing}: How to fix the problem (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Examples:
Adding a duplicate description:
using CTBase
algorithms = CTBase.Descriptions.add((), (:a, :b))
try # hide
CTBase.Descriptions.add(algorithms, (:a, :b)) # Error: duplicate
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Using invalid indices for the Unicode helpers:
using CTBase
try # hide
CTBase.Unicode.ctindice(-1) # Error: must be between 0 and 9
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Use this exception whenever one input value is outside the allowed domain (wrong range, duplicate, empty when it must not be, etc.).
CTBase.Exceptions.AmbiguousDescription <: CTBase.Exceptions.CTExceptionWhen to use: Thrown when a description (a tuple of Symbols) cannot be matched to any known
valid description.
Fields:
description::Description: The ambiguous descriptioncandidates::Union{Vector{String},Nothing}: Suggested alternatives (optional)suggestion::Union{String,Nothing}: How to fix the problem (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Example:
using CTBase
D = ((:a, :b), (:a, :b, :c), (:b, :c))
try # hide
CTBase.Descriptions.complete(:f; descriptions=D) # Error: no match found
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Use this exception when the high-level choice of description itself is wrong or ambiguous and there is no sensible default.
CTBase.Exceptions.PreconditionError <: CTBase.Exceptions.CTExceptionWhen to use: Thrown when a function is called in the wrong order or when the system is in an invalid state.
Fields:
msg::String: Error messagereason::Union{String,Nothing}: Why the precondition failed (optional)suggestion::Union{String,Nothing}: How to fix the problem (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Examples:
System initialization order:
function configure!(state::SystemState, config::Dict)
if !state.initialized
throw(CTBase.Exceptions.PreconditionError(
"System must be initialized before configuration",
reason="initialize! not called yet",
suggestion="Call initialize!(state) before configure!",
context="system configuration"
))
end
# ... configure system ...
endState validation:
function dynamics!(ocp::PreModel, f::Function)
if !__is_state_set(ocp)
throw(CTBase.Exceptions.PreconditionError(
"State must be set before defining dynamics",
reason="state has not been defined yet",
suggestion="Call state!(ocp, dimension) before dynamics!",
context="dynamics! function"
))
end
# ... set dynamics ...
endUse this exception for:
- Functions called in the wrong order
- Operations on uninitialized objects
- State machine violations
- Workflow step dependencies
Distinction from IncorrectArgument:
CTBase.Exceptions.IncorrectArgument: The value of an argument is wrongCTBase.Exceptions.PreconditionError: The timing or state is wrong
CTBase.Exceptions.NotImplemented <: CTBase.Exceptions.CTExceptionWhen to use: Used to mark interface points that must be implemented by concrete subtypes.
Fields:
msg::String: Error messagerequired_method::Union{String,Nothing}: Method signature that needs implementation (optional)suggestion::Union{String,Nothing}: How to implement (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Example:
The typical pattern is to provide a method on an abstract type that throws
NotImplemented, and then override it in each concrete implementation:
abstract type MyAbstractAlgorithm end
function run!(algo::MyAbstractAlgorithm, state)
throw(CTBase.Exceptions.NotImplemented(
"run! is not implemented for $(typeof(algo))",
required_method="run!(::$(typeof(algo)), state)",
suggestion="Implement run! for your algorithm type",
context="algorithm execution"
))
end
# Concrete implementation
struct MyConcreteAlgorithm <: MyAbstractAlgorithm end
function run!(algo::MyConcreteAlgorithm, state)
# actual implementation
endUse this exception when defining interfaces and you want an explicit,
typed error rather than a generic error("TODO").
CTBase.Exceptions.ParsingError <: CTBase.Exceptions.CTExceptionWhen to use: Intended for errors detected during parsing of input structures or DSLs (domain-specific languages).
Fields:
msg::String: Error messagelocation::Union{String,Nothing}: Where in the input the error occurred (optional)suggestion::Union{String,Nothing}: How to fix the syntax (optional)
Example:
using CTBase
try # hide
throw(CTBase.Exceptions.ParsingError(
"unexpected token 'end'",
location="line 42, column 10",
suggestion="Check for unmatched 'begin' or remove extra 'end'"
))
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Use this exception when parsing user input, configuration files, or DSL expressions.
CTBase.Exceptions.ExtensionError <: CTBase.Exceptions.CTExceptionWhen to use: Thrown when a feature requires optional dependencies (weak dependencies) that are not loaded.
Fields:
msg::String: Error messageweakdeps::Tuple{Vararg{Symbol}}: Names of missing packagesfeature::Union{String,Nothing}: What feature needs the dependencies (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Example:
function plot_results(data)
throw(CTBase.Exceptions.ExtensionError(
:Plots,
feature="result visualization",
context="plot_results function"
))
endThe enriched display automatically suggests:
using CTBase
try # hide
throw(CTBase.Exceptions.ExtensionError(
:Plots,
feature="result visualization",
context="plot_results function",
))
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Use this exception when:
- A feature requires optional packages
- Extensions are not loaded
- Weak dependencies are missing
CTBase.Exceptions.SolverFailure <: CTBase.Exceptions.CTExceptionWhen to use: Thrown when a solver (ODE integrator, optimization solver, linear solver, etc.) fails to complete successfully or returns an error code.
Fields:
msg::String: Error message describing the failureretcode::Union{String,Nothing}: Solver-specific return code (optional)suggestion::Union{String,Nothing}: How to fix the problem (optional)context::Union{String,Nothing}: Where the error occurred (optional)
Example:
using CTBase
function integrate_ode(system, integrator)
result = solve(system, integrator)
if result.retcode != :Success
throw(CTBase.Exceptions.SolverFailure(
"ODE integration failed",
retcode=string(result.retcode),
suggestion="Reduce time step or check initial conditions",
context="SciML integrator"
))
end
return result
end
nothing # hide
The enriched display shows the solver-specific return code:
try # hide
throw(CTBase.Exceptions.SolverFailure(
"ODE integration failed",
retcode=":Unstable",
suggestion="Reduce time step or check initial conditions",
context="SciML integrator",
))
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
Common return codes:
- SciML integrators:
:Unstable,:DtLessThanMin,:MaxIters,:Success - NLP solvers:
:Infeasible,:MaxIterations,:Stalled,:FirstOrder - Linear solvers: Condition number indicators, singular matrix flags
Use this exception when:
- ODE integration fails in CTFlows
- Optimization solver does not converge in CTDirect
- Linear system is ill-conditioned
- Any numerical solver returns a failure status
Distinction from other exceptions:
CTBase.Exceptions.IncorrectArgument: The input is invalidCTBase.Exceptions.PreconditionError: The state or timing is wrongCTBase.Exceptions.SolverFailure: The numerical computation itself failed
| Situation | Exception | Example |
|---|---|---|
| Invalid argument value | CTBase.Exceptions.IncorrectArgument |
throw(IncorrectArgument("x must be > 0", got="-5")) |
| Wrong function call order | CTBase.Exceptions.PreconditionError |
throw(PreconditionError("Must init first")) |
| Unimplemented interface | CTBase.Exceptions.NotImplemented |
throw(NotImplemented("run! not implemented")) |
| Parsing error | CTBase.Exceptions.ParsingError |
throw(ParsingError("unexpected token")) |
| Ambiguous description | CTBase.Exceptions.AmbiguousDescription |
throw(AmbiguousDescription((:x,))) |
| Missing optional dependency | CTBase.Exceptions.ExtensionError |
throw(ExtensionError(:Plots)) |
| Solver/integrator failure | CTBase.Exceptions.SolverFailure |
throw(SolverFailure("ODE failed")) |
All CTBase exceptions provide an enriched, user-friendly display with:
- Type name on line 1, with caller location when available
- Pipe-box layout using
│separators for visual structure - Aligned labels (Got, Expected, Reason, Hint, Context, …) padded to a common width
- Color coding: red for type name, yellow for warnings, green for hints
- Dynamic Hint for
ExtensionErrorgenerated fromweakdeps
Example of enriched display:
PreconditionError → configure!, MyModule.jl:42
│
│ System must be initialized before configuration
│
│ Reason initialize! not called yet
│
│ Context system configuration
│ Hint Call initialize!(state) before configure!
└─
This makes debugging faster by providing all the information needed to understand and fix the problem.
- Choose the right exception type: Use the decision table above
- Provide context: Always fill in optional fields when available
- Be specific: Include actual values in error messages
- Suggest solutions: Help users fix the problem
- Catch specifically: Use
e isa SpecificExceptionrather than catching all exceptions - Don't hide errors: Only catch exceptions you can handle
- Descriptions Tutorial: Understanding the description system
- Test Runner Guide: Testing exception handling