Skip to content

Commit d3f0790

Browse files
authored
ensure that default solver is chosen based on primals (#1105)
1 parent 7e3a84a commit d3f0790

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

ext/LinearSolveForwardDiffExt.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,21 @@ function SciMLBase.init(
326326
)
327327
end
328328

329+
# `solve(prob)` resolves `alg::Nothing` itself (common.jl) before `init` is called,
330+
# which would select from the *dual* A/b types. The algorithm always executes on the
331+
# primal arrays (`nodual_value(A)`), so selection must see those instead: e.g. a
332+
# reinterpret-wrapped Dual A is not a `DenseMatrix` and would select KrylovJL_GMRES,
333+
# while the primal cache is a dense `Matrix` whose default-solver Krylov cacheval
334+
# slots are initialized as `Nothing` (see `_init_default_cacheval`).
335+
function SciMLBase.solve(
336+
prob::DualAbstractLinearProblem, ::Nothing, args...;
337+
assump = OperatorAssumptions(issquare(prob.A)), kwargs...
338+
)
339+
new_A = nodual_value(prob.A)
340+
new_b = nodual_value(prob.b)
341+
return solve(prob, defaultalg(new_A, new_b, assump), args...; kwargs...)
342+
end
343+
329344
function __dual_init(
330345
prob::DualAbstractLinearProblem, alg::SciMLLinearSolveAlgorithm,
331346
args...;

test/Core/forwarddiff_overloads.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,3 +719,34 @@ end
719719
@test getfield(cache, :dual_linear_cache) === inner
720720
@test (sol3, Matrix(A2) \ b2, rtol = 1.0e-9)
721721
end
722+
723+
struct ReinterpretTestTag end
724+
725+
@testset "Default algorithm selection uses primal values (issue with reinterpret-wrapped duals)" begin
726+
# `solve(prob)` with no algorithm must select the default from the *primal*
727+
# A/b (what the inner LinearCache actually solves with), not the dual types.
728+
# A reinterpret/reshape-wrapped Dual A (as produced by PreallocationTools'
729+
# get_tmp) is not a DenseMatrix, so dual-type-based selection picks
730+
# KrylovJL_GMRES — but the primal cache is a dense Matrix whose default-solver
731+
# Krylov cacheval slots are initialized as Nothing, giving a TypeError in
732+
# __setfield! at solve time.
733+
DualT = ForwardDiff.Dual{ForwardDiff.Tag{ReinterpretTestTag, Float64}, Float64, 1}
734+
735+
A_buf = zeros(2)
736+
b_buf = zeros(2)
737+
A = reshape(reinterpret(DualT, A_buf), 1, 1)
738+
b = reinterpret(DualT, b_buf)
739+
A[1, 1] = DualT(2.0, ForwardDiff.Partials((1.0,)))
740+
b[1] = DualT(3.0, ForwardDiff.Partials((0.5,)))
741+
742+
@test !(A isa DenseMatrix) # the wrapper that triggered dual-based GMRES selection
743+
744+
sol = solve(LinearProblem(A, b))
745+
# u = b/A = 1.5, du = (db - u * dA)/A = (0.5 - 1.5)/2 = -0.5
746+
@test ForwardDiff.value(sol.u[1]) 1.5
747+
@test ForwardDiff.partials(sol.u[1])[1] -0.5
748+
749+
# The init entry point selects from primal values too and must agree.
750+
cache = init(LinearProblem(A, b), nothing)
751+
@test solve!(cache).u sol.u
752+
end

0 commit comments

Comments
 (0)