Hey there,
I’m trying to compute the derivatives of a function involving an ODE solution. This works if I use dense Jacobians in the ODE solver.
However, the Jacobian is actually sparse (PDE discretization, stiff) and I thought to exploit that:
using DifferentialEquations, ForwardDiff, SparseArrays, SciMLSensitivity
function rhs!(du, u, p, t)
    du[1] = p[1] * u[2]
    du[2] = -p[2] * u[1]
    du[3] = u[1] - u[2]
end
u0 = [1.0, 0.0, 0.0]
p0 = [0.5, 2.0]
tspan = (0.0, 5.0)
jac0 = spzeros(length(u0), length(u0))
jac0[1, 2] = 1.0
jac0[2, 1] = -1.0
jac0[3, 1] = 1.0
jac0[3, 2] = -1.0
function f_dense(p)
    prob = ODEProblem(ODEFunction(rhs!), u0, tspan)
    sol = solve(prob, QNDF(autodiff=true), p=p, saveat=1.0)
    return sol.u[end]
end
# This works:
ForwardDiff.jacobian(f_dense, p0)
function f_umfpack(p)
    prob = ODEProblem(ODEFunction(rhs!; jac_prototype=jac0), u0, tspan)
    sol = solve(prob, QNDF(autodiff=true, linsolve=UMFPACKFactorization()), p=p, saveat=1.0)
    return sol.u[end]
end
# This fails:
ForwardDiff.jacobian(f_umfpack, p0)
The error is
ERROR: MethodError: no method matching SparseArrays.UMFPACK.UmfpackLU(::SparseMatrixCSC{ForwardDiff.Dual{ForwardDiff.Tag{…}, Float64, 2}, Int64})
Closest candidates are:
  SparseArrays.UMFPACK.UmfpackLU(::SparseArrays.UMFPACK.Symbolic{Tv, Ti}, ::SparseArrays.UMFPACK.Numeric{Tv, Ti}, ::Int64, ::Int64, ::Vector{Ti}, ::Vector{Ti}, ::Vector{Tv}, ::Int64, ::SparseArrays.UMFPACK.UmfpackWS{Ti}, ::Vector{Float64}, ::Vector{Float64}, ::ReentrantLock) where {Tv<:Union{Float64, ComplexF64}, Ti<:Union{Int32, Int64}}
   @ SparseArrays ~/.julia/juliaup/julia-1.10.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/SparseArrays/src/solvers/umfpack.jl:225
  SparseArrays.UMFPACK.UmfpackLU(::SparseArrays.AbstractSparseMatrixCSC{Tv, Ti}; control) where {Tv<:Union{Float64, ComplexF64}, Ti<:Union{Int32, Int64}}
   @ SparseArrays ~/.julia/juliaup/julia-1.10.4+0.aarch64.apple.darwin14/share/julia/stdlib/v1.10/SparseArrays/src/solvers/umfpack.jl:239
