From 675fee6c28589b1e899fd5485797fc4d8afacb23 Mon Sep 17 00:00:00 2001 From: termi-official Date: Wed, 28 Oct 2026 15:09:20 +0100 Subject: [PATCH 1/2] Type the step bounds by the time type and expose the leaf verbosity A `Float32` time span could not be integrated since 0.4.2: `dtmax` defaults to the span length in the time type while `dtmin` defaulted to `eps(Float64)`, leaving `IntegratorOptions` with no common type parameter. Both bounds are now converted to the node's time type at construction, with `eps(T)` as the floating-point default, and the struct carries no defaults of its own. A leaf integrator built from `config.values.verbose` holds the splitting node's `OperatorSplittingVerbosity`, whose toggles are the splitting diagnostics, not the leaf's. `inner_verbosity(verbose)` is what `init` hands OrdinaryDiffEq leaves, now reachable for custom leaves. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01RWfoaMxJzyy72Q7MzHEfYX --- Project.toml | 2 +- src/OrdinaryDiffEqOperatorSplitting.jl | 10 +++++++++ src/config_tree.jl | 16 ++++++++++----- src/integrator.jl | 4 ++-- test/precision.jl | 28 ++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 test/precision.jl diff --git a/Project.toml b/Project.toml index c814f6a..429a984 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "OrdinaryDiffEqOperatorSplitting" uuid = "760fc936-9fa0-4281-9c1a-468957eedc89" -version = "0.4.3" +version = "0.4.4" authors = ["Dennis Ogiermann and contributors"] [deps] diff --git a/src/OrdinaryDiffEqOperatorSplitting.jl b/src/OrdinaryDiffEqOperatorSplitting.jl index 773e179..878d400 100644 --- a/src/OrdinaryDiffEqOperatorSplitting.jl +++ b/src/OrdinaryDiffEqOperatorSplitting.jl @@ -146,6 +146,16 @@ end _inner_verbose(verbose) = verbose _process_verbose(verbose) = verbose +""" + inner_verbosity(verbose) + +The verbosity a leaf integrator under a splitting node runs with: the `inner_verbosity` +of an [`OperatorSplittingVerbosity`](@ref), or `verbose` itself where it already is a +leaf setting. A leaf integrator built from `config.values.verbose` passes it through +here; the splitting toggles live on the node, not on its leaves. +""" +inner_verbosity(verbose) = _inner_verbose(verbose) + # `verbose` reaches us either as a Bool or, through DiffEqBase v7's `init`, as a # verbosity specifier whose first type parameter is the on/off flag. Neither can be # used in a boolean context directly. diff --git a/src/config_tree.jl b/src/config_tree.jl index 2d53472..b4cd8dc 100644 --- a/src/config_tree.jl +++ b/src/config_tree.jl @@ -429,9 +429,15 @@ const SPLIT_OPTION_KEYS = ( function split_integrator_options(values::NamedTuple) inner = inner_values(values) known = filter(in(SPLIT_OPTION_KEYS), keys(inner)) - return IntegratorOptions(; - verbose = values.verbose, - adaptive = values.adaptive, - NamedTuple{known}(inner)... - ) + opts = NamedTuple{known}(inner) + # `dtmin` and `dtmax` share the node's time type: a `Float32` time span with the + # default `dtmin` would otherwise leave the two with no common type parameter. + T = typeof(values.dt) + opts = merge(opts, (; + dtmin = T(get(opts, :dtmin, default_dtmin(T))), + dtmax = T(get(opts, :dtmax, typemax(T))), + )) + return IntegratorOptions(; verbose = values.verbose, adaptive = values.adaptive, opts...) end +default_dtmin(::Type{T}) where {T <: AbstractFloat} = eps(T) +default_dtmin(::Type{T}) where {T} = zero(T) diff --git a/src/integrator.jl b/src/integrator.jl index 7663e84..d4b20e4 100644 --- a/src/integrator.jl +++ b/src/integrator.jl @@ -8,8 +8,8 @@ IntegratorStats() = IntegratorStats(0, 0) Base.@kwdef mutable struct IntegratorOptions{tType, fType, vbType, F3, atolType, rtolType, normType} adaptive::Bool - dtmin::tType = eps(Float64) - dtmax::tType = Inf + dtmin::tType + dtmax::tType failfactor::fType = 4.0 verbose::vbType = DEFAULT_VERBOSITY isoutofdomain::F3 = DiffEqBase.ODE_DEFAULT_ISOUTOFDOMAIN diff --git a/test/precision.jl b/test/precision.jl new file mode 100644 index 0000000..830708b --- /dev/null +++ b/test/precision.jl @@ -0,0 +1,28 @@ +using OrdinaryDiffEqLowOrderRK +using OrdinaryDiffEqOperatorSplitting +using Test + +@testset "Float32 time span" begin + f(du, u, p, t) = @. du = -u + split_f = GenericSplitFunction((ODEFunction(f),), (1:1,)) + prob = OperatorSplittingProblem(split_f, Float32[1.0], (0.0f0, 1.0f0)) + alg = LieTrotterGodunov((Euler(),)) + + integrator = init(prob, alg; dt = 0.1f0) + @test integrator.opts.dtmin === eps(Float32) + @test integrator.opts.dtmax === 1.0f0 + step!(integrator) + @test integrator.t === 0.1f0 + @test integrator.u ≈ Float32[0.9] + + # An explicitly given bound is converted to the time type. + integrator = init(prob, alg; dt = 0.1f0, dtmin = 1.0e-3) + @test integrator.opts.dtmin === 1.0f-3 +end + +@testset "inner_verbosity" begin + @test OrdinaryDiffEqOperatorSplitting.inner_verbosity(true) isa OrdinaryDiffEqOperatorSplitting.DiffEqBase.DEVerbosity + v = OrdinaryDiffEqOperatorSplitting.OperatorSplittingVerbosity(OrdinaryDiffEqOperatorSplitting.SciMLLogging.Standard()) + @test OrdinaryDiffEqOperatorSplitting.inner_verbosity(v) === v.inner_verbosity + @test OrdinaryDiffEqOperatorSplitting.inner_verbosity(v.inner_verbosity) === v.inner_verbosity +end From b4dbda9e42d40e0ce119fd6a738a75add758bb51 Mon Sep 17 00:00:00 2001 From: termi-official Date: Wed, 28 Oct 2026 15:41:32 +0100 Subject: [PATCH 2/2] Runic --- src/config_tree.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/config_tree.jl b/src/config_tree.jl index b4cd8dc..26513c3 100644 --- a/src/config_tree.jl +++ b/src/config_tree.jl @@ -433,10 +433,12 @@ function split_integrator_options(values::NamedTuple) # `dtmin` and `dtmax` share the node's time type: a `Float32` time span with the # default `dtmin` would otherwise leave the two with no common type parameter. T = typeof(values.dt) - opts = merge(opts, (; - dtmin = T(get(opts, :dtmin, default_dtmin(T))), - dtmax = T(get(opts, :dtmax, typemax(T))), - )) + opts = merge( + opts, (; + dtmin = T(get(opts, :dtmin, default_dtmin(T))), + dtmax = T(get(opts, :dtmax, typemax(T))), + ) + ) return IntegratorOptions(; verbose = values.verbose, adaptive = values.adaptive, opts...) end default_dtmin(::Type{T}) where {T <: AbstractFloat} = eps(T)