Skip to content

Commit b8a13ef

Browse files
authored
implement in-place ldiv! for Cholesky factorization (#547)
* implement solve2 without workspace arguments * implement in-place ldiv! for Cholesky factorization * ldiv! with Dense not required * import ldiv! for tests * safer * fix Dense case and add more tests * rearrange wrap_dense_and_ptr * more tests
1 parent 1527014 commit b8a13ef

File tree

2 files changed

+126
-6
lines changed

2 files changed

+126
-6
lines changed

src/solvers/cholmod.jl

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ using LinearAlgebra
1818
using LinearAlgebra: RealHermSymComplexHerm, AdjOrTrans
1919
import LinearAlgebra: (\), AdjointFactorization,
2020
cholesky, cholesky!, det, diag, ishermitian, isposdef,
21-
issuccess, issymmetric, ldlt, ldlt!, logdet,
21+
issuccess, issymmetric, ldiv!, ldlt, ldlt!, logdet,
2222
lowrankdowndate, lowrankdowndate!, lowrankupdate, lowrankupdate!
2323

2424
using SparseArrays
@@ -913,6 +913,32 @@ function Base.convert(::Type{Dense{Tnew}}, A::Dense{T}) where {Tnew, T}
913913
end
914914
Base.convert(::Type{Dense{T}}, A::Dense{T}) where T = A
915915

916+
# Just calling Dense(x) or Dense(b) will allocate new
917+
# `cholmod_dense_struct`s in CHOLMOD. Instead, we want to reuse
918+
# the existing memory. We can do this by creating new
919+
# `cholmod_dense_struct`s and filling them manually.
920+
function wrap_dense_and_ptr(x::StridedVecOrMat{T}) where {T <: VTypes}
921+
dense_x = cholmod_dense_struct()
922+
dense_x.nrow = size(x, 1)
923+
dense_x.ncol = size(x, 2)
924+
dense_x.nzmax = length(x)
925+
dense_x.d = stride(x, 2)
926+
dense_x.x = pointer(x)
927+
dense_x.z = C_NULL
928+
dense_x.xtype = xtyp(eltype(x))
929+
dense_x.dtype = dtyp(eltype(x))
930+
return dense_x, pointer_from_objref(dense_x)
931+
end
932+
# We need to use a special handling for the case of `Dense`
933+
# input arrays since the `pointer` refers to the pointer to the
934+
# `cholmod_dense`, not to the array values themselves as for
935+
# standard arrays.
936+
function wrap_dense_and_ptr(x::Dense{T}) where {T <: VTypes}
937+
dense_x_ptr = x.ptr
938+
dense_x = unsafe_load(dense_x_ptr)
939+
return dense_x, pointer_from_objref(dense_x)
940+
end
941+
916942
# This constructor assumes zero based colptr and rowval
917943
function Sparse(m::Integer, n::Integer,
918944
colptr0::Vector{Ti}, rowval0::Vector{Ti},
@@ -1913,6 +1939,66 @@ const AbstractSparseVecOrMatInclAdjAndTrans = Union{AbstractSparseVecOrMat, AdjO
19131939
throw(ArgumentError("self-adjoint sparse system solve not implemented for sparse rhs B," *
19141940
" consider to convert B to a dense array"))
19151941

1942+
# in-place ldiv!
1943+
for TI in IndexTypes
1944+
@eval function ldiv!(x::StridedVecOrMat{T},
1945+
L::Factor{T, $TI},
1946+
b::StridedVecOrMat{T}) where {T<:VTypes}
1947+
if x === b
1948+
throw(ArgumentError("output array must not be aliased with input array"))
1949+
end
1950+
if size(L, 1) != size(b, 1)
1951+
throw(DimensionMismatch("Factorization and RHS should have the same number of rows. " *
1952+
"Factorization has $(size(L, 2)) rows, but RHS has $(size(b, 1)) rows."))
1953+
end
1954+
if size(L, 2) != size(x, 1)
1955+
throw(DimensionMismatch("Factorization and solution should match sizes. " *
1956+
"Factorization has $(size(L, 1)) columns, but solution has $(size(x, 1)) rows."))
1957+
end
1958+
if size(x, 2) != size(b, 2)
1959+
throw(DimensionMismatch("Solution and RHS should have the same number of columns. " *
1960+
"Solution has $(size(x, 2)) columns, but RHS has $(size(b, 2)) columns."))
1961+
end
1962+
if !issuccess(L)
1963+
s = unsafe_load(pointer(L))
1964+
if s.is_ll == 1
1965+
throw(LinearAlgebra.PosDefException(s.minor))
1966+
else
1967+
throw(LinearAlgebra.ZeroPivotException(s.minor))
1968+
end
1969+
end
1970+
1971+
# Just calling Dense(x) or Dense(b) will allocate new
1972+
# `cholmod_dense_struct`s in CHOLMOD. Instead, we want to reuse
1973+
# the existing memory. We can do this by creating new
1974+
# `cholmod_dense_struct`s and filling them manually.
1975+
dense_x, dense_x_ptr = wrap_dense_and_ptr(x)
1976+
dense_b, dense_b_ptr = wrap_dense_and_ptr(b)
1977+
1978+
X_Handle = Ptr{cholmod_dense_struct}(dense_x_ptr)
1979+
Y_Handle = Ptr{cholmod_dense_struct}(C_NULL)
1980+
E_Handle = Ptr{cholmod_dense_struct}(C_NULL)
1981+
status = GC.@preserve x dense_x b dense_b begin
1982+
$(cholname(:solve2, TI))(
1983+
CHOLMOD_A, L,
1984+
Ref(dense_b), C_NULL,
1985+
Ref(X_Handle), C_NULL,
1986+
Ref(Y_Handle),
1987+
Ref(E_Handle),
1988+
getcommon($TI))
1989+
end
1990+
if Y_Handle != C_NULL
1991+
free!(Y_Handle)
1992+
end
1993+
if E_Handle != C_NULL
1994+
free!(E_Handle)
1995+
end
1996+
@assert !iszero(status)
1997+
1998+
return x
1999+
end
2000+
end
2001+
19162002
## Other convenience methods
19172003
function diag(F::Factor{Tv, Ti}) where {Tv, Ti}
19182004
f = unsafe_load(typedpointer(F))

test/cholmod.jl

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ using Random
1313
using Serialization
1414
using LinearAlgebra:
1515
I, cholesky, cholesky!, det, diag, eigmax, ishermitian, isposdef, issuccess,
16-
issymmetric, ldlt, ldlt!, logdet, norm, opnorm, Diagonal, Hermitian, Symmetric,
16+
issymmetric, ldiv!, ldlt, ldlt!, logdet, norm, opnorm, Diagonal, Hermitian, Symmetric,
1717
PosDefException, ZeroPivotException, RowMaximum
1818
using SparseArrays
1919
using SparseArrays: getcolptr
@@ -138,6 +138,9 @@ Random.seed!(123)
138138
@test CHOLMOD.isvalid(chma)
139139
@test unsafe_load(pointer(chma)).is_ll == 1 # check that it is in fact an LLt
140140
@test chma\b x
141+
x2 = zero(x)
142+
@inferred ldiv!(x2, chma, b)
143+
@test x2 x
141144
@test nnz(chma) == 489
142145
@test nnz(cholesky(A, perm=1:size(A,1))) > nnz(chma)
143146
@test size(chma) == size(A)
@@ -281,6 +284,37 @@ end
281284
end
282285
end
283286

287+
@testset "ldiv! $Tv $Ti" begin
288+
local A, x, x2, b, X, X2, B
289+
A = sprand(10, 10, 0.1)
290+
A = I + A * A'
291+
A = convert(SparseMatrixCSC{Tv,Ti}, A)
292+
factor = cholesky(A)
293+
294+
x = fill(Tv(1), 10)
295+
b = A * x
296+
x2 = zero(x)
297+
@inferred ldiv!(x2, factor, b)
298+
@test x2 x
299+
300+
X = fill(Tv(1), 10, 5)
301+
B = A * X
302+
X2 = zero(X)
303+
@inferred ldiv!(X2, factor, B)
304+
@test X2 X
305+
306+
c = fill(Tv(1), size(x, 1) + 1)
307+
C = fill(Tv(1), size(X, 1) + 1, size(X, 2))
308+
y = fill(Tv(1), size(x, 1) + 1)
309+
Y = fill(Tv(1), size(X, 1) + 1, size(X, 2))
310+
@test_throws DimensionMismatch ldiv!(y, factor, b)
311+
@test_throws DimensionMismatch ldiv!(Y, factor, B)
312+
@test_throws DimensionMismatch ldiv!(x2, factor, c)
313+
@test_throws DimensionMismatch ldiv!(X2, factor, C)
314+
@test_throws DimensionMismatch ldiv!(X2, factor, b)
315+
@test_throws DimensionMismatch ldiv!(x2, factor, B)
316+
end
317+
284318
end #end for Ti ∈ itypes
285319

286320
for Tv (Float32, Float64)
@@ -365,9 +399,9 @@ end
365399
@test isa(CHOLMOD.eye(3), CHOLMOD.Dense{Float64})
366400
end
367401

368-
@testset "Core functionality ($elty, $elty2)" for
369-
elty in (Tv, Complex{Tv}),
370-
Tv2 in (Float32, Float64),
402+
@testset "Core functionality ($elty, $elty2)" for
403+
elty in (Tv, Complex{Tv}),
404+
Tv2 in (Float32, Float64),
371405
elty2 in (Tv2, Complex{Tv2}),
372406
Ti itypes
373407
A1 = sparse(Ti[1:5; 1], Ti[1:5; 2], elty <: Real ? randn(Tv, 6) : complex.(randn(Tv, 6), randn(Tv, 6)))
@@ -972,7 +1006,7 @@ end
9721006
f = ones(size(K, 1))
9731007
u = K \ f
9741008
residual = norm(f - K * u) / norm(f)
975-
@test residual < 1e-6
1009+
@test residual < 1e-6
9761010
end
9771011

9781012
@testset "wrapped sparse matrices" begin

0 commit comments

Comments
 (0)