Skip to content

Commit 9f3b51e

Browse files
Merge branch 'main' into do/csc-interface-baseline
2 parents 3765e0b + 95fd7ff commit 9f3b51e

File tree

6 files changed

+214
-78
lines changed

6 files changed

+214
-78
lines changed

docs/make.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ makedocs(
88
sitename = "SparseArrays",
99
pages = Any[
1010
"SparseArrays" => "index.md",
11-
"Sparse Linear Algebra" => "solvers.md",
1211
];
1312
warnonly = [:missing_docs, :cross_references],
1413
)

docs/src/index.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,27 @@ section of the standard library reference.
206206
| [`sprandn(m,n,d)`](@ref) | [`randn(m,n)`](@ref) | Creates a *m*-by-*n* random matrix (of density *d*) with iid non-zero elements distributed according to the standard normal (Gaussian) distribution. |
207207
| [`sprandn(rng,m,n,d)`](@ref) | [`randn(rng,m,n)`](@ref) | Creates a *m*-by-*n* random matrix (of density *d*) with iid non-zero elements generated with the `rng` random number generator |
208208

209+
## [Sparse Linear Algebra](@id stdlib-sparse-linalg)
210+
211+
Sparse matrix solvers call functions from [SuiteSparse](http://suitesparse.com). The following factorizations are available:
212+
213+
| Type | Description |
214+
|:----------------------|:--------------------------------------------- |
215+
| `CHOLMOD.Factor` | Cholesky and LDLt factorizations |
216+
| `UMFPACK.UmfpackLU` | LU factorization |
217+
| `SPQR.QRSparse` | QR factorization |
218+
219+
These factorizations are described in more detail in the [Sparse Linear Algebra API section](@ref stdlib-sparse-linalg-api):
220+
221+
1. [`cholesky`](@ref SparseArrays.CHOLMOD.cholesky)
222+
2. [`ldlt`](@ref SparseArrays.CHOLMOD.ldlt)
223+
3. [`lu`](@ref SparseArrays.UMFPACK.lu)
224+
4. [`qr`](@ref SparseArrays.SPQR.qr)
225+
226+
```@meta
227+
DocTestSetup = nothing
228+
```
229+
209230
# [SparseArrays API](@id stdlib-sparse-arrays)
210231

211232
```@docs
@@ -245,6 +266,26 @@ SparseArrays.ftranspose!
245266
```@meta
246267
DocTestSetup = nothing
247268
```
269+
270+
# [Sparse Linear Algebra API](@id stdlib-sparse-linalg-api)
271+
272+
```@docs
273+
SparseArrays.CHOLMOD.cholesky
274+
SparseArrays.CHOLMOD.cholesky!
275+
SparseArrays.CHOLMOD.lowrankupdate
276+
SparseArrays.CHOLMOD.lowrankupdate!
277+
SparseArrays.CHOLMOD.lowrankdowndate
278+
SparseArrays.CHOLMOD.lowrankdowndate!
279+
SparseArrays.CHOLMOD.lowrankupdowndate!
280+
SparseArrays.CHOLMOD.ldlt
281+
SparseArrays.UMFPACK.lu
282+
SparseArrays.SPQR.qr
283+
```
284+
285+
```@meta
286+
DocTestSetup = nothing
287+
```
288+
248289
# Noteworthy External Sparse Packages
249290

250291
Several other Julia packages provide sparse matrix implementations that should be mentioned:
@@ -264,3 +305,15 @@ Several other Julia packages provide sparse matrix implementations that should b
264305
7. [ExtendableSparse.jl](https://github.com/j-fu/ExtendableSparse.jl) enables fast insertion into sparse matrices using a lazy approach to new stored indices.
265306

266307
8. [Finch.jl](https://github.com/willow-ahrens/Finch.jl) supports extensive multidimensional sparse array formats and operations through a mini tensor language and compiler, all in native Julia. Support for COO, CSF, CSR, CSC and more, as well as operations like broadcast, reduce, etc. and custom operations.
308+
309+
External packages providing sparse direct solvers:
310+
1. [KLU.jl](https://github.com/JuliaSparse/KLU.jl)
311+
2. [Pardiso.jl](https://github.com/JuliaSparse/Pardiso.jl/)
312+
313+
External packages providing solvers for iterative solution of eigensystems and singular value decompositions:
314+
1. [ArnoldiMethods.jl](https://github.com/JuliaLinearAlgebra/ArnoldiMethod.jl)
315+
2. [KrylovKit](https://github.com/Jutho/KrylovKit.jl)
316+
3. [Arpack.jl](https://github.com/JuliaLinearAlgebra/Arpack.jl)
317+
318+
External packages for working with graphs:
319+
1. [Graphs.jl](https://github.com/JuliaGraphs/Graphs.jl)

docs/src/solvers.md

Lines changed: 0 additions & 44 deletions
This file was deleted.

src/solvers/cholmod.jl

Lines changed: 121 additions & 27 deletions
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
@@ -795,7 +795,7 @@ function ssmult(A::Sparse{Tv1, Ti1}, B::Sparse{Tv2, Ti2}, stype::Integer,
795795
A, B = convert.(Sparse{promote_type(Tv1, Tv2), promote_type(Ti1, Ti2)}, (A, B))
796796
return ssmult(A, B, stype, values, sorted)
797797
end
798-
function horzcat(A::Sparse{Tv1, Ti1}, B::Sparse{Tv2, Ti2}, values::Bool) where
798+
function horzcat(A::Sparse{Tv1, Ti1}, B::Sparse{Tv2, Ti2}, values::Bool) where
799799
{Tv1<:VRealTypes, Tv2<:VRealTypes, Ti1, Ti2}
800800
A, B = convert.(Sparse{promote_type(Tv1, Tv2), promote_type(Ti1, Ti2)}, (A, B))
801801
return horzcat(A, B, values)
@@ -809,7 +809,7 @@ function sdmult!(A::Sparse{Tv1, Ti}, transpose::Bool,
809809
A, X = convert(Sparse{Tv3, Ti}, A), convert(Dense{Tv3}, X)
810810
return sdmult!(A, transpose, α, β, X, Y)
811811
end
812-
function vertcat(A::Sparse{Tv1, Ti1}, B::Sparse{Tv2, Ti2}, values::Bool) where
812+
function vertcat(A::Sparse{Tv1, Ti1}, B::Sparse{Tv2, Ti2}, values::Bool) where
813813
{Tv1<:VRealTypes, Ti1, Tv2<:VRealTypes, Ti2}
814814
A, B = convert.(Sparse{promote_type(Tv1, Tv2), promote_type(Ti1, Ti2)}, (A, B))
815815
return vertcat(A, B, values)
@@ -895,7 +895,7 @@ function Dense(A::StridedVecOrMatInclAdjAndTrans)
895895
return Dense{T}(A)
896896
end
897897
# Don't always promote to Float64 now that we have Float32 support.
898-
Dense(A::StridedVecOrMatInclAdjAndTrans{T}) where
898+
Dense(A::StridedVecOrMatInclAdjAndTrans{T}) where
899899
{T<:Union{Float16, ComplexF16, Float32, ComplexF32}} = Dense{promote_type(T, Float32)}(A)
900900

901901

@@ -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},
@@ -1055,8 +1081,8 @@ Sparse(A::Hermitian{Tv, SparseMatrixCSC{Tv,Ti}}) where {Tv, Ti} =
10551081
Sparse{promote_type(Tv, Float64), Ti <: ITypes ? Ti : promote_type(Ti, Int)}(
10561082
A.data, A.uplo == 'L' ? -1 : 1
10571083
)
1058-
Sparse(A::Hermitian{Tv, SparseMatrixCSC{Tv,Ti}}) where
1059-
{Tv<:Union{Float16, Float32, ComplexF32, ComplexF16}, Ti} =
1084+
Sparse(A::Hermitian{Tv, SparseMatrixCSC{Tv,Ti}}) where
1085+
{Tv<:Union{Float16, Float32, ComplexF32, ComplexF16}, Ti} =
10601086
Sparse{promote_type(Float32, Tv), Ti <: ITypes ? Ti : promote_type(Ti, Int)}(
10611087
A.data, A.uplo == 'L' ? -1 : 1
10621088
)
@@ -1076,7 +1102,7 @@ function Base.convert(::Type{Sparse{Tnew, Inew}}, A::Sparse{Tv, Ti}) where {Tnew
10761102
a = unsafe_load(typedpointer(A))
10771103
S = allocate_sparse(a.nrow, a.ncol, a.nzmax, Bool(a.sorted), Bool(a.packed), a.stype, Tnew, Inew)
10781104
s = unsafe_load(typedpointer(S))
1079-
1105+
10801106
ap = unsafe_wrap(Array, a.p, (a.ncol + 1,), own = false)
10811107
sp = unsafe_wrap(Array, s.p, (s.ncol + 1,), own = false)
10821108
copyto!(sp, ap)
@@ -1376,7 +1402,7 @@ end
13761402

13771403
## Multiplication
13781404
(*)(A::Sparse, B::Sparse) = ssmult(A, B, 0, true, true)
1379-
(*)(A::Sparse, B::Dense) = sdmult!(A, false, 1., 0., B,
1405+
(*)(A::Sparse, B::Dense) = sdmult!(A, false, 1., 0., B,
13801406
zeros(size(A, 1), size(B, 2), promote_type(eltype(A), eltype(B)))
13811407
)
13821408
(*)(A::Sparse, B::VecOrMat) = (*)(A, Dense(B))
@@ -1413,7 +1439,7 @@ function *(adjA::Adjoint{<:Any,<:Sparse}, B::Sparse)
14131439
end
14141440

14151441
*(adjA::Adjoint{<:Any,<:Sparse}, B::Dense) = (
1416-
A = parent(adjA); sdmult!(A, true, 1., 0., B,
1442+
A = parent(adjA); sdmult!(A, true, 1., 0., B,
14171443
zeros(size(A, 2), size(B, 2), promote_type(eltype(A), eltype(B))))
14181444
)
14191445
*(adjA::Adjoint{<:Any,<:Sparse}, B::VecOrMat) = adjA * Dense(B)
@@ -1423,25 +1449,33 @@ end
14231449

14241450
## Compute that symbolic factorization only
14251451
function symbolic(A::Sparse{<:VTypes, Ti};
1426-
perm::Union{Nothing,AbstractVector{<:Integer}}=nothing,
1427-
postorder::Bool=isnothing(perm)||isempty(perm), userperm_only::Bool=true) where Ti
1452+
perm::Union{Nothing,AbstractVector{<:Integer}}=nothing,
1453+
postorder::Bool=isnothing(perm)||isempty(perm),
1454+
userperm_only::Bool=true,
1455+
nested_dissection::Bool=false) where Ti
14281456

14291457
sA = unsafe_load(pointer(A))
14301458
sA.stype == 0 && throw(ArgumentError("sparse matrix is not symmetric/Hermitian"))
14311459

1432-
@cholmod_param postorder = postorder begin
1433-
if perm === nothing || isempty(perm) # TODO: deprecate empty perm
1434-
return analyze(A)
1435-
else # user permutation provided
1436-
if userperm_only # use perm even if it is worse than AMD
1437-
@cholmod_param nmethods = 1 begin
1460+
# The default is to just use AMD. Use nested dissection only if explicitly asked for.
1461+
# https://github.com/JuliaSparse/SparseArrays.jl/issues/548
1462+
# https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/26ababc7f3af725c5fb9168a1b94850eab74b666/CHOLMOD/Include/cholmod.h#L555-L574
1463+
@cholmod_param nmethods = (nested_dissection ? 0 : 2) begin
1464+
@cholmod_param postorder = postorder begin
1465+
if perm === nothing || isempty(perm) # TODO: deprecate empty perm
1466+
return analyze(A)
1467+
else # user permutation provided
1468+
if userperm_only # use perm even if it is worse than AMD
1469+
@cholmod_param nmethods = 1 begin
1470+
return analyze_p(A, Ti[p-1 for p in perm])
1471+
end
1472+
else
14381473
return analyze_p(A, Ti[p-1 for p in perm])
14391474
end
1440-
else
1441-
return analyze_p(A, Ti[p-1 for p in perm])
14421475
end
14431476
end
14441477
end
1478+
14451479
end
14461480

14471481
function cholesky!(F::Factor{Tv}, A::Sparse{Tv};
@@ -1467,7 +1501,7 @@ See also [`cholesky`](@ref).
14671501
14681502
!!! note
14691503
This method uses the CHOLMOD library from SuiteSparse, which only supports
1470-
real or complex types in single or double precision.
1504+
real or complex types in single or double precision.
14711505
Input matrices not of those element types will
14721506
be converted to these types as appropriate.
14731507
"""
@@ -1587,8 +1621,8 @@ true
15871621
15881622
!!! note
15891623
This method uses the CHOLMOD[^ACM887][^DavisHager2009] library from [SuiteSparse](https://github.com/DrTimothyAldenDavis/SuiteSparse).
1590-
CHOLMOD only supports real or complex types in single or double precision.
1591-
Input matrices not of those element types will be
1624+
CHOLMOD only supports real or complex types in single or double precision.
1625+
Input matrices not of those element types will be
15921626
converted to these types as appropriate.
15931627
15941628
Many other functions from CHOLMOD are wrapped but not exported from the
@@ -1633,8 +1667,8 @@ have the type tag, it must still be symmetric or Hermitian.
16331667
See also [`ldlt`](@ref).
16341668
16351669
!!! note
1636-
This method uses the CHOLMOD library from [SuiteSparse](https://github.com/DrTimothyAldenDavis/SuiteSparse),
1637-
which only supports real or complex types in single or double precision.
1670+
This method uses the CHOLMOD library from [SuiteSparse](https://github.com/DrTimothyAldenDavis/SuiteSparse),
1671+
which only supports real or complex types in single or double precision.
16381672
Input matrices not of those element types will
16391673
be converted to these types as appropriate.
16401674
"""
@@ -1695,7 +1729,7 @@ it should be a permutation of `1:size(A,1)` giving the ordering to use
16951729
16961730
!!! note
16971731
This method uses the CHOLMOD[^ACM887][^DavisHager2009] library from [SuiteSparse](https://github.com/DrTimothyAldenDavis/SuiteSparse).
1698-
CHOLMOD only supports real or complex types in single or double precision.
1732+
CHOLMOD only supports real or complex types in single or double precision.
16991733
Input matrices not of those element types will
17001734
be converted to these types as appropriate.
17011735
@@ -1767,7 +1801,7 @@ See also [`lowrankupdate!`](@ref), [`lowrankdowndate`](@ref), [`lowrankdowndate!
17671801
"""
17681802
lowrankupdate(F::Factor{Tv}, V::AbstractArray{Tv2}) where {Tv, Tv2} =
17691803
lowrankupdate!(
1770-
change_xdtype(F, promote_type(Tv, Tv2)),
1804+
change_xdtype(F, promote_type(Tv, Tv2)),
17711805
convert(AbstractArray{promote_type(Tv, Tv2)}, V)
17721806
)
17731807

@@ -1782,7 +1816,7 @@ See also [`lowrankdowndate!`](@ref), [`lowrankupdate`](@ref), [`lowrankupdate!`]
17821816
"""
17831817
lowrankdowndate(F::Factor{Tv}, V::AbstractArray{Tv2}) where {Tv, Tv2} =
17841818
lowrankdowndate!(
1785-
change_xdtype(F, promote_type(Tv, Tv2)),
1819+
change_xdtype(F, promote_type(Tv, Tv2)),
17861820
convert(AbstractArray{promote_type(Tv, Tv2)}, V)
17871821
)
17881822

@@ -1905,6 +1939,66 @@ const AbstractSparseVecOrMatInclAdjAndTrans = Union{AbstractSparseVecOrMat, AdjO
19051939
throw(ArgumentError("self-adjoint sparse system solve not implemented for sparse rhs B," *
19061940
" consider to convert B to a dense array"))
19071941

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+
19082002
## Other convenience methods
19092003
function diag(F::Factor{Tv, Ti}) where {Tv, Ti}
19102004
f = unsafe_load(typedpointer(F))

0 commit comments

Comments
 (0)