Skip to content

Commit 7e96b21

Browse files
Eliminate dense homotopy step allocations
Cache an owned ForwardDiff destination adapter for dense in-place Jacobians and avoid reshaping already-flat Newton steps. Keep the stored Jacobian matrix identity visible to callers. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent 579761d commit 7e96b21

3 files changed

Lines changed: 40 additions & 4 deletions

File tree

lib/NonlinearSolveBase/src/jacobian.jl

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ function construct_jacobian_cache(
125125
end
126126
end
127127

128-
return JacobianCache(J, f, fu, p, stats, autodiff, di_extras)
128+
ad_buffer = _jacobian_ad_buffer(J, f, fu, u, autodiff)
129+
return JacobianCache(J, ad_buffer, f, fu, p, stats, autodiff, di_extras)
129130
end
130131

131132
function construct_jacobian_cache(
@@ -134,7 +135,7 @@ function construct_jacobian_cache(
134135
linsolve = missing
135136
)
136137
if SciMLBase.has_jac(f) || SciMLBase.has_vjp(f) || SciMLBase.has_jvp(f)
137-
return JacobianCache(fu, f, fu, p, stats, autodiff, nothing)
138+
return JacobianCache(fu, fu, f, fu, p, stats, autodiff, nothing)
138139
end
139140
if autodiff === nothing
140141
throw(ArgumentError("`autodiff` argument to `construct_jacobian_cache` must be \
@@ -145,11 +146,37 @@ function construct_jacobian_cache(
145146
@assert !(autodiff isa AutoSparse) "`autodiff` cannot be `AutoSparse` for scalar \
146147
nonlinear problems."
147148
di_extras = DI.prepare_derivative(f, autodiff, u, Constant(prob.p))
148-
return JacobianCache(u, f, fu, p, stats, autodiff, di_extras)
149+
return JacobianCache(u, u, f, fu, p, stats, autodiff, di_extras)
150+
end
151+
152+
# ForwardDiff reshapes its destination before returning it. This adapter lets a dense
153+
# vector-to-vector Jacobian return the stored matrix directly instead of allocating an
154+
# equivalent array header on every evaluation.
155+
struct _JacobianADBuffer{T, M <: Matrix{T}} <: AbstractMatrix{T}
156+
data::M
157+
end
158+
159+
Base.parent(A::_JacobianADBuffer) = A.data
160+
Base.size(A::_JacobianADBuffer) = size(parent(A))
161+
Base.axes(A::_JacobianADBuffer) = axes(parent(A))
162+
Base.IndexStyle(::Type{<:_JacobianADBuffer}) = IndexLinear()
163+
@inline Base.getindex(A::_JacobianADBuffer, i::Int) = parent(A)[i]
164+
@inline Base.setindex!(A::_JacobianADBuffer, value, i::Int) = (parent(A)[i] = value)
165+
function Base.reshape(A::_JacobianADBuffer, dims::Dims)
166+
return dims == size(A) ? parent(A) : reshape(parent(A), dims)
167+
end
168+
169+
_jacobian_ad_buffer(J, f, fu, u, autodiff) = J
170+
function _jacobian_ad_buffer(
171+
J::Matrix, f, fu::Vector, u::Vector, autodiff::AutoForwardDiff
172+
)
173+
SciMLBase.isinplace(f) && !SciMLBase.has_jac(f) || return J
174+
return _JacobianADBuffer(J)
149175
end
150176

151177
@concrete mutable struct JacobianCache <: AbstractJacobianCache
152178
J
179+
ad_buffer
153180
f <: NonlinearFunction
154181
fu
155182
p
@@ -218,7 +245,8 @@ function (cache::JacobianCache)(u)
218245
f.jac(J, u, p)
219246
else
220247
DI.jacobian!(
221-
f, cache.fu, J, cache.di_extras, cache.autodiff, u, Constant(p)
248+
f, cache.fu, cache.ad_buffer, cache.di_extras, cache.autodiff, u,
249+
Constant(p)
222250
)
223251
end
224252
return J

lib/NonlinearSolveBase/src/utils.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ convert_real(::Type{T}, ::Nothing) where {T} = nothing
105105
convert_real(::Type{T}, x) where {T} = real(T(x))
106106

107107
restructure(::Number, x::Number) = x
108+
function restructure(y::Vector, x::Vector)
109+
size(y) == size(x) && return x
110+
return ArrayInterface.restructure(y, x)
111+
end
108112
function restructure(
109113
y::T1, x::T2
110114
) where {T1 <: AbstractSciMLOperator, T2 <: AbstractSciMLOperator}

lib/NonlinearSolveFirstOrder/test/sparsity_tests__item2.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ for nlf in (f, f!)
2828

2929
cache = init(nlprob, NewtonRaphson(); abstol = 1.0e-9)
3030
@test cache.jac_cache.J isa Matrix
31+
if nlf === f!
32+
J = cache.jac_cache.J
33+
@test cache.jac_cache(u0) === J
34+
end
3135
sol = solve!(cache)
3236
@test SciMLBase.successful_retcode(sol)
3337
end

0 commit comments

Comments
 (0)