Strategy Parameters
This guide explains the Strategy Parameters system in CTBase. Parameters are singleton types that allow a strategy to specialize its metadata and default options depending on the execution context (e.g., CPU vs GPU).
Prerequisites
Read the Implementing a Strategy guide first. Parameters extend the strategy system with type-based specialization.
Concept
Strategy parameters are singleton types that enable:
Type-based dispatch so the same strategy struct can carry different defaults on CPU vs GPU
Compile-time specialization through Julia's type system
Registry-level routing so a method tuple like
(:mysolver, :cpu)resolves to the right concrete type
Parameters are not runtime values — they exist purely for dispatch and metadata specialization.
Built-in Parameters
CTBase ships two built-in parameters:
Strategies.id(Strategies.CPU):cpuStrategies.id(Strategies.GPU):gpuStrategies.description(Strategies.CPU)"CPU-based computation"describe shows full introspection for a parameter type:
Strategies.describe(Strategies.CPU)CPU (parameter)
├─ id: :cpu
├─ hierarchy: CPU → AbstractStrategyParameter
└─ description: CPU-based computationParameter Contract
Every parameter type must:
Subtype
AbstractStrategyParameterBe a singleton (no fields)
Implement
id(::Type{<:YourParameter})returning aSymbolImplement
description(::Type{<:YourParameter})returning aString
struct Distributed <: Strategies.AbstractStrategyParameter end
Strategies.id(::Type{Distributed}) = :distributed
Strategies.description(::Type{Distributed}) = "Distributed multi-node execution"
Strategies.describe(Distributed)Distributed (parameter)
├─ id: :distributed
├─ hierarchy: Distributed → AbstractStrategyParameter
└─ description: Distributed multi-node executionParameter Validation
Strategies.is_a_parameter(Strategies.CPU) # truetrueStrategies.is_a_parameter(Int) # falsefalseStrategies.parameter_id(Strategies.CPU) # :cpu:cpuvalidate_parameter_type checks the full contract and returns nothing if valid:
Strategies.validate_parameter_type(Strategies.CPU)Parameterized Strategy: Step-by-Step
A parameterized strategy is a generic struct over P <: AbstractStrategyParameter. The metadata method is specialized on Type{MyStrategy{P}}, letting Julia dispatch select the right defaults for each parameter.
Step 1 — Define the strategy family and struct
abstract type AbstractFakeOptimizer <: Strategies.AbstractStrategy end
struct FakeOptimizer{P <: Strategies.AbstractStrategyParameter} <: AbstractFakeOptimizer
options::Strategies.StrategyOptions
endStep 2 — Implement id
All parameter variants share the same ID:
Strategies.id(::Type{<:FakeOptimizer}) = :fake_optimizerStep 3 — Parameter-specific default helpers
__fake_default_precision(::Type{Strategies.CPU}) = :float64
__fake_default_precision(::Type{Strategies.GPU}) = :float32Step 4 — Implement metadata specialized on the parameterized type
The dispatch is on ::Type{FakeOptimizer{P}} — not on a second argument:
function Strategies.metadata(::Type{FakeOptimizer{P}}) where {P <: Strategies.AbstractStrategyParameter}
return Strategies.StrategyMetadata(
Options.OptionDefinition(
name = :precision,
type = Symbol,
default = __fake_default_precision(P),
description = "Numerical precision (:float64 for CPU, :float32 for GPU)",
computed = true,
),
Options.OptionDefinition(
name = :max_iter,
type = Int,
default = 1000,
description = "Maximum number of iterations",
),
)
endMark computed options with computed=true
An option whose default value depends on the parameter type P should be marked computed=true. It is evaluated at metadata construction time, not hard-coded. This flag is optional but strongly recommended: describe separates computed options by parameter (showing the actual default for each), making the parameter-specific behavior immediately visible to users.
Let's verify the metadata for each parameter — the :precision default differs:
Strategies.metadata(FakeOptimizer{Strategies.CPU})StrategyMetadata with 2 options:
│
├─ precision::Symbol (default: float64 [computed])
│ description: Numerical precision (:float64 for CPU, :float32 for GPU)
│
└─ max_iter::Int64 (default: 1000)
description: Maximum number of iterationsStrategies.metadata(FakeOptimizer{Strategies.GPU})StrategyMetadata with 2 options:
│
├─ precision::Symbol (default: float32 [computed])
│ description: Numerical precision (:float64 for CPU, :float32 for GPU)
│
└─ max_iter::Int64 (default: 1000)
description: Maximum number of iterationsStep 5 — Implement the constructor
The constructor is specialized on the parameterized type so build_strategy_options calls the right metadata:
function FakeOptimizer{P}(; mode::Symbol = :strict, kwargs...) where {P <: Strategies.AbstractStrategyParameter}
opts = Strategies.build_strategy_options(FakeOptimizer{P}; mode = mode, kwargs...)
return FakeOptimizer{P}(opts)
endStep 6 — Instantiate and inspect
FakeOptimizer{Strategies.CPU}()FakeOptimizer{CPU} (instance, id=:fake_optimizer)
├─ max_iter = 1000 [default]
└─ precision = float64 [computed]
Tip: use describe(FakeOptimizer) to see all available options.FakeOptimizer{Strategies.GPU}()FakeOptimizer{GPU} (instance, id=:fake_optimizer)
├─ max_iter = 1000 [default]
└─ precision = float32 [computed]
Tip: use describe(FakeOptimizer) to see all available options.FakeOptimizer{Strategies.CPU}(max_iter = 500)FakeOptimizer{CPU} (instance, id=:fake_optimizer)
├─ max_iter = 500 [user]
└─ precision = float64 [computed]
Tip: use describe(FakeOptimizer) to see all available options.Option access works exactly like non-parameterized strategies:
solver = FakeOptimizer{Strategies.GPU}(max_iter = 200)FakeOptimizer{GPU} (instance, id=:fake_optimizer)
├─ max_iter = 200 [user]
└─ precision = float32 [computed]
Tip: use describe(FakeOptimizer) to see all available options.julia> solver[:precision]
:float32
julia> solver[:max_iter]
200
julia> Strategies.source(Strategies.options(solver), :max_iter)
:userRegistering Parameterized Strategies
In create_registry, a parameterized strategy is declared as a (StrategyType, [Param1, Param2, ...]) tuple. Non-parameterized strategies are listed as plain types:
registry = Strategies.create_registry(
AbstractFakeOptimizer => (
(FakeOptimizer, [Strategies.CPU, Strategies.GPU]),
),
)StrategyRegistry with 1 family and 2 parameters:
├─ AbstractFakeOptimizer
│ └─ FakeOptimizer (id=:fake_optimizer) [:cpu, :gpu]
└─ parameters: :cpu → CPU, :gpu → GPUThe registry expands this into one concrete type per parameter. strategy_ids deduplicates:
Strategies.strategy_ids(AbstractFakeOptimizer, registry)(:fake_optimizer,)Strategies.type_from_id(:fake_optimizer, AbstractFakeOptimizer, registry; parameter=Strategies.CPU)Main.FakeOptimizer{CTBase.Strategies.CPU}Strategies.type_from_id(:fake_optimizer, AbstractFakeOptimizer, registry; parameter=Strategies.GPU)Main.FakeOptimizer{CTBase.Strategies.GPU}Building Strategies from the Registry
build_strategy accepts an optional parameter type as second argument:
Strategies.build_strategy(:fake_optimizer, Strategies.CPU, AbstractFakeOptimizer, registry;
max_iter = 300)FakeOptimizer{CPU} (instance, id=:fake_optimizer)
├─ max_iter = 300 [user]
└─ precision = float64 [computed]
Tip: use describe(FakeOptimizer) to see all available options.Strategies.build_strategy(:fake_optimizer, Strategies.GPU, AbstractFakeOptimizer, registry)FakeOptimizer{GPU} (instance, id=:fake_optimizer)
├─ max_iter = 1000 [default]
└─ precision = float32 [computed]
Tip: use describe(FakeOptimizer) to see all available options.Method Tuple Routing
When using a method tuple (e.g., (:fake_optimizer, :cpu)), extract_global_parameter_from_method reads the parameter token from the registry:
method = (:fake_optimizer, :cpu)
param = Strategies.extract_global_parameter_from_method(method, registry)CTBase.Strategies.CPUid = Strategies.extract_id_from_method(method, AbstractFakeOptimizer, registry)
Strategies.build_strategy(id, param, AbstractFakeOptimizer, registry)FakeOptimizer{CPU} (instance, id=:fake_optimizer)
├─ max_iter = 1000 [default]
└─ precision = float64 [computed]
Tip: use describe(FakeOptimizer) to see all available options.Mixed Registries
A registry can mix parameterized and non-parameterized strategies in the same family:
struct FallbackOptimizer <: AbstractFakeOptimizer
options::Strategies.StrategyOptions
end
Strategies.id(::Type{<:FallbackOptimizer}) = :fallback
function Strategies.metadata(::Type{<:FallbackOptimizer})
return Strategies.StrategyMetadata(
Options.OptionDefinition(
name = :max_iter, type = Int, default = 500,
description = "Maximum iterations",
),
)
end
function FallbackOptimizer(; mode::Symbol = :strict, kwargs...)
opts = Strategies.build_strategy_options(FallbackOptimizer; mode = mode, kwargs...)
return FallbackOptimizer(opts)
end
mixed_registry = Strategies.create_registry(
AbstractFakeOptimizer => (
FallbackOptimizer,
(FakeOptimizer, [Strategies.CPU, Strategies.GPU]),
),
)StrategyRegistry with 1 family and 2 parameters:
├─ AbstractFakeOptimizer
│ ├─ FallbackOptimizer (id=:fallback)
│ └─ FakeOptimizer (id=:fake_optimizer) [:cpu, :gpu]
└─ parameters: :cpu → CPU, :gpu → GPUStrategies.strategy_ids(AbstractFakeOptimizer, mixed_registry)(:fallback, :fake_optimizer)describe with a Registry
describe with a registry shows the full picture including available parameters:
Strategies.describe(:fake_optimizer, registry)FakeOptimizer (strategy)
├─ id: :fake_optimizer
├─ hierarchy: FakeOptimizer → AbstractFakeOptimizer → AbstractStrategy
├─ family: AbstractFakeOptimizer
├─ parameters: CPU, GPU
│
├─ computed options for CPU:
│ └─ precision::Symbol (default: float64 [computed])
│ description: Numerical precision (:float64 for CPU, :float32 for GPU)
│
├─ computed options for GPU:
│ └─ precision::Symbol (default: float32 [computed])
│ description: Numerical precision (:float64 for CPU, :float32 for GPU)
│
└─ common options (1 option):
└─ max_iter::Int64 (default: 1000)
description: Maximum number of iterationsSummary
| Aspect | Description |
|---|---|
| Purpose | Compile-time specialization of strategy defaults and metadata |
| Contract | Singleton struct + id + description implementations |
| Built-in | CPU, GPU |
| Metadata dispatch | metadata(::Type{MyStrategy{P}}) where {P} |
| Registry syntax | (MyStrategy, [CPU, GPU]) tuple inside the family tuple |
| Builder | build_strategy(id, Param, Family, registry; kwargs...) |
| Validation | validate_parameter_type, is_a_parameter, parameter_id |
See Also
Implementing a Strategy — Strategy contract and metadata
Options System —
OptionDefinition,StrategyOptionsStrategies.AbstractStrategyParameter— API reference