Skip to content

Commit 63e043b

Browse files
Restore GPU Broyden inverse initialization
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent d3656a0 commit 63e043b

3 files changed

Lines changed: 41 additions & 2 deletions

File tree

lib/NonlinearSolveBase/src/utils.jl

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ linsolve_workspace(A::Diagonal) = nothing, A
235235
linsolve_workspace(A::SMatrix) = nothing, A
236236
function linsolve_workspace(A::AbstractMatrix)
237237
LinearAlgebra.checksquare(A)
238+
# Backends without fast scalar indexing may not implement factorization solves with
239+
# a matrix right-hand side. Leave those arrays on their native `pinv` path.
240+
ArrayInterface.fast_scalar_indexing(A) || return nothing, A
238241
# the linear solve promotes e.g. integer eltypes; the buffers must hold the promoted
239242
# values. The `size` form of `similar` gives a dense buffer for structured input
240243
# (e.g. Tridiagonal), which the generically-dense inverse and identity RHS need.
@@ -283,8 +286,14 @@ end
283286
function linsolve_identity!!(workspace, A::AbstractMatrix)
284287
LinearAlgebra.checksquare(A)
285288
# a `nothing` workspace (the preinverted-init path in NonlinearSolveQuasiNewton)
286-
# builds a transient linear-solve cache; it allocates, but runs at most once per solve
287-
ws = workspace === nothing ? first(linsolve_workspace(A)) : workspace
289+
# builds a transient linear-solve cache; it allocates, but runs at most once per solve.
290+
# Sparse dispatch gets a chance to construct its dense workspace here, while backend
291+
# arrays that cannot use the cached matrix-RHS solve stay on their native `pinv`.
292+
ws = workspace
293+
if ws === nothing
294+
ws, workspace_A = linsolve_workspace(A)
295+
ws === nothing && return pinv(workspace_A)
296+
end
288297
# the previous solve may have consumed the RHS buffer, so refill it with I. The
289298
# lincache call copies A into its internal buffer before overwriting the solution
290299
# buffer, so A is never mutated and passing the previously returned inverse as A is

lib/NonlinearSolveBase/test/linsolve_workspace.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
using NonlinearSolveBase: NonlinearSolveBase, Utils
2+
using ArrayInterface
23
using LinearSolve # linsolve_identity!! routes matrices through the LinearSolve default
34
using LinearAlgebra
45
using Test
56

7+
struct NoFastScalarMatrix{T} <: AbstractMatrix{T}
8+
data::Matrix{T}
9+
end
10+
11+
Base.size(A::NoFastScalarMatrix) = size(A.data)
12+
Base.getindex(A::NoFastScalarMatrix, i::Int, j::Int) = A.data[i, j]
13+
Base.copy(A::NoFastScalarMatrix) = copy(A.data)
14+
ArrayInterface.fast_scalar_indexing(::Type{<:NoFastScalarMatrix}) = false
15+
616
@testset "nonsingular input matches inv" begin
717
n = 20
818
A_general = rand(n, n) + n * I
@@ -46,6 +56,13 @@ end
4656
@test Utils.linsolve_identity!!(workspace, workspace.rhs) inv(A_general)
4757
end
4858

59+
@testset "arrays without fast scalar indexing use pinv" begin
60+
A = NoFastScalarMatrix(rand(5, 5))
61+
workspace, A_ret = Utils.linsolve_workspace(A)
62+
@test workspace === nothing && A_ret === A
63+
@test Utils.linsolve_identity!!(workspace, A) pinv(A.data)
64+
end
65+
4966
@testset "singular input takes the pivoted-QR rescue" begin
5067
# The result is the LinearSolve default algorithm's least-squares generalized
5168
# inverse from its singular-LU → pivoted-QR rescue, NOT the SVD `pinv` (an

test/gpu/cuda_tests__item1.jl

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

33
using CUDA, NonlinearSolve, LinearSolve, StableRNGs, ADTypes
4+
using LinearAlgebra: norm
45

56
if CUDA.functional()
67
CUDA.allowscalar(false)
@@ -12,6 +13,7 @@ if CUDA.functional()
1213
linear_f(du, u, p) = (du .= A * u .+ b)
1314

1415
prob = NonlinearProblem(linear_f, u0)
16+
iip_prob = prob
1517

1618
# ForwardDiff uses scalar indexing which doesn't work on GPU
1719
# Use AutoFiniteDiff for GPU-compatible Jacobian computation
@@ -39,10 +41,21 @@ if CUDA.functional()
3941
linear_f(u, p) = A * u .+ b
4042

4143
prob = NonlinearProblem{false}(linear_f, u0)
44+
oop_prob = prob
4245

4346
@testset "[OOP] GPU Solvers" begin
4447
@testset "$(nameof(typeof(alg)))" for alg in SOLVERS
4548
@test_nowarn sol = solve(prob, alg; abstol = 1.0f-5, reltol = 1.0f-5)
4649
end
4750
end
51+
52+
@testset "Broyden inverse initialization" begin
53+
for prob in (iip_prob, oop_prob)
54+
sol = solve(
55+
prob, Broyden(; linesearch = LiFukushimaLineSearch());
56+
abstol = 1.0f-5, reltol = 1.0f-5
57+
)
58+
@test norm(A * sol.u + b) < 1.0f-4
59+
end
60+
end
4861
end

0 commit comments

Comments
 (0)