Skip to content

Commit 562c552

Browse files
Use flat Vector u0 for MIRK/FIRK/MIRKN nlprob to preserve inference
@wsmoses noted that NonlinearSolve's polyalg is type-stable (PR #870). Verified: a `NonlinearProblem` with a plain `Vector{Float64}` u0 does infer concretely under `solve(prob)`. The widening I was seeing on LTS / 1.11 CI is specifically triggered by the **type** of `nlprob.u0`: under RecursiveArrayTools v4, `vec(::VectorOfArray)` returns a `Base.ReshapedArray{T, 1, VectorOfArray{...}, …}` (in v3 it returned a plain `Vector`). NonlinearSolve's polyalg cannot infer `T, N, uType, R` of the resulting `NonlinearSolution` when `u0` is that ReshapedArray, so the inferred return type widens to `NonlinearSolution{_A,_B,_C,_D, …}` and `@inferred solve(::BVProblem, ::MIRKN, …)` rejects it. Minimal repro (no BVDE involved): using NonlinearSolve, RecursiveArrayTools f!(du, u, p) = (du .= u .^ 2 .- p) voa = VectorOfArray([[1.0], [1.0], [1.0]]) prob_resh = NonlinearProblem(NonlinearFunction(f!), vec(voa), [2.0,2.0,2.0]) Base.return_types(solve, Tuple{typeof(prob_resh)}) # → NonlinearSolution{_A, _B, _C, _D, NonlinearProblem{ReshapedArray{…}, …}, …} prob_flat = NonlinearProblem(NonlinearFunction(f!), reduce(vcat, voa.u), [2.0,2.0,2.0]) Base.return_types(solve, Tuple{typeof(prob_flat)}) # → NonlinearSolution{Float64, 1, Vector{Float64}, Vector{Float64}, …} (concrete) So this is a downstream RAT-v4 inference cliff, not a polyalg bug. Fix: in MIRK / FIRK / MIRKN's `__perform_*_iteration`, flatten the VectorOfArray initial guess to a plain `Vector` before constructing `nlprob`: nlprob = __construct_problem(cache, reduce(vcat, cache.y₀.u), copy(cache.y₀)) `recursive_unflatten!(cache.y₀, sol_nlprob.u)` afterwards still copies the result back, so the loss of view-aliasing on the (always-copied- internally-by-the-solver) `u0` is benign. Also revert the previous workaround that commented out the MIRK `@inferred solve` blocks in `test/misc/type_stability_tests.jl`; with this fix they pass on Julia 1.10/1.11/1.12. Bumps BoundaryValueDiffEqMIRKN patch version to 1.15.1. Local verification on Julia 1.11.9: === Test 2: @inferred === PASS Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent 85f7143 commit 562c552

5 files changed

Lines changed: 39 additions & 28 deletions

File tree

