Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "CTBase"
uuid = "54762871-cc72-4466-b8e8-f6c8b58076cd"
version = "0.21.1-beta"
version = "0.22.0-beta"
authors = ["Olivier Cots <olivier.cots@irit.fr>", "Jean-Baptiste Caillau <caillau@univ-cotedazur.fr>"]

[deps]
Expand Down
28 changes: 28 additions & 0 deletions docs/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,34 @@ let ctOutDir = ''

And reference them in `head` using `${baseTemp.base}assets/...` instead of the remote URLs.

#### Simplify nav configuration

The generated `docs/src/.vitepress/config.mts` includes a placeholder for the navigation that spreads from `navTemp.nav`. Replace it with a direct array definition:

Replace:

```typescript
const navTemp = {
nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
}

const nav = [
...navTemp.nav,
{
component: 'VersionPicker'
}
]
```

With:

```typescript
const nav = [
{ text: 'Home', link: '/index' },
{component: 'VersionPicker'}
]
```

### 6. Install npm dependencies

```bash
Expand Down
7 changes: 7 additions & 0 deletions docs/api_reference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ function generate_api_reference(src_dir::String)
joinpath("Interpolation", "Interpolation.jl"), joinpath("Interpolation", "types.jl"),
joinpath("Interpolation", "ctinterpolate.jl"), joinpath("Interpolation", "display.jl"),
)),
(mod=CTBase.Traits, title="Traits", filename="traits", files=src(
joinpath("Traits", "Traits.jl"), joinpath("Traits", "helpers.jl"),
joinpath("Traits", "abstract.jl"), joinpath("Traits", "mode.jl"),
joinpath("Traits", "dynamics.jl"), joinpath("Traits", "ad.jl"),
joinpath("Traits", "variable_costate.jl"), joinpath("Traits", "mutability.jl"),
joinpath("Traits", "time_dependence.jl"), joinpath("Traits", "variable_dependence.jl"),
)),
(mod=CTBase.Options, title="Options", filename="options", files=src(
joinpath("Options", "Options.jl"), joinpath("Options", "not_provided.jl"),
joinpath("Options", "option_value.jl"), joinpath("Options", "option_definition.jl"),
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ with_api_reference(src_dir) do api_pages
"Core Concepts" => [
"Descriptions" => joinpath("guide", "descriptions.md"),
"Exceptions" => joinpath("guide", "exceptions.md"),
"Traits" => joinpath("guide", "traits.md"),
],
"Strategies & Options" => [
"Options System" => joinpath("guide", "options-system.md"),
Expand Down
10 changes: 2 additions & 8 deletions docs/src/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,9 @@ const baseTemp = {
base: 'REPLACE_ME_DOCUMENTER_VITEPRESS',// TODO: replace this in makedocs!
}

const navTemp = {
nav: 'REPLACE_ME_DOCUMENTER_VITEPRESS',
}

const nav = [
...navTemp.nav,
{
component: 'VersionPicker'
}
{ text: 'Home', link: '/index' },
{component: 'VersionPicker'}
]

// https://vitepress.dev/reference/site-config
Expand Down
4 changes: 3 additions & 1 deletion docs/src/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ Three things to keep in mind:
CTBase.Exceptions.NotImplemented
```
2. **Submodule-first API.** The public API lives in named submodules
(`Descriptions`, `Exceptions`, `DevTools`, `Core`, `Unicode`).
(`Core`, `Descriptions`, `Exceptions`, `Traits`, `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
```
3. **Extension-backed features.** `run_tests`, `postprocess_coverage`, and
`automatic_reference_documentation` require loading the matching weak dependency
Expand Down Expand Up @@ -111,6 +112,7 @@ For more, see the **[Exceptions guide](guide/exceptions.md)**.
| :--- | :--- |
| Descriptions catalogue and completion | [Descriptions](guide/descriptions.md) |
| Exception hierarchy and best practices | [Exceptions](guide/exceptions.md) |
| Compile-time traits and dispatch | [Traits](guide/traits.md) |
| Modular test runner setup | [Test Runner](guide/test-runner.md) |
| Coverage report generation | [Coverage](guide/coverage.md) |
| Auto-generated API reference | [API Documentation](guide/api-documentation.md) |
Expand Down
149 changes: 149 additions & 0 deletions docs/src/guide/traits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Traits: compile-time properties

```@meta
CurrentModule = CTBase
```

[`CTBase.Traits`](@ref CTBase.Traits) provides a small set of **compile-time
traits** shared across the control-toolbox ecosystem. A trait is an abstract type
used as a **type parameter** or returned by an accessor function so that
behaviour can be selected by dispatch, with **no runtime cost**.

```@repl traits
using CTBase
import CTBase.Traits
```

A typical use case is encoding a property of a callable object — does it take a
time argument? does it depend on an extra variable? is it evaluated in-place? —
so that a wrapper type can select the correct call path at compile time, without
runtime conditionals.

## Trait families

### Time dependence

Does the object depend on time ``t``?

| Type | Meaning |
|---|---|
| [`Traits.Autonomous`](@ref CTBase.Traits.Autonomous) | ``t`` is not an argument |
| [`Traits.NonAutonomous`](@ref CTBase.Traits.NonAutonomous) | ``t`` must be supplied |

Both are abstract subtypes of [`Traits.TimeDependence`](@ref CTBase.Traits.TimeDependence).
Because they are abstract, they can only appear as type parameters — they are not
instantiated, only dispatched upon.

```@repl traits
Traits.Autonomous <: Traits.TimeDependence
Traits.NonAutonomous <: Traits.TimeDependence
```

### Variable dependence

Does the object depend on an extra parameter ``v`` (e.g. a free final time or a
design variable)?

| Type | Meaning |
|---|---|
| [`Traits.Fixed`](@ref CTBase.Traits.Fixed) | no ``v`` argument |
| [`Traits.NonFixed`](@ref CTBase.Traits.NonFixed) | ``v`` must be supplied |

Both are concrete subtypes of [`Traits.VariableDependence`](@ref CTBase.Traits.VariableDependence):

```@repl traits
Traits.Fixed <: Traits.VariableDependence
Traits.NonFixed <: Traits.VariableDependence
```

### Mutability

Does a function allocate a new output, or write into a pre-allocated buffer?

| Type | Meaning |
|---|---|
| [`Traits.OutOfPlace`](@ref CTBase.Traits.OutOfPlace) | returns a new value |
| [`Traits.InPlace`](@ref CTBase.Traits.InPlace) | writes into a buffer (first arg) |

### Other families

These traits encode properties that are fixed at construction time and carried
as type parameters. They are not opted into via the two-method contract described
below — they are passed directly as type arguments.

| Family | Values |
|---|---|
| Integration mode | [`Traits.EndPointMode`](@ref CTBase.Traits.EndPointMode), [`Traits.TrajectoryMode`](@ref CTBase.Traits.TrajectoryMode) |
| Dynamics | [`Traits.StateDynamics`](@ref CTBase.Traits.StateDynamics), [`Traits.HamiltonianDynamics`](@ref CTBase.Traits.HamiltonianDynamics), [`Traits.AugmentedHamiltonianDynamics`](@ref CTBase.Traits.AugmentedHamiltonianDynamics) |
| Automatic differentiation | [`Traits.WithAD`](@ref CTBase.Traits.WithAD), [`Traits.WithoutAD`](@ref CTBase.Traits.WithoutAD) |
| Variable costate | [`Traits.SupportsVariableCostate`](@ref CTBase.Traits.SupportsVariableCostate), [`Traits.NoVariableCostate`](@ref CTBase.Traits.NoVariableCostate) |

## The trait contract

A type opts in to a trait by implementing **two methods**: one declaring that it
*has* the trait, and one returning the trait *value*. The boolean predicates
([`Traits.is_autonomous`](@ref CTBase.Traits.is_autonomous),
[`Traits.is_variable`](@ref CTBase.Traits.is_variable), …) then follow
generically — they are not implemented per type.

For time and variable dependence:

```@repl traits
struct MyObject end

Traits.has_time_dependence_trait(::MyObject) = true
Traits.time_dependence(::MyObject) = Traits.NonAutonomous

Traits.has_variable_dependence_trait(::MyObject) = true
Traits.variable_dependence(::MyObject) = Traits.Fixed

obj = MyObject()
Traits.is_autonomous(obj)
Traits.is_nonautonomous(obj)
Traits.is_variable(obj)
Traits.is_nonvariable(obj)
```

For mutability, the same pattern applies with `has_mutability_trait` and `mutability`:

```@repl traits
struct MyMutableObject end

Traits.has_mutability_trait(::MyMutableObject) = true
Traits.mutability(::MyMutableObject) = Traits.InPlace

Traits.is_inplace(MyMutableObject())
Traits.is_outofplace(MyMutableObject())
```

If a type does not declare a trait, the predicates throw an informative error
rather than returning a wrong default:

```@repl traits
try # hide
Traits.is_autonomous(3.14)
catch e # hide
showerror(IOContext(stdout, :color => false), e) # hide
end # hide
```

!!! note "Trait types are shared"
Because trait types (e.g. `Traits.Autonomous`) are defined in a single place,
a type that carries `Traits.Autonomous` as a type parameter and an object that
implements `time_dependence` returning `Traits.Autonomous` are compatible by
construction — no conversion or mapping needed.

## Accessor / predicate summary

| Trait value function | Boolean predicates |
|---|---|
| [`Traits.time_dependence`](@ref CTBase.Traits.time_dependence) | `is_autonomous`, `is_nonautonomous` |
| [`Traits.variable_dependence`](@ref CTBase.Traits.variable_dependence) | `is_variable`, `is_nonvariable`, `has_variable` |
| [`Traits.mutability`](@ref CTBase.Traits.mutability) | `is_inplace`, `is_outofplace` |
| [`Traits.ad_trait`](@ref CTBase.Traits.ad_trait) | — |
| [`Traits.variable_costate_trait`](@ref CTBase.Traits.variable_costate_trait) | — |
| [`Traits.dynamics_trait`](@ref CTBase.Traits.dynamics_trait) | — |

## See Also

- [Exceptions guide](exceptions.md) — understanding `IncorrectArgument` and `NotImplemented`.
2 changes: 2 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ It provides the **base layer** shared by all packages: common types, structured
| [`CTBase.Core`](@ref) | Fundamental numeric type alias (`ctNumber`) and internal display helpers |
| [`CTBase.Descriptions`](@ref) | Symbolic description tuples: catalogue management, pattern completion, similarity search |
| [`CTBase.Exceptions`](@ref) | Typed exception hierarchy with rich context fields |
| [`CTBase.Traits`](@ref) | Compile-time trait types for time dependence, variable dependence, mutability, and dynamics dispatch |
| [`CTBase.DevTools`](@ref) | Developer tools with tag-based dispatch for `run_tests`, `postprocess_coverage`, and `automatic_reference_documentation` |
| [`CTBase.Unicode`](@ref) | Unicode subscript/superscript helpers for display |

Expand All @@ -35,6 +36,7 @@ It provides the **base layer** shared by all packages: common types, structured
- **[Getting Started](getting-started.md)** — installation, mental model, 5-minute walkthrough.
- **[Descriptions](guide/descriptions.md)** — catalogue API, pattern matching, error handling.
- **[Exceptions](guide/exceptions.md)** — exception hierarchy, choosing the right type, best practices.
- **[Traits](guide/traits.md)** — compile-time trait types, the opt-in contract, and predicate functions.
- **[Test Runner](guide/test-runner.md)** — modular test infrastructure with `CTBase.DevTools.run_tests`.
- **[Coverage](guide/coverage.md)** — post-processing coverage artifacts with `CTBase.postprocess_coverage`.
- **[API Documentation](guide/api-documentation.md)** — auto-generating per-module API pages.
Expand Down
4 changes: 4 additions & 0 deletions src/CTBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ using .Strategies
include(joinpath(@__DIR__, "Orchestration", "Orchestration.jl"))
using .Orchestration

# Traits module - trait types and trait-based dispatch (moved from CTFlows)
include(joinpath(@__DIR__, "Traits", "Traits.jl"))
using .Traits

# Unicode module - Unicode character utilities
include(joinpath(@__DIR__, "Unicode", "Unicode.jl"))
using .Unicode
Expand Down
8 changes: 4 additions & 4 deletions src/Strategies/api/describe_registry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ julia> _strategy_type_name(Collocation)
- This is the most common case, handling concrete instantiated types
- For parameterized types, each parameter is formatted recursively

See also: [`describe`](@ref), [`_describe_parameter_registry`](@ref)
See also: [`CTBase.Strategies.describe`](@ref), [`CTBase.Strategies._describe_parameter_registry`](@ref)
"""
function _strategy_type_name(T::DataType)
base_name = string(T.name.name)
Expand Down Expand Up @@ -427,7 +427,7 @@ the type parameter variable name.
- This is a fallback for generic types that are not yet instantiated
- Less common than the DataType method in typical usage

See also: [`_strategy_type_name(::DataType)`](@ref)
See also: [`CTBase.Strategies._strategy_type_name(::DataType)`](@ref)
"""
function _strategy_type_name(T::UnionAll)
base_name = string(T.body.name.name)
Expand All @@ -452,7 +452,7 @@ This is the most general fallback method for types that don't match more specifi
- This is the ultimate fallback for edge cases
- Simply converts the type to a string representation

See also: [`_strategy_type_name(::DataType)`](@ref), [`_strategy_type_name(::UnionAll)`](@ref)
See also: [`CTBase.Strategies._strategy_type_name(::DataType)`](@ref), [`CTBase.Strategies._strategy_type_name(::UnionAll)`](@ref)
"""
function _strategy_type_name(T::Type)
return string(T)
Expand Down Expand Up @@ -488,7 +488,7 @@ julia> _strategy_base_name(Collocation)
- Used specifically for strategy headers to avoid redundancy with parameter display
- Handles both DataType and UnionAll types

See also: [`_strategy_type_name`](@ref), [`describe`](@ref)
See also: [`CTBase.Strategies._strategy_type_name`](@ref), [`CTBase.Strategies.describe`](@ref)
"""
function _strategy_base_name(T::DataType)
return string(T.name.name)
Expand Down
8 changes: 4 additions & 4 deletions src/Strategies/contract/abstract_strategy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ julia> modeler[:maxiter] # Alias - automatically resolved
- All functionality (alias resolution, provenance tracking) is handled by StrategyOptions
- Use `options(strategy)` for full access to OptionValue objects with source information

See also: [`options`](@ref), [`Base.haskey`](@ref), [`Base.keys`](@ref), [`CTBase.Strategies.StrategyOptions`](@extref)
See also: [`CTBase.Strategies.options`](@ref), [`Base.haskey`](@ref), [`Base.keys`](@ref), [`CTBase.Strategies.StrategyOptions`](@ref)
"""
function Base.getindex(strategy::AbstractStrategy, key::Symbol)
return options(strategy)[key]
Expand Down Expand Up @@ -340,7 +340,7 @@ false
- This is syntactic sugar for `haskey(options(strategy), key)`
- Aliases are automatically resolved to canonical names

See also: [`options`](@ref), [`Base.getindex`](@ref), [`Base.keys`](@ref), [`CTBase.Strategies.StrategyOptions`](@extref)
See also: [`CTBase.Strategies.options`](@ref), [`Base.getindex`](@ref), [`Base.keys`](@ref), [`CTBase.Strategies.StrategyOptions`](@ref)
"""
function Base.haskey(strategy::AbstractStrategy, key::Symbol)
return haskey(options(strategy), key)
Expand Down Expand Up @@ -372,7 +372,7 @@ julia> collect(keys(modeler))
- This is syntactic sugar for `keys(options(strategy))`
- Returns canonical names only (not aliases)

See also: [`options`](@ref), [`Base.getindex`](@ref), [`Base.haskey`](@ref), [`CTBase.Strategies.StrategyOptions`](@extref)
See also: [`CTBase.Strategies.options`](@ref), [`Base.getindex`](@ref), [`Base.haskey`](@ref), [`CTBase.Strategies.StrategyOptions`](@ref)
"""
function Base.keys(strategy::AbstractStrategy)
return keys(options(strategy))
Expand Down Expand Up @@ -566,7 +566,7 @@ description(::Type{<:Modelers.ADNLP}) =
"NLP modeler using ADNLPModels.\\nSee: https://jso.dev/ADNLPModels.jl"
```

See also: [`describe`](@ref), [`AbstractStrategy`](@ref)
See also: [`CTBase.Strategies.describe`](@ref), [`CTBase.Strategies.AbstractStrategy`](@ref)
"""
description(::Type{<:AbstractStrategy}) = nothing

Expand Down
6 changes: 3 additions & 3 deletions src/Strategies/contract/parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ julia> description(GPU)
"GPU-based computation"
\`\`\`

See also: [`id`](@ref), [`AbstractStrategyParameter`](@ref)
See also: [`CTBase.Strategies.id`](@ref), [`CTBase.Strategies.AbstractStrategyParameter`](@ref)
"""
function description(parameter_type::Type{<:AbstractStrategyParameter})
throw(
Expand Down Expand Up @@ -297,7 +297,7 @@ CPU (parameter)
└─ description: CPU-based computation
\`\`\`

See also: [`describe(::Symbol, ::StrategyRegistry)`](@ref), [`id`](@ref), [`description`](@ref)
See also: [`CTBase.Strategies.describe(::Symbol, ::StrategyRegistry)`](@ref), [`CTBase.Strategies.id`](@ref), [`CTBase.Strategies.description`](@ref)
"""
function describe(parameter_type::Type{T}) where {T<:AbstractStrategyParameter}
describe(stdout, parameter_type)
Expand All @@ -308,7 +308,7 @@ $(TYPEDSIGNATURES)

Display parameter information to a specific IO stream.

See [`describe(::Type{<:AbstractStrategyParameter})`](@ref) for details.
See [`CTBase.Strategies.describe(::Type{<:AbstractStrategyParameter})`](@ref) for details.
"""
function describe(io::IO, parameter_type::Type{T}) where {T<:AbstractStrategyParameter}
fmt = Core.get_format_codes(io)
Expand Down
Loading