Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions ext/LinearSolveForwardDiffExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -763,4 +763,15 @@ function update_partials_list!(partial_matrix::SparseMatrixCSC, list_cache)
return list_cache
end


# The MC64 matching in the vendored SupernodalLU solver runs its combinatorial
# side (the max-product assignment) in Float64, so it needs a real magnitude
# for a `Dual` entry. Only the *ordering* of candidate pivots is decided from
# these numbers; the factorization itself stays in Dual arithmetic, so taking
# the primal here loses nothing. Without this, `snlu` on a Dual matrix with a
# missing or weak structural diagonal - exactly when `matching = :auto`
# engages - throws `MethodError: no method matching Float64(::Dual)`.
@inline LinearSolve.SupernodalLU._costabs(x::Dual) =
LinearSolve.SupernodalLU._costabs(ForwardDiff.value(x))

end
25 changes: 25 additions & 0 deletions test/Core/forwarddiff_overloads.jl
Original file line number Diff line number Diff line change
Expand Up @@ -750,3 +750,28 @@ struct ReinterpretTestTag end
cache = init(LinearProblem(A, b), nothing)
@test solve!(cache).u ≈ sol.u
end

@testset "SupernodalLU matching accepts Dual entries" begin
# MC64 matching decides its assignment in Float64, so it needs a real
# magnitude per entry. An anti-diagonal matrix has no structural diagonal
# at all, so `matching = :auto` engages and the factorization goes through
# `_costabs` — which threw on Duals until the ForwardDiff extension
# supplied the primal-extracting overload its docstring promised.
SNLU = LinearSolve.SupernodalLU
n = 40
vals = [ForwardDiff.Dual{Nothing}(2.0 + i / n, 1.0) for i in 1:n]
P = sparse(collect(n:-1:1), collect(1:n), vals, n, n)
@test SNLU.needs_matching(P)
F = SNLU.snlu(P)
@test F.matched

# Solving must be right in both the value and the derivative. With
# A(t) = diag-reversed(2 + i/n + t) and b fixed, each x_i = b_j / a_i, so
# dx_i/dt = -b_j / a_i^2.
b = randn(n)
x = similar(b, eltype(vals))
SNLU.solve!(x, F, ForwardDiff.Dual{Nothing}.(b, 0.0))
@test ForwardDiff.value.(x) ≈ [b[n + 1 - i] / (2.0 + i / n) for i in 1:n] rtol = 1.0e-10
@test ForwardDiff.partials.(x, 1) ≈
[-b[n + 1 - i] / (2.0 + i / n)^2 for i in 1:n] rtol = 1.0e-10
end
Loading