I am trying to use ForwardDiff to calculate the Jacobian of a function so that I can reuse the resulting Jacobian function and use it as an input for several packages. For instance, pass it to NLsolve and later use it in DiffEq as well. I know that the Jacobian will be sparse, so I do one evaluation to get the sparsity and use the in-place evaluations later.
The MWE below works, and @code_warntype is making the type inference correctly in every step. However, I still get allocations when evaluating the Jacobian function on a set of values.
using ForwardDiff
using Random
using SparseArrays
function component1(u, common) where T
common[1] = 1.0*u[1]
return -0.1 * u[1]
end
function component2(u, common) where T
common[2] = -0.8 * u[2]
return -0.4 * u[2]
end
function component3(u, common) where T
return sum(common .* [-0.1, 0.2, 0.01]) + 0.02 * u[3]
end
function system(du, u, common, t) where T
du[1] = component1(u, common)
du[2] = component2(u, common)
du[3] = component3(u, common)
nothing
end
struct modelf{T, U}
common::Vector{T}
common_dual::Vector{U}
du::Vector{T}
du_dual::Vector{U}
function modelf(du0::Vector{T}) where T <: Number
# Forced the CHUNK to 3 for the MWE.
U = ForwardDiff.Dual{typeof(ForwardDiff.Tag(system, T)), T, 3}
new{T, U}(zeros(T, 3), zeros(U, 3), similar(du0), zeros(U, length(du0)))
end
end
get_common(m::modelf, ::Type{Float64}) = m.common
get_common(m::modelf, ::Type{T}) where T <: ForwardDiff.Dual = reinterpret(T, m.common_dual)
get_du(m::modelf, ::Type{Float64}) = m.du
get_du(m::modelf, ::Type{T}) where T <: ForwardDiff.Dual = reinterpret(T, m.du_dual)
function (f::modelf)(u::AbstractArray{T}, t) where T
system(get_du(f, T), u, get_common(f, T), t)
return get_du(f, T)
end
struct modelJ{T}
Jf::T
Jv::SparseMatrixCSC{Float64, Int64}
x::Vector{Float64}
function modelJ(x)
model = modelf(zeros(3))
fu = (u) -> model(u, 0)
jconfig = ForwardDiff.JacobianConfig(fu , x, ForwardDiff.Chunk{3}())
Jf = (Jv, x) -> ForwardDiff.jacobian!(Jv, fu, x, jconfig)
n = length(x)
sJ = sparse(Jf(zeros(n, n), x))
new{typeof(Jf)}(Jf, sJ, x)
end
end
function (J::modelJ{T})(x) where T <: Function
J.x .= x
return J.Jf(J.Jv, x)
end
test = modelJ(rand(3))
@time test(rand(3))
0.000014 seconds (9 allocations: 656 bytes)
3×3 SparseMatrixCSC{Float64, Int64} with 5 stored entries:
-0.1 ⋅ ⋅
⋅ -0.4 ⋅
-0.1 -0.16 0.02
Any ideas on where the allocations might be coming from?