Skip to content

Commit

Permalink
libSparse direct solving methods and matrix arithmetic
Browse files Browse the repository at this point in the history
  • Loading branch information
luke-kiernan committed Jan 22, 2025
1 parent 9874fe4 commit 1e4b9f8
Show file tree
Hide file tree
Showing 7 changed files with 947 additions and 1 deletion.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.4.1"
[deps]
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[compat]
julia = "1.9"
Expand Down
5 changes: 5 additions & 0 deletions src/AppleAccelerate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ if Sys.isapple()
include("Util.jl")
include("Array.jl")
include("DSP.jl")
include("libSparse/wrappers.jl")
include("libSparse/AASparseMatrices.jl")
include("libSparse/AAFactorizations.jl")
export AASparseMatrix, muladd!
export AAFactorization, solve, solve!, factor!
end

end # module
89 changes: 89 additions & 0 deletions src/libSparse/AAFactorizations.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
@doc """Factorization object.
Create via `f = AAFactorization(A::SparseMatrixCSC{T, Int64})`. Calls to `solve`,
`ldiv`, and their in-place versions require explicitly passing in the
factorization object as the first argument. On construction, the struct stores a
placeholder yet-to-be-factored object: the factorization is computed upon the first call
to `solve`, or by explicitly calling `factor!`. If the matrix is symmetric, it defaults to
a Cholesky factorization; otherwise, it defaults to QR.
"""
mutable struct AAFactorization{T<:vTypes} <: LinearAlgebra.Factorization{T}
matrixObj::AASparseMatrix{T}
_factorization::SparseOpaqueFactorization{T}
end

# returns an AAFactorization containing A and a dummy "yet-to-be-factored" factorization.
function AAFactorization(A::AASparseMatrix{T}) where T<:vTypes
obj = AAFactorization(A, SparseOpaqueFactorization(T))
function cleanup(aa_fact)

Check warning on line 18 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L16-L18

Added lines #L16 - L18 were not covered by tests
# If it's yet-to-be-factored, then there's nothing to release
if !(aa_fact._factorization.status in (SparseYetToBeFactored,

Check warning on line 20 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L20

Added line #L20 was not covered by tests
SparseStatusReleased))
SparseCleanup(aa_fact._factorization)

Check warning on line 22 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L22

Added line #L22 was not covered by tests
end
end
return finalizer(cleanup, obj)

Check warning on line 25 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L25

Added line #L25 was not covered by tests
end

LinearAlgebra.factorize(A::AASparseMatrix{T}) where T<:vTypes = AAFactorization(A)

Check warning on line 28 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L28

Added line #L28 was not covered by tests

# julia's LinearAlgebra module doesn't provide similar constructors.
AAFactorization(M::SparseMatrixCSC{T, Int64}) where T<:vTypes =

Check warning on line 31 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L31

Added line #L31 was not covered by tests
AAFactorization(AASparseMatrix(M))

# easiest way to make this follow the defaults and naming conventions of LinearAlgebra?
# TODO: add tests for the different kinds of factorizations, beyond QR.
function factor!(aa_fact::AAFactorization{T},

Check warning on line 36 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L36

Added line #L36 was not covered by tests
kind::SparseFactorization_t = SparseFactorizationTBD) where T<:vTypes
if aa_fact._factorization.status == SparseYetToBeFactored
if kind == SparseFactorizationTBD

Check warning on line 39 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L38-L39

Added lines #L38 - L39 were not covered by tests
# so far I'm only dealing with ordinary and symmetric
kind = issymmetric(aa_fact.matrixObj) ? SparseFactorizationCholesky :

Check warning on line 41 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L41

Added line #L41 was not covered by tests
SparseFactorizationQR
end
aa_fact._factorization = SparseFactor(kind, aa_fact.matrixObj.matrix)
if aa_fact._factorization.status == SparseMatrixIsSingular

Check warning on line 45 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L44-L45

Added lines #L44 - L45 were not covered by tests
# throw a SingularException? Factoring a singular matrix usually does not
# make it this far: the call to SparseFactor throws an error.
throw(ErrorException("The matrix is singular."))
elseif aa_fact._factorization.status == SparseStatusFailed
throw(ErrorException("Factorization failed: check that the matrix"

Check warning on line 50 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L48-L50

Added lines #L48 - L50 were not covered by tests
* " has the correct properties for the factorization."))
elseif aa_fact._factorization.status != SparseStatusOk
throw(ErrorException("Something went wrong internally. Error type: "

Check warning on line 53 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L52-L53

Added lines #L52 - L53 were not covered by tests
* String(aa_fact._factorization.status)))
end
end
end

function solve(aa_fact::AAFactorization{T}, b::StridedVecOrMat{T}) where T<:vTypes
@assert size(aa_fact.matrixObj)[2] == size(b, 1)
factor!(aa_fact)
x = Array{T}(undef, size(aa_fact.matrixObj)[2], size(b)[2:end]...)
SparseSolve(aa_fact._factorization, b, x)
return x

Check warning on line 64 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L59-L64

Added lines #L59 - L64 were not covered by tests
end

function solve!(aa_fact::AAFactorization{T}, xb::StridedVecOrMat{T}) where T<:vTypes
@assert (xb isa StridedVector) ||

Check warning on line 68 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L67-L68

Added lines #L67 - L68 were not covered by tests
(size(aa_fact.matrixObj)[1] == size(aa_fact.matrixObj)[2]) "Can't in-place " *
"solve: x and b are different sizes and Julia cannot resize a matrix."
factor!(aa_fact)
SparseSolve(aa_fact._factorization, xb)
return xb # written in imitation of KLU.jl, which also returns

Check warning on line 73 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L71-L73

Added lines #L71 - L73 were not covered by tests
end

LinearAlgebra.ldiv!(aa_fact::AAFactorization{T}, xb::StridedVecOrMat{T}) where T<:vTypes =

Check warning on line 76 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L76

Added line #L76 was not covered by tests
solve!(aa_fact, xb)

function LinearAlgebra.ldiv!(x::StridedVecOrMat{T},

Check warning on line 79 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L79

Added line #L79 was not covered by tests
aa_fact::AAFactorization{T},
b::StridedVecOrMat{T}) where T<:vTypes
@assert size(aa_fact.matrixObj)[2] == size(b, 1)
factor!(aa_fact)
SparseSolve(aa_fact._factorization, b, x)
return x

Check warning on line 85 in src/libSparse/AAFactorizations.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AAFactorizations.jl#L82-L85

Added lines #L82 - L85 were not covered by tests
end



116 changes: 116 additions & 0 deletions src/libSparse/AASparseMatrices.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@

@doc """Matrix wrapper, containing the Apple sparse matrix struct
and the pointed-to data. Construct from a `SparseMatrixCSC`.
Multiplication (`*`) and multiply-add (`muladd!`) with both
`Vector` and `Matrix` objects are working.
`transpose` creates a new matrix structure with the opposite
transpose flag, that references the same CSC data.
"""
mutable struct AASparseMatrix{T<:vTypes} <: AbstractMatrix{T}
matrix::SparseMatrix{T}
_colptr::Vector{Clong}
_rowval::Vector{Cint}
_nzval::Vector{T}
end

# I use StridedVector here because it allows for views/references,
# so you can do shallow copies: same pointed-to data. Better way?
"""Constructor for advanced usage: col and row here are 0-indexed CSC data.
Could allow for shared `_colptr`, `_rowval`, `_nzval` between multiple
structs via views or references. Currently unused."""
function AASparseMatrix(n::Int, m::Int,

Check warning on line 22 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L22

Added line #L22 was not covered by tests
col::StridedVector{Clong}, row::StridedVector{Cint},
data::StridedVector{T},
attributes::att_type = ATT_ORDINARY) where T<:vTypes
@assert stride(col, 1) == 1 && stride(row, 1) == 1 && stride(data, 1) == 1

Check warning on line 26 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L26

Added line #L26 was not covered by tests
# I'm assuming here that pointer(row) == pointer(_row_inds),
# ie that col, row, and data are passed by reference, not by value.
s = SparseMatrixStructure(n, m, pointer(col),

Check warning on line 29 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L29

Added line #L29 was not covered by tests
pointer(row), attributes, 1)
m = SparseMatrix(s, pointer(data))
return AASparseMatrix(m, col, row, data)

Check warning on line 32 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L31-L32

Added lines #L31 - L32 were not covered by tests
end

function AASparseMatrix(sparseM::SparseMatrixCSC{T, Int64},

Check warning on line 35 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L35

Added line #L35 was not covered by tests
attributes::att_type = ATT_ORDINARY) where T<:vTypes
if issymmetric(sparseM) && attributes == ATT_ORDINARY
return AASparseMatrix(tril(sparseM), ATT_SYMMETRIC | ATT_LOWER_TRIANGLE)
elseif (istril(sparseM) || istriu(sparseM)) && attributes == ATT_ORDINARY
attributes = istril(sparseM) ? ATT_TRI_LOWER : ATT_TRI_UPPER

Check warning on line 40 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L37-L40

Added lines #L37 - L40 were not covered by tests
end
if attributes in (ATT_TRI_LOWER, ATT_TRI_UPPER) &&

Check warning on line 42 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L42

Added line #L42 was not covered by tests
all(diag(sparseM) .== one(eltype(sparseM)))
attributes |= ATT_UNIT_TRIANGULAR

Check warning on line 44 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L44

Added line #L44 was not covered by tests
end
c = Clong.(sparseM.colptr .+ -1)
r = Cint.(sparseM.rowval .+ -1)
vals = copy(sparseM.nzval)
return AASparseMatrix(size(sparseM)..., c, r, vals, attributes)

Check warning on line 49 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L46-L49

Added lines #L46 - L49 were not covered by tests
end

Base.size(M::AASparseMatrix) = (M.matrix.structure.rowCount,

Check warning on line 52 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L52

Added line #L52 was not covered by tests
M.matrix.structure.columnCount)
Base.eltype(M::AASparseMatrix) = eltype(M._nzval)
LinearAlgebra.issymmetric(M::AASparseMatrix) = (M.matrix.structure.attributes &

Check warning on line 55 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L54-L55

Added lines #L54 - L55 were not covered by tests
ATT_KIND_MASK) == ATT_SYMMETRIC
istri(M::AASparseMatrix) = (M.matrix.structure.attributes

Check warning on line 57 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L57

Added line #L57 was not covered by tests
& ATT_KIND_MASK) == ATT_TRIANGULAR
LinearAlgebra.istriu(M::AASparseMatrix) = istri(M) && (MM.matrix.structure.attributes

Check warning on line 59 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L59

Added line #L59 was not covered by tests
& ATT_TRIANGLE_MASK == ATT_LOWER_TRIANGLE)
LinearAlgebra.istril(M::AASparseMatrix) = istri(M) && (MM.matrix.structure.attributes

Check warning on line 61 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L61

Added line #L61 was not covered by tests
& ATT_TRIANGLE_MASK == ATT_UPPER_TRIANGLE)

function Base.getindex(M::AASparseMatrix, i::Int, j::Int)
@assert all((1, 1) .<= (i,j) .<= size(M))
(startCol, endCol) = (M._colptr[j], M._colptr[j+1]-1) .+ 1
rowsInCol = @view M._rowval[startCol:endCol]
ind = searchsortedfirst(rowsInCol, i-1)
if ind <= length(rowsInCol) && rowsInCol[ind] == i-1
return M._nzval[startCol+ind-1]

Check warning on line 70 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L64-L70

Added lines #L64 - L70 were not covered by tests
end
return zero(eltype(M))

Check warning on line 72 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L72

Added line #L72 was not covered by tests
end

function Base.getindex(M::AASparseMatrix, i::Int)
@assert 1 <= i <= size(M)[1]*size(M)[2]
return M[(i-1) % size(M)[1] + 1, div(i-1, size(M)[1]) + 1]

Check warning on line 77 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L75-L77

Added lines #L75 - L77 were not covered by tests
end
# Creates a new structure, referring to the same data,
# but with the transpose flag (in attributes) flipped.
# TODO: untested
Base.transpose(M::AASparseMatrix) = AASparseMatrix(SparseGetTranspose(M.matrix),

Check warning on line 82 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L82

Added line #L82 was not covered by tests
M._colptr, M._rowval, M._nzval)

function Base.:(*)(A::AASparseMatrix{T}, x::StridedVecOrMat{T}) where T<:vTypes
@assert size(x)[1] == size(A)[2]
y = Array{T}(undef, size(A)[1], size(x)[2:end]...)
SparseMultiply(A.matrix, x, y)
return y

Check warning on line 89 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L85-L89

Added lines #L85 - L89 were not covered by tests
end

function Base.:(*)(alpha::T, A::AASparseMatrix{T},

Check warning on line 92 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L92

Added line #L92 was not covered by tests
x::StridedVecOrMat{T}) where T<:vTypes
@assert size(x)[1] == size(A)[2]
y = Array{T}(undef, size(A)[1], size(x)[2:end]...)
SparseMultiply(alpha, A.matrix, x, y)
return y

Check warning on line 97 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L94-L97

Added lines #L94 - L97 were not covered by tests
end

"""
Computes y += A*x in place. Note that this modifies its LAST argument.
"""
function muladd!(A::AASparseMatrix{T}, x::StridedVecOrMat{T},

Check warning on line 103 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L103

Added line #L103 was not covered by tests
y::StridedVecOrMat{T}) where T<:vTypes
@assert size(x) == size(y) && size(x)[1] == size(A)[2]
SparseMultiplyAdd(A.matrix, x, y)

Check warning on line 106 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L105-L106

Added lines #L105 - L106 were not covered by tests
end

"""
Computes y += alpha*A*x in place. Note that this modifies its LAST argument.
"""
function muladd!(alpha::T, A::AASparseMatrix{T},

Check warning on line 112 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L112

Added line #L112 was not covered by tests
x::StridedVecOrMat{T}, y::StridedVecOrMat{T}) where T<:vTypes
@assert size(x) == size(y) && size(x)[1] == size(A)[2]
SparseMultiplyAdd(alpha, A.matrix, x, y)

Check warning on line 115 in src/libSparse/AASparseMatrices.jl

View check run for this annotation

Codecov / codecov/patch

src/libSparse/AASparseMatrices.jl#L114-L115

Added lines #L114 - L115 were not covered by tests
end
Loading

0 comments on commit 1e4b9f8

Please sign in to comment.