Skip to content

Make _process_verbose_param(::Bool) constprop-friendly to fix @inferred solve#3438

Merged
ChrisRackauckas merged 9 commits into
SciML:masterfrom
ChrisRackauckas-Claude:verbose_inference_constprop
Apr 15, 2026
Merged

Make _process_verbose_param(::Bool) constprop-friendly to fix @inferred solve#3438
ChrisRackauckas merged 9 commits into
SciML:masterfrom
ChrisRackauckas-Claude:verbose_inference_constprop

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor

Depends on #3365 — this branch is move_verbosity + one extra commit. Once #3365 merges, only the new commit (Make _process_verbose_param(::Bool) constprop-friendly) will remain on the diff against master. If preferred, this can be merged into #3365's branch first; mirror PR was at jClugstor#2 (closed in favor of this).

Summary

  • DEFAULT_VERBOSE and NONE_VERBOSE are different concrete parameterizations of DEVerbosity{...} (different Silent/WarnLevel/InfoLevel type parameters per toggle), so the ternary verbose ? DEFAULT_VERBOSE : NONE_VERBOSE returns a Union{...} whenever verbose::Bool isn't a compile-time constant.
  • That Union flows into the solver caches — you can see LinearVerbosity{Silent,Silent,...,WarnLevel,WarnLevel,...} baked into the LinearCache type in Move DEVerbosity and default verbosity to DiffEqBase #3365's CI failure logs — and it poisons return-type inference all the way to ODESolution. Tests like @inferred solve(prob_mm, Rodas5P(), ...) in lib/OrdinaryDiffEqRosenbrock/test/dae_rosenbrock_ad_tests.jl fail with does not match inferred return type Any.
  • This PR annotates the Bool method with Base.@constprop :aggressive so the compiler threads the literal default verbose = true through the kwargs chain to a single concrete DEVerbosity parameterization.
  • Adds Val{true}/Val{false} overloads as an explicit type-stable entry point for callers that don't want to rely on constprop reaching them.

The runtime-Bool-with-no-constprop case still returns a Union; that's an inherent property of the @verbosity_specifier design (verbosity levels are encoded as type parameters). The proper fix for that case is to keep verbosity out of the cache type parameters, but this is the smallest change that unblocks #3365's @inferred tests.

Test plan

  • New lib/DiffEqBase/test/verbose_inference.jl exercises both the Val-dispatch path (unconditionally inferable) and the literal-Bool constprop path (verifies Base.return_types resolves to a single concrete type).
  • Runic clean.
  • Re-run the Rosenbrock / DiffEqDevTools / Integrators test groups that were failing on Move DEVerbosity and default verbosity to DiffEqBase #3365 with @inferred solve(...) errors.

@ChrisRackauckas-Claude ChrisRackauckas-Claude force-pushed the verbose_inference_constprop branch from e43ab1d to bf421cf Compare April 15, 2026 07:05
@ChrisRackauckas-Claude ChrisRackauckas-Claude force-pushed the verbose_inference_constprop branch 3 times, most recently from 342b718 to 32f75c5 Compare April 15, 2026 15:21
@jClugstor
Copy link
Copy Markdown
Member

Testing the MWE in #3351 with this change, I still get that the inferred return type is Any.

Putting a function barrier like :

_verbose = verbose isa Bool ? (verbose ? Standard() : None()) : verbose
    return _ode_init(prob, alg, timeseries_init, ts_init, ks_init; verbose = _verbose, kwargs...)

in OrdinaryDiffEqCore.__init doesn't let @inferred pass, but it makes the inference better, it infers as a Union of the two possible verbosity objects from the Bool path.

I haven't found a way to make the Bool path fully infer yet.

@ChrisRackauckas-Claude ChrisRackauckas-Claude force-pushed the verbose_inference_constprop branch from 32f75c5 to 1fc8763 Compare April 15, 2026 16:12
`DEFAULT_VERBOSE` and `NONE_VERBOSE` are different concrete
parameterizations of `DEVerbosity{...}`, so the ternary
`verbose ? DEFAULT_VERBOSE : NONE_VERBOSE` returns `Union{...}` whenever
`verbose::Bool` isn't a compile-time constant. That `Union` propagates
into solver caches (e.g. baked into `LinearCache` via `LinearVerbosity`)
and breaks return-type inference of `solve`, causing `@inferred
solve(...)` failures across the Rosenbrock/Default/BDF/SDIRK test suites.

Simply annotating `_process_verbose_param(::Bool)` with `Base.@constprop
:aggressive` isn't enough, because the `verbose = true` default at the
`DiffEqBase.solve` entrypoint has to thread through several layers of
kwargs splatting (`solve` → `solve_up` → `__solve` → `__init` →
`_ode_init`) before reaching it, and constprop gives up at any
unannotated layer. Annotate each layer with `Base.@constprop :aggressive`
so the Bool literal propagates end-to-end. Also add `Val{true}`/
`Val{false}` overloads for callers that want to force type-stability
without relying on constprop at all.

Also add `[sources.DiffEqBase]` to `OrdinaryDiffEqMultirate/Project.toml`
so Pkg pins the local `DiffEqBase 6.217` alongside the other sublibs
during its tests — otherwise the resolver only sees the registered
`6.216` which violates `OrdinaryDiffEqCore 3.32`'s new `DiffEqBase ≥
6.217` compat and the test environment fails to instantiate.

Verified locally: `@inferred solve(prob_mm, Rodas5P(), reltol=1.0e-8,
abstol=1.0e-8)` passes on this branch (failed without the annotations).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas-Claude ChrisRackauckas-Claude force-pushed the verbose_inference_constprop branch from 1fc8763 to 3e0d99e Compare April 15, 2026 17:01
@ChrisRackauckas
Copy link
Copy Markdown
Member

This at least makes constant prop work which I think is good enough for the pre-v7 if verbosity is fully supported downstream, because then the defaults and the suggested ways to set verbose are all covered.

@ChrisRackauckas ChrisRackauckas merged commit c2de709 into SciML:master Apr 15, 2026
237 of 255 checks passed
singhharsh1708 pushed a commit to singhharsh1708/OrdinaryDiffEq.jl that referenced this pull request Apr 16, 2026
… solve (SciML#3438)

* raise DEVerbosity to DiffEqBase

* add toggle for Sundials

* bump versions

* bump versions

* use DiffEqBase for DEVerbosity access

* bump lower bound for DiffEqBase in DelayDiffEq

* change default verbosity back to true

* remove unnecessary function extensions

* Fix @inferred solve via constprop on the verbose kwarg chain

`DEFAULT_VERBOSE` and `NONE_VERBOSE` are different concrete
parameterizations of `DEVerbosity{...}`, so the ternary
`verbose ? DEFAULT_VERBOSE : NONE_VERBOSE` returns `Union{...}` whenever
`verbose::Bool` isn't a compile-time constant. That `Union` propagates
into solver caches (e.g. baked into `LinearCache` via `LinearVerbosity`)
and breaks return-type inference of `solve`, causing `@inferred
solve(...)` failures across the Rosenbrock/Default/BDF/SDIRK test suites.

Simply annotating `_process_verbose_param(::Bool)` with `Base.@constprop
:aggressive` isn't enough, because the `verbose = true` default at the
`DiffEqBase.solve` entrypoint has to thread through several layers of
kwargs splatting (`solve` → `solve_up` → `__solve` → `__init` →
`_ode_init`) before reaching it, and constprop gives up at any
unannotated layer. Annotate each layer with `Base.@constprop :aggressive`
so the Bool literal propagates end-to-end. Also add `Val{true}`/
`Val{false}` overloads for callers that want to force type-stability
without relying on constprop at all.

Also add `[sources.DiffEqBase]` to `OrdinaryDiffEqMultirate/Project.toml`
so Pkg pins the local `DiffEqBase 6.217` alongside the other sublibs
during its tests — otherwise the resolver only sees the registered
`6.216` which violates `OrdinaryDiffEqCore 3.32`'s new `DiffEqBase ≥
6.217` compat and the test environment fails to instantiate.

Verified locally: `@inferred solve(prob_mm, Rodas5P(), reltol=1.0e-8,
abstol=1.0e-8)` passes on this branch (failed without the annotations).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>

---------

Co-authored-by: jClugstor <jadonclugston@gmail.com>
Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants