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
1 change: 1 addition & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[default]
locale = "en"
extend-ignore-re = [
"OT",
]

[files]
Expand Down
160 changes: 146 additions & 14 deletions ext/CTFlowsSciMLExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ function Strategies.metadata(::Type{SciML})
),
Strategies.OptionDefinition(;
name = :save_everystep,
type = Bool,
default = Options.NotProvided,
description = "Save the solution at every solver step.",
type = Union{Bool, Symbol},
default = :auto,
description = "Save the solution at every solver step. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).",
),
Strategies.OptionDefinition(;
name = :saveat,
Expand All @@ -173,9 +173,9 @@ function Strategies.metadata(::Type{SciML})
),
Strategies.OptionDefinition(;
name = :dense,
type = Bool,
default = true,
description = "Whether to save extra pieces for dense (continuous) output.",
type = Union{Bool, Symbol},
default = :auto,
description = "Dense output. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).",
),
Strategies.OptionDefinition(;
name = :save_idxs,
Expand Down Expand Up @@ -253,9 +253,9 @@ function Strategies.metadata(::Type{SciML})
),
Strategies.OptionDefinition(;
name = :save_start,
type = Bool,
default = Options.NotProvided,
description = "Whether to save the initial condition.",
type = Union{Bool, Symbol},
default = :auto,
description = "Save initial condition in solution. Set `true`/`false` to force, or `:auto` to infer from call pattern (false for `flow(t0, x0[, p0], tf)`, true for `flow((t0, tf), x0[, p0])`).",
),
Strategies.OptionDefinition(;
name = :save_end,
Expand All @@ -280,16 +280,148 @@ end
# build_sciml_integrator — actual implementation
# =============================================================================

# =============================================================================
# Config-dependent option resolution
# =============================================================================

"""
_AUTO_OPTION_KEYS

Tuple of option keys that support automatic resolution based on configuration type.

These options use the `:auto` sentinel value in their metadata and are resolved
dynamically during integrator construction:
- For `PointConfig`: set to `false` (only final state needed)
- For `TrajectoryConfig`: set to `true` (full trajectory storage needed)

Users can override automatic resolution by providing explicit `true`/`false` values
when constructing the integrator.
"""
const _AUTO_OPTION_KEYS = (:dense, :save_everystep, :save_start)

"""
$(TYPEDSIGNATURES)

Build a `SciML` integrator with validated options.
Build a `SciML` integrator with validated options and pre-computed config-specific options.

This function constructs a SciML integrator with automatic resolution of config-dependent
options. Options in `_AUTO_OPTION_KEYS` support the `:auto` sentinel value, which is
resolved based on the configuration type used during integration:
- For `PointConfig` (e.g., `flow(t0, x0, tf)`): options set to `false` to minimize memory
since only the final state is needed
- For `TrajectoryConfig` (e.g., `flow((t0, tf), x0)`): options set to `true` to enable
full trajectory storage and interpolation

The resolved options are pre-computed and cached in the integrator for performance,
avoiding repeated resolution during integration.

# Arguments
- `::Type{CTFlows.Integrators.SciMLTag}`: The SciML integrator tag type.
- `mode::Symbol`: Validation mode for strategy options (`:strict` or `:permissive`).
- `kwargs...`: User-provided option values. Explicit `true`/`false` values override
automatic `:auto` resolution.

# Returns
- `CTFlows.Integrators.SciML`: Parametric SciML integrator with cached `options_point`
and `options_trajectory` fields.

# Notes
- The `:auto` sentinel is defined in option metadata as `Union{Bool, Symbol}` with
default `:auto`.
- Pre-computation happens at construction time, not during integration.
- Config-specific options are returned by `Integrators.build_options` based on dispatch
on the configuration type.

See also: [`CTFlows.Integrators.build_options`](@ref), [`CTFlows.Integrators.SciML`](@ref),
[`CTFlows.Common.PointConfig`](@ref), [`CTFlows.Common.TrajectoryConfig`](@ref).
"""
function CTFlows.Integrators.build_sciml_integrator(
::Type{CTFlows.Integrators.SciMLTag}; mode::Symbol = :strict, kwargs...,
)
opts = Strategies.build_strategy_options(SciML; mode = mode, kwargs...)
return CTFlows.Integrators.SciML(opts)
raw = Strategies.options_dict(opts)

# Pre-compute options for PointConfig
options_point = copy(raw)
for key in _AUTO_OPTION_KEYS
get(options_point, key, :auto) === :auto && (options_point[key] = false)
end

# Pre-compute options for TrajectoryConfig
options_trajectory = copy(raw)
for key in _AUTO_OPTION_KEYS
get(options_trajectory, key, :auto) === :auto && (options_trajectory[key] = true)
end

return CTFlows.Integrators.SciML{typeof(opts), typeof(options_point), typeof(options_trajectory)}(
opts, options_point, options_trajectory
)
end

# =============================================================================
# build_options — config-dependent option resolution
# =============================================================================

"""
$(TYPEDSIGNATURES)

Return pre-computed solver options for PointConfig.

For a PointConfig, options like `dense`, `save_everystep`, and `save_start`
are set to `false` to minimize memory since only the final state is needed.

# Arguments
- `integ::SciML`: The SciML integrator with pre-computed option caches.
- `config::Common.PointConfig`: The point configuration.

# Returns
- `Dict{Symbol,Any}`: Pre-computed options optimized for PointConfig.

See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref).
"""
function Integrators.build_options(integ::SciML, config::Common.PointConfig)
return integ.options_point
end

"""
$(TYPEDSIGNATURES)

Return pre-computed solver options for TrajectoryConfig.

For a TrajectoryConfig, options like `dense`, `save_everystep`, and `save_start`
are set to `true` to enable full trajectory storage and interpolation.

# Arguments
- `integ::SciML`: The SciML integrator with pre-computed option caches.
- `config::Common.TrajectoryConfig`: The trajectory configuration.

# Returns
- `Dict{Symbol,Any}`: Pre-computed options optimized for TrajectoryConfig.

See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref).
"""
function Integrators.build_options(integ::SciML, config::Common.TrajectoryConfig)
return integ.options_trajectory
end

"""
$(TYPEDSIGNATURES)

Return pre-computed solver options for fallback case (Nothing).

Defaults to TrajectoryConfig options when no configuration is provided.

# Arguments
- `integ::SciML`: The SciML integrator with pre-computed option caches.
- `config::Nothing`: No configuration provided (fallback).

# Returns
- `Dict{Symbol,Any}`: Pre-computed options for TrajectoryConfig (fallback).

See also: [`Integrators.build_options`](@ref), [`Integrators.SciML`](@ref).
"""
function Integrators.build_options(integ::SciML, config::Nothing)
return integ.options_trajectory # fallback vers Trajectory par défaut
end

# =============================================================================
Expand Down Expand Up @@ -356,12 +488,13 @@ end
"""
$(TYPEDSIGNATURES)

Solve an `ODEProblem` using the `SciML`'s configured options.
Solve an `ODEProblem` using resolved options.
Returns a `SciMLIntegrationResult` wrapping the raw `ODESolution`.

# Arguments
- `integ::SciML`: The SciML integrator strategy.
- `prob::SciMLBase.AbstractODEProblem`: The ODE problem to solve.
- `options::Dict{Symbol,Any}`: Resolved solver options (typically from `build_options`).
- `unsafe=Common.__unsafe()`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure.

# Returns
Expand All @@ -370,8 +503,7 @@ Returns a `SciMLIntegrationResult` wrapping the raw `ODESolution`.
# Throws
- `CTBase.Exceptions.SolverFailure`: If the ODE solver returns an unsuccessful retcode and `unsafe=false`.
"""
function Integrators.solve_problem(integ::SciML, prob::SciMLBase.AbstractODEProblem; unsafe=Common.__unsafe())
options = Strategies.options_dict(integ)
function Integrators.solve_problem(integ::SciML, prob::SciMLBase.AbstractODEProblem, options::Dict{Symbol,Any}; unsafe=Common.__unsafe())
ode_sol = SciMLBase.solve(prob; options...)
if !unsafe && !SciMLBase.successful_retcode(ode_sol.retcode)
throw(Exceptions.SolverFailure(
Expand Down
5 changes: 4 additions & 1 deletion src/Flows/calling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ function call(flow::Flows.AbstractFlow{TD, VD}, config::Common.AbstractConfig; v
# build ode problem
prob = Integrators.build_problem(int, sys, config; variable=variable)

# build config-specific options
opts = Integrators.build_options(int, config)

# integrate ode problem
result = Integrators.solve_problem(int, prob; unsafe=unsafe)
result = Integrators.solve_problem(int, prob, opts; unsafe=unsafe)

# build flow solution
flow_sol = Solutions.build_solution(result, sys, config)
Expand Down
42 changes: 35 additions & 7 deletions src/Integrators/abstract_integrator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ Methods defined on **instances** that provide the actual configuration:

# Concrete Implementation

All subtypes must implement two named functions:
All subtypes must implement three named functions:

- `build_problem(integrator::AbstractIntegrator, system::CTFlows.Systems.AbstractSystem, config::CTFlows.Common.AbstractConfig; variable)`: Build the ODE problem representation from a system and configuration.
- `solve_problem(integrator::AbstractIntegrator, prob)`: Solve the given ODE problem (tspan is embedded in `prob`).
- `build_options(integrator::AbstractIntegrator, config::Union{CTFlows.Common.AbstractConfig, Nothing})`: Build solver options dict for the given configuration.
- `solve_problem(integrator::AbstractIntegrator, prob, options::Dict{Symbol,Any})`: Solve the given ODE problem with resolved options (tspan is embedded in `prob`).

# Throws
- `CTBase.Exceptions.NotImplemented`: If the methods are not implemented by the concrete type.
Expand Down Expand Up @@ -63,11 +64,12 @@ end
"""
$(TYPEDSIGNATURES)

Solve the given ODE problem.
Solve the given ODE problem with resolved options.

# Arguments
- `integrator::AbstractIntegrator`: The integrator strategy.
- `prob`: The ODE problem to solve (type varies by concrete integrator; tspan is embedded).
- `options::Dict{Symbol,Any}`: Resolved solver options (typically from `build_options`).
- `unsafe=Common.__unsafe()`: If `true`, bypass ODE solver retcode checking; if `false`, throw `SolverFailure` on integration failure.

# Returns
Expand All @@ -76,13 +78,39 @@ Solve the given ODE problem.
# Throws
- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type.

See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.build_problem`](@ref).
See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.build_options`](@ref).
"""
function solve_problem(integrator::AbstractIntegrator, prob; unsafe=Common.__unsafe())
function solve_problem(integrator::AbstractIntegrator, prob, options::Dict{Symbol,Any}; unsafe=Common.__unsafe())
throw(Exceptions.NotImplemented(
"AbstractIntegrator solve_problem not implemented";
required_method = "solve_problem(integrator::$(typeof(integrator)), prob; unsafe=false)",
suggestion = "Implement solve_problem(i::YourIntegrator, prob; unsafe=false) returning an AbstractIntegrationResult.",
required_method = "solve_problem(integrator::$(typeof(integrator)), prob, options::Dict{Symbol,Any}; unsafe=false)",
suggestion = "Implement solve_problem(i::YourIntegrator, prob, options::Dict; unsafe=false) returning an AbstractIntegrationResult.",
context = "AbstractIntegrator solve_problem - required method implementation",
))
end

"""
$(TYPEDSIGNATURES)

Build solver options dict for the given configuration.

# Arguments
- `integrator::AbstractIntegrator`: The integrator strategy.
- `config::Union{CTFlows.Common.AbstractConfig, Nothing}`: The integration configuration (or `Nothing` for fallback).

# Returns
- `Dict{Symbol,Any}`: Resolved solver options for the given configuration.

# Throws
- `CTBase.Exceptions.NotImplemented`: If not implemented by the concrete type.

See also: [`CTFlows.Integrators.AbstractIntegrator`](@ref), [`CTFlows.Integrators.build_problem`](@ref), [`CTFlows.Integrators.solve_problem`](@ref).
"""
function build_options(integrator::AbstractIntegrator, config::Union{Common.AbstractConfig, Nothing})
throw(Exceptions.NotImplemented(
"AbstractIntegrator build_options not implemented";
required_method = "build_options(integrator::$(typeof(integrator)), config::Union{Common.AbstractConfig, Nothing})",
suggestion = "Implement build_options(i::YourIntegrator, config) returning a Dict{Symbol,Any} of resolved solver options.",
context = "AbstractIntegrator build_options - required method implementation",
))
end
12 changes: 8 additions & 4 deletions src/Integrators/sciml.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,14 @@ To activate the extension, load any of:
- `using DifferentialEquations`

# Fields
- `options::CTSolvers.Strategies.StrategyOptions`: validated option bundle.
"""
struct SciML <: AbstractSciMLIntegrator
options::CTSolvers.Strategies.StrategyOptions
- `options::CTSolvers.Strategies.StrategyOptions`: Validated option bundle.
- `options_point::Dict{Symbol, Any}`: Pre-computed options for PointConfig.
- `options_trajectory::Dict{Symbol, Any}`: Pre-computed options for TrajectoryConfig.
"""
struct SciML{O<:CTSolvers.Strategies.StrategyOptions, OP<:Dict{Symbol, Any}, OT<:Dict{Symbol, Any}} <: AbstractSciMLIntegrator
options::O
options_point::OP
options_trajectory::OT
end

# ============================================================================
Expand Down
Loading
Loading