Stacktrace:
  [1] init_cacheval(alg::UMFPACKFactorization, A::SparseMatrixCSC{…}, b::Vector{…}, u::Vector{…}, Pl::LinearSolve.InvPreconditioner{…}, Pr::LinearAlgebra.Diagonal{…}, maxiters::Int64, abstol::ForwardDiff.Dual{…}, reltol::ForwardDiff.Dual{…}, verbose::Bool, assumptions::OperatorAssumptions{…})
    @ LinearSolve ~/.julia/packages/LinearSolve/0Q5jw/src/factorization.jl:812
  [2] init(::LinearProblem{…}, ::UMFPACKFactorization; alias_A::Bool, alias_b::Bool, abstol::Float64, reltol::Float64, maxiters::Int64, verbose::Bool, Pl::LinearSolve.InvPreconditioner{…}, Pr::LinearAlgebra.Diagonal{…}, assumptions::OperatorAssumptions{…}, sensealg::LinearSolveAdjoint{…}, kwargs::@Kwargs{})
    @ LinearSolve ~/.julia/packages/LinearSolve/0Q5jw/src/common.jl:208
  [3] build_nlsolver(alg::QNDF{…}, nlalg::NLNewton{…}, u::Vector{…}, uprev::Vector{…}, p::Vector{…}, t::Float64, dt::Float64, f::ODEFunction{…}, rate_prototype::Vector{…}, ::Type{…}, ::Type{…}, ::Type{…}, γ::Rational{…}, c::Int64, α::Int64, ::Val{…})
    @ OrdinaryDiffEqNonlinearSolve ~/.julia/packages/OrdinaryDiffEqNonlinearSolve/EXjDj/src/utils.jl:199
  [4] build_nlsolver
    @ ~/.julia/packages/OrdinaryDiffEqNonlinearSolve/EXjDj/src/utils.jl:144 [inlined]
  [5] build_nlsolver
    @ ~/.julia/packages/OrdinaryDiffEqNonlinearSolve/EXjDj/src/utils.jl:134 [inlined]
  [6] alg_cache(alg::QNDF{…}, u::Vector{…}, rate_prototype::Vector{…}, ::Type{…}, ::Type{…}, ::Type{…}, uprev::Vector{…}, uprev2::Vector{…}, f::ODEFunction{…}, t::Float64, dt::Float64, reltol::Float64, p::Vector{…}, calck::Bool, ::Val{…})
    @ OrdinaryDiffEqBDF ~/.julia/packages/OrdinaryDiffEqBDF/J0IGS/src/bdf_caches.jl:430
  [7] __init(prob::ODEProblem{…}, alg::QNDF{…}, timeseries_init::Tuple{}, ts_init::Tuple{}, ks_init::Tuple{}, recompile::Type{…}; saveat::Float64, tstops::Tuple{}, d_discontinuities::Tuple{}, save_idxs::Nothing, save_everystep::Bool, save_on::Bool, save_start::Bool, save_end::Nothing, callback::Nothing, dense::Bool, calck::Bool, dt::Float64, dtmin::Float64, dtmax::Float64, force_dtmin::Bool, adaptive::Bool, gamma::Rational{…}, abstol::Nothing, reltol::Nothing, qmin::Rational{…}, qmax::Int64, qsteady_min::Int64, qsteady_max::Rational{…}, beta1::Nothing, beta2::Nothing, qoldinit::Rational{…}, controller::Nothing, fullnormalize::Bool, failfactor::Int64, maxiters::Int64, internalnorm::typeof(DiffEqBase.ODE_DEFAULT_NORM), internalopnorm::typeof(LinearAlgebra.opnorm), isoutofdomain::typeof(DiffEqBase.ODE_DEFAULT_ISOUTOFDOMAIN), unstable_check::typeof(DiffEqBase.ODE_DEFAULT_UNSTABLE_CHECK), verbose::Bool, timeseries_errors::Bool, dense_errors::Bool, advance_to_tstop::Bool, stop_at_next_tstop::Bool, initialize_save::Bool, progress::Bool, progress_steps::Int64, progress_name::String, progress_message::typeof(DiffEqBase.ODE_DEFAULT_PROG_MESSAGE), progress_id::Symbol, userdata::Nothing, allow_extrapolation::Bool, initialize_integrator::Bool, alias_u0::Bool, alias_du0::Bool, initializealg::OrdinaryDiffEqCore.DefaultInit, kwargs::@Kwargs{})
    @ OrdinaryDiffEqCore ~/.julia/packages/OrdinaryDiffEqCore/55UVY/src/solve.jl:349
  [8] __init (repeats 5 times)
    @ ~/.julia/packages/OrdinaryDiffEqCore/55UVY/src/solve.jl:11 [inlined]
  [9] __solve(::ODEProblem{…}, ::QNDF{…}; kwargs::@Kwargs{…})
    @ OrdinaryDiffEqCore ~/.julia/packages/OrdinaryDiffEqCore/55UVY/src/solve.jl:6
 [10] __solve
    @ ~/.julia/packages/OrdinaryDiffEqCore/55UVY/src/solve.jl:1 [inlined]
 [11] #solve_call#44
    @ ~/.julia/packages/DiffEqBase/vscDj/src/solve.jl:612 [inlined]
 [12] solve_up(prob::ODEProblem{…}, sensealg::Nothing, u0::Vector{…}, p::Vector{…}, args::QNDF{…}; kwargs::@Kwargs{…})
    @ DiffEqBase ~/.julia/packages/DiffEqBase/vscDj/src/solve.jl:1084
 [13] solve_up
    @ ~/.julia/packages/DiffEqBase/vscDj/src/solve.jl:1070 [inlined]
 [14] solve(prob::ODEProblem{…}, args::QNDF{…}; sensealg::Nothing, u0::Nothing, p::Vector{…}, wrap::Val{…}, kwargs::@Kwargs{…})
    @ DiffEqBase ~/.julia/packages/DiffEqBase/vscDj/src/solve.jl:1007
 [15] f_umfpack(p::Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(f_umfpack), Float64}, Float64, 2}})
    @ Main ./REPL[13]:3
 [16] vector_mode_dual_eval!
    @ ~/.julia/packages/ForwardDiff/PcZ48/src/apiutils.jl:24 [inlined]
 [17] vector_mode_jacobian(f::typeof(f_umfpack), x::Vector{…}, cfg::ForwardDiff.JacobianConfig{…})
    @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/jacobian.jl:125
 [18] jacobian(f::Function, x::Vector{…}, cfg::ForwardDiff.JacobianConfig{…}, ::Val{…})
    @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/jacobian.jl:0
 [19] jacobian(f::Function, x::Vector{Float64}, cfg::ForwardDiff.JacobianConfig{ForwardDiff.Tag{…}, Float64, 2, Vector{…}})
    @ ForwardDiff ~/.julia/packages/ForwardDiff/PcZ48/src/jacobian.jl:19
 [20] top-level scope
    @ REPL[14]:1
It does not depend on the linear solver since KLU also fails.
How can I use sparse Jacobians in the ODE solver while computing sensitivities?
Best,
Neodym