Skip to content

Commit 247a8bf

Browse files
Use LinearSolve for ForwardDiff implicit sensitivities (#1055)
Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
1 parent c223b21 commit 247a8bf

2 files changed

Lines changed: 61 additions & 2 deletions

File tree

lib/NonlinearSolveBase/ext/NonlinearSolveBaseForwardDiffExt.jl

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function NonlinearSolveBase.nonlinearsolve_forwarddiff_solve(
178178

179179
Jₚ = NonlinearSolveBase.nonlinearsolve_∂f_∂p(ad_prob, fn, uu, p)
180180
Jᵤ = NonlinearSolveBase.nonlinearsolve_∂f_∂u(ad_prob, fn, uu, p)
181-
z = -Jᵤ \ Jₚ
181+
z = -_forwarddiff_implicit_solve(Jᵤ, Jₚ)
182182
pp = prob.p
183183
sumfun = ((z, p),) -> map(Base.Fix2(*, ForwardDiff.partials(p)), z)
184184

@@ -222,6 +222,22 @@ function NonlinearSolveBase.nonlinearsolve_∂f_∂u(prob, f::F, u, p) where {F}
222222
return ForwardDiff.jacobian(Base.Fix2(f, p), u)
223223
end
224224

225+
_forwarddiff_implicit_solve(A::Number, B) = A \ B
226+
227+
function _forwarddiff_implicit_solve(A, B)
228+
Utils.is_extension_loaded(Val(:LinearSolve)) || return A \ B
229+
230+
T = promote_type(typeof(oneunit(eltype(A)) / oneunit(eltype(A))), eltype(B))
231+
u = similar(B, T, size(B))
232+
lincache = NonlinearSolveBase.construct_linear_solver(
233+
nothing, nothing, A, B, u, nothing;
234+
stats = SciMLBase.NLStats(0, 0, 0, 0, 0), verbose = false
235+
)
236+
linres = lincache()
237+
linres.success || error("Linear solve failed while differentiating a nonlinear solve.")
238+
return linres.u
239+
end
240+
225241
function NonlinearSolveBase.nonlinearsolve_dual_solution(
226242
u::Number, partials,
227243
::Union{<:AbstractArray{<:Dual{T, V, P}}, Dual{T, V, P}}
@@ -302,7 +318,7 @@ function CommonSolve.solve!(cache::NonlinearSolveForwardDiffCache)
302318
Jₚ = NonlinearSolveBase.nonlinearsolve_∂f_∂p(ad_prob, fn, uu, cache.values_p)
303319
Jᵤ = NonlinearSolveBase.nonlinearsolve_∂f_∂u(ad_prob, fn, uu, cache.values_p)
304320

305-
z_arr = -Jᵤ \ Jₚ
321+
z_arr = -_forwarddiff_implicit_solve(Jᵤ, Jₚ)
306322

307323
sumfun = ((z, p),) -> map(zᵢ -> zᵢ * ForwardDiff.partials(p), z)
308324
if cache.p isa Number

test/Core/forward_ad_tests__item2.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using NonlinearSolve
22

3+
using SciMLBase
34
using ForwardDiff, FiniteDiff
45

56
function objfn(F, init, params)
@@ -107,3 +108,45 @@ hess1 = ForwardDiff.hessian(solve_nlprob_with_cache, [34.0, 87.0])
107108
hess2 = FiniteDiff.finite_difference_hessian(solve_nlprob_with_cache, [34.0, 87.0])
108109

109110
@test hess1 hess2 atol = 1.0e-3
111+
112+
function singular_nlls_resid!(resid, u, p)
113+
resid[1] = u[1] - p[1]
114+
resid[2] = 0
115+
return nothing
116+
end
117+
118+
function singular_nlls_solution(p)
119+
prob = NonlinearLeastSquaresProblem(
120+
NonlinearFunction(singular_nlls_resid!, resid_prototype = zeros(2)),
121+
[1.0, 2.0], p
122+
)
123+
return solve(prob, NewtonRaphson(); abstol = 1.0e-12, reltol = 1.0e-12)
124+
end
125+
126+
singular_nlls_sum(p) = sum(singular_nlls_solution(p).u)
127+
128+
function singular_nlls_cached_solution(p)
129+
prob = NonlinearLeastSquaresProblem(
130+
NonlinearFunction(singular_nlls_resid!, resid_prototype = zeros(2)),
131+
[1.0, 2.0], p
132+
)
133+
cache = init(prob, NewtonRaphson(); abstol = 1.0e-12, reltol = 1.0e-12)
134+
return solve!(cache)
135+
end
136+
137+
singular_nlls_cached_sum(p) = sum(singular_nlls_cached_solution(p).u)
138+
139+
@test SciMLBase.successful_retcode(singular_nlls_solution([1.0]).retcode)
140+
@test SciMLBase.successful_retcode(singular_nlls_cached_solution([1.0]).retcode)
141+
@test ForwardDiff.gradient(singular_nlls_sum, [1.0]) [1.0]
142+
@test ForwardDiff.gradient(singular_nlls_cached_sum, [1.0]) [1.0]
143+
144+
function scalar_nl_solution(p)
145+
prob = NonlinearProblem((u, p) -> u^2 - p[1] - p[2], 2.0, p)
146+
return solve(prob, NewtonRaphson(); abstol = 1.0e-12, reltol = 1.0e-12)
147+
end
148+
149+
scalar_nl_sum(p) = scalar_nl_solution(p).u
150+
151+
@test SciMLBase.successful_retcode(scalar_nl_solution([4.0, 0.0]).retcode)
152+
@test ForwardDiff.gradient(scalar_nl_sum, [4.0, 0.0]) [0.25, 0.25]

0 commit comments

Comments
 (0)