lib/BoundaryValueDiffEqFIRK/src/firk.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,11 @@ function SciMLBase.solve!(
552552
end
553553

554554
function __perform_firk_iteration(cache::Union{FIRKCacheExpand, FIRKCacheNested}, abstol, adaptive::Bool)
555-
nlprob = __construct_problem(cache, vec(cache.y₀), copy(cache.y₀))
555+
# See note in BoundaryValueDiffEqMIRK/src/mirk.jl: `vec(::VectorOfArray)`
556+
# returns a `ReshapedArray` under RAT v4, which widens the inferred
557+
# return type of NonlinearSolve's polyalg. Flatten via `.u` to keep
558+
# `nlprob.u0` a plain `Vector` and preserve inference.
559+
nlprob = __construct_problem(cache, reduce(vcat, cache.y₀.u), copy(cache.y₀))
556560
solve_alg = __concrete_solve_algorithm(nlprob, cache.alg.nlsolve, cache.alg.optimize)
557561
kwargs = __concrete_kwargs(
558562
cache.alg.nlsolve, cache.alg.optimize, cache.nlsolve_kwargs, cache.optimize_kwargs,

lib/BoundaryValueDiffEqMIRK/src/mirk.jl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,16 @@ function SciMLBase.solve!(
308308
end
309309

310310
function __perform_mirk_iteration(cache::MIRKCache, abstol, adaptive::Bool, controller::AbstractErrorControl)
311-
nlprob = __construct_problem(cache, vec(cache.y₀), copy(cache.y₀))
311+
# Flatten via `reduce(vcat, .u)` rather than `vec(::VectorOfArray)`. Under
312+
# RecursiveArrayTools v4, `vec` on a `VectorOfArray` returns a
313+
# `ReshapedArray{T, 1, VectorOfArray{...}}`, whose presence as
314+
# `nlprob.u0` widens the inferred return type of `solve(nlprob, polyalg)`
315+
# — `T`, `N`, `uType`, `R` of the resulting `NonlinearSolution` come back
316+
# as free type variables. A plain `Vector{Float64}` u0 lets NonlinearSolve's
317+
# polyalg infer its return type concretely. (Behaviorally equivalent: the
318+
# solver copies u0 internally and `recursive_unflatten!` writes the result
319+
# back into `cache.y₀` afterwards.)
320+
nlprob = __construct_problem(cache, reduce(vcat, cache.y₀.u), copy(cache.y₀))
312321
solve_alg = __concrete_solve_algorithm(nlprob, cache.alg.nlsolve, cache.alg.optimize)
313322
kwargs = __concrete_kwargs(
314323
cache.alg.nlsolve, cache.alg.optimize, cache.nlsolve_kwargs, cache.optimize_kwargs,

lib/BoundaryValueDiffEqMIRKN/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "BoundaryValueDiffEqMIRKN"
22
uuid = "9255f1d6-53bf-473e-b6bd-23f1ff009da4"
33
authors = ["Qingyu Qu <erikqqy123@gmail.com>"]
4-
version = "1.15.0"
4+
version = "1.15.1"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"

lib/BoundaryValueDiffEqMIRKN/src/mirkn.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,11 @@ function SciMLBase.solve!(cache::MIRKNCache{iip, T}) where {iip, T}
137137
end
138138

139139
function __perform_mirkn_iteration(cache::MIRKNCache)
140-
nlprob = __construct_nlproblem(cache, vec(cache.y₀), copy(cache.y₀))
140+
# See note in BoundaryValueDiffEqMIRK/src/mirk.jl: `vec(::VectorOfArray)`
141+
# returns a `ReshapedArray` under RAT v4, which widens the inferred
142+
# return type of NonlinearSolve's polyalg. Flatten via `.u` to keep
143+
# `nlprob.u0` a plain `Vector` and preserve inference.
144+
nlprob = __construct_nlproblem(cache, reduce(vcat, cache.y₀.u), copy(cache.y₀))
141145
solve_alg = __concrete_solve_algorithm(nlprob, cache.alg.nlsolve, cache.alg.optimize)
142146
kwargs = __concrete_kwargs(
143147
cache.alg.nlsolve, cache.alg.optimize, cache.nlsolve_kwargs, cache.optimize_kwargs,

test/misc/type_stability_tests.jl

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,15 @@
5151
# @inferred solve(mpbvp_oop, MultipleShooting(5, Tsit5(); jac_alg))
5252
# end
5353

54-
# MIRK methods can hit the same NonlinearSolve polyalg type instability
55-
# as Shooting once adaptive mesh refinement is exercised — the inner
56-
# NonlinearSolution type depends on which solver in the polyalg succeeds,
57-
# which is not statically resolvable. Skipped pending the same upstream
58-
# investigation noted above for Shooting.
59-
# @testset "MIRK Methods" begin
60-
# for solver in (
61-
# MIRK2(; jac_alg), MIRK3(; jac_alg), MIRK4(; jac_alg),
62-
# MIRK5(; jac_alg), MIRK6(; jac_alg),
63-
# )
64-
# @inferred solve(mpbvp_iip, solver; dt = 0.2)
65-
# @inferred solve(mpbvp_oop, solver; dt = 0.2)
66-
# end
67-
# end
54+
@testset "MIRK Methods" begin
55+
for solver in (
56+
MIRK2(; jac_alg), MIRK3(; jac_alg), MIRK4(; jac_alg),
57+
MIRK5(; jac_alg), MIRK6(; jac_alg),
58+
)
59+
@inferred solve(mpbvp_iip, solver; dt = 0.2)
60+
@inferred solve(mpbvp_oop, solver; dt = 0.2)
61+
end
62+
end
6863
end
6964

7065
# Two-Point BVP
@@ -84,15 +79,14 @@
8479
# @inferred solve(tpbvp_oop, MultipleShooting(5, Tsit5(); jac_alg))
8580
# end
8681

87-
# See note above about MIRK + NonlinearSolve polyalg.
88-
# @testset "MIRK Methods" begin
89-
# for solver in (
90-
# MIRK2(; jac_alg), MIRK3(; jac_alg), MIRK4(; jac_alg),
91-
# MIRK5(; jac_alg), MIRK6(; jac_alg),
92-
# )
93-
# @inferred solve(tpbvp_iip, solver; dt = 0.2)
94-
# @inferred solve(tpbvp_oop, solver; dt = 0.2)
95-
# end
96-
# end
82+
@testset "MIRK Methods" begin
83+
for solver in (
84+
MIRK2(; jac_alg), MIRK3(; jac_alg), MIRK4(; jac_alg),
85+
MIRK5(; jac_alg), MIRK6(; jac_alg),
86+
)
87+
@inferred solve(tpbvp_iip, solver; dt = 0.2)
88+
@inferred solve(tpbvp_oop, solver; dt = 0.2)
89+
end
90+
end
9791
end
9892
end

0 commit comments

Comments
 (0)