From 86f1fdcdb5417b1ef9b99ae431f47418c4a89181 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Fri, 19 Jun 2026 11:18:18 -0400 Subject: [PATCH] Fix Downgrade (Core) and Aqua/JET QA failures Downgrade (Core): raise SteadyStateDiffEq compat floor to 2.5. The old "1, 2" floor let the downgrade resolver pick a SteadyStateDiffEq whose solve.jl references SciMLBase.AbstractNonlinearTerminationMode against a SciMLBase too old to define it, failing precompilation with `UndefVarError: AbstractNonlinearTerminationMode not defined`. 2.5 forces a SciMLBase floor (resolved 2.99) that defines the symbol. Also add the missing SparseArrays compat entry (Aqua deps_compat) and raise the DiffEqBase floor to 6.140 for downgrade resolvability. QA / Aqua method-ambiguity + unbound-type-parameter: rewrite the `pairedindices(::DefaultIndexHandler{N}, ::NTuple{N,T}, ...)` methods using `Tuple{T, Vararg{T}}` (so T is always bound; NTuple{0,T} === Tuple{} left T free) and add an explicit zero-dimensional method, removing both the Number/AbstractVector ambiguity at NTuple{0} and the unbound M/T params in the dimension-mismatch fallback. QA / JET: qualify the NullParameters default argument as `DiffEqBase.NullParameters()` in build_rhs.jl / build_rhs_ss.jl (NullParameters is not exported from DiffEqBase, so the bare reference was an undefined-binding error). This also clears the cascade of JET `iterate(::Nothing)` reports that stemmed from the ambiguous/unbound pairedindices methods. Verified locally: - Downgrade Core on Julia 1.10 (lts) with =floor-rewritten compat: telegraph 17/17, feedbackloop 12/12, birthdeath2D 18/18, tests passed. - Core on Julia 1.12 ("1"): 17/17, 12/12, 18/18, tests passed. - Aqua ambiguities/unbound/deps_compat: pass. JET.test_package: pass. Remaining QA undefined-exports failure (CartesianGridReJ, infimum, iscall, params, supremum) is inherited via `@reexport using Catalyst` and is an upstream Catalyst export bug (Catalyst exports CartesianGridReJ but JumpProcesses defines CartesianGridRej; the others come from MTK/Symbolics re-exports). Not fixable from within FiniteStateProjection without disabling the check; needs an upstream Catalyst release. Co-Authored-By: Chris Rackauckas Co-Authored-By: Claude Opus 4.8 (1M context) --- Project.toml | 5 +++-- src/build_rhs.jl | 2 +- src/build_rhs_ss.jl | 2 +- src/indexhandlers.jl | 22 +++++++++++++++++----- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index 80f21f8..7c29577 100644 --- a/Project.toml +++ b/Project.toml @@ -13,7 +13,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [compat] Catalyst = "15" -DiffEqBase = "6, 7" +DiffEqBase = "6.140, 7" Distributions = "0.25" LinearAlgebra = "1" MacroTools = "^0.5.5" @@ -21,8 +21,9 @@ OrdinaryDiffEq = "6, 7" Reexport = "1.2.2" RuntimeGeneratedFunctions = "0.5" SafeTestsets = "0.1, 1" +SparseArrays = "1" SciMLTesting = "1" -SteadyStateDiffEq = "1, 2" +SteadyStateDiffEq = "2.5" Sundials = "4, 5, 6" Test = "1" julia = "1.10" diff --git a/src/build_rhs.jl b/src/build_rhs.jl index 2332580..2b8254c 100644 --- a/src/build_rhs.jl +++ b/src/build_rhs.jl @@ -140,6 +140,6 @@ Return an `ODEProblem` for use in `DifferentialEquations`. This function implici calls `convert(ODEFunction, sys)`. It is usually more efficient to create an `ODEFunction` first and then use that to create `ODEProblem`s. """ -function DiffEqBase.ODEProblem(sys::FSPSystem, u0, tint, pmap = NullParameters()) +function DiffEqBase.ODEProblem(sys::FSPSystem, u0, tint, pmap = DiffEqBase.NullParameters()) return ODEProblem(convert(ODEFunction, sys), u0, tint, pmap_to_p(sys, pmap)) end diff --git a/src/build_rhs_ss.jl b/src/build_rhs_ss.jl index ba2c2dd..c44cf59 100644 --- a/src/build_rhs_ss.jl +++ b/src/build_rhs_ss.jl @@ -75,6 +75,6 @@ end Return a `SteadyStateProblem` for use in `DifferentialEquations. """ -function DiffEqBase.SteadyStateProblem(sys::FSPSystem, u0, pmap = NullParameters()) +function DiffEqBase.SteadyStateProblem(sys::FSPSystem, u0, pmap = DiffEqBase.NullParameters()) return SteadyStateProblem(convert(ODEFunction, sys, SteadyState()), u0, pmap_to_p(sys, pmap)) end diff --git a/src/indexhandlers.jl b/src/indexhandlers.jl index d5af604..934985b 100644 --- a/src/indexhandlers.jl +++ b/src/indexhandlers.jl @@ -86,16 +86,28 @@ function pairedindices( return pairedindices(ih, axes(arr), shift) end +# `dims` is written as `Tuple{T, Vararg{T}}` rather than `NTuple{N, T}` so that the +# element type `T` is always bound: `NTuple{0, T} === Tuple{}` leaves `T` free, which +# trips Aqua's unbound-type-parameter check. The zero-dimensional case is handled by the +# explicit method below. function pairedindices( - ih::DefaultIndexHandler{N}, dims::NTuple{N, T}, + ih::DefaultIndexHandler{N}, dims::Tuple{T, Vararg{T}}, shift::CartesianIndex{N} ) where {N, T <: Number} return pairedindices(ih, Base.OneTo.(dims), shift) end +# Handles the degenerate zero-dimensional case (no species), where neither `T`-constrained +# method below matches because `Tuple{}` has no element type. +function pairedindices( + ::DefaultIndexHandler{0}, ::Tuple{}, ::CartesianIndex{0} + ) + return zip(CartesianIndices(()), CartesianIndices(())) +end + # Important: the species in `shift` are ordered according to `Catalyst.species`! function pairedindices( - ih::DefaultIndexHandler{N}, dims::NTuple{N, T}, + ih::DefaultIndexHandler{N}, dims::Tuple{T, Vararg{T}}, shift::CartesianIndex{N} ) where {N, T <: AbstractVector} ranges = tuple( @@ -114,9 +126,9 @@ function pairedindices( end function pairedindices( - ::DefaultIndexHandler, dims::NTuple{N, T}, - shift::CartesianIndex{M} - ) where {N, M, T <: AbstractVector} + ::DefaultIndexHandler, dims::Tuple, + shift::CartesianIndex + ) return @error "Dimension of state space ($(length(dims))) does not match number of species ($(length(shift)))" end