{"id":18011266,"url":"https://github.com/sloisel/sparsesparse","last_synced_at":"2025-04-04T13:42:39.288Z","repository":{"id":205104974,"uuid":"713428117","full_name":"sloisel/SparseSparse","owner":"sloisel","description":"Sparse solver for sparse right-hand-sides in Julia","archived":false,"fork":false,"pushed_at":"2024-05-25T13:26:01.000Z","size":27,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-09T23:27:28.061Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sloisel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-02T13:58:27.000Z","updated_at":"2024-08-04T23:32:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"a12c997b-382d-4efd-a029-ee90ca86ef04","html_url":"https://github.com/sloisel/SparseSparse","commit_stats":null,"previous_names":["sloisel/sparsesparse"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloisel%2FSparseSparse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloisel%2FSparseSparse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloisel%2FSparseSparse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloisel%2FSparseSparse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sloisel","download_url":"https://codeload.github.com/sloisel/SparseSparse/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247188814,"owners_count":20898585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-30T03:08:46.986Z","updated_at":"2025-04-04T13:42:39.270Z","avatar_url":"https://github.com/sloisel.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SparseSparse\n\n### Author: Sébastien Loisel\n\n## Installation\n```julia\nusing Pkg; Pkg.add(url=\"https://github.com/sloisel/SparseSparse\")\n```\n\n# Introduction\n\nWe say that a matrix $A$ is **doubly sparse** if both $A$ and $A^{-1}$ are sparse. The class of doubly sparse matrices includes all matrices of the form $P(\\prod B_k) Q$ where $P,Q$ are permutations and $B_k$ are block diagonal. `SparseSparse` is a package that inverts sparse matrices with sparse inverses, or otherwise solve sparse linear problems with sparse right-hand-sides.\n\nWith stock Julia, here is what happens if you try to invert a sparse matrix:\n\n```julia\njulia\u003e using LinearAlgebra, SparseArrays\n       A = sparse([2.0  3.0  0.0  0.0\n                   4.0  5.0  0.0  0.0\n                   0.0  0.0  6.0  7.0\n                   0.0  0.0  8.0  9.0])\n       inv(A)\nThe inverse of a sparse matrix can often be dense and can cause the computer to run out of memory[...]\n\nStacktrace:\n [1] error(s::String)\n   @ Base ./error.jl:35\n[...]\n```\n\nThe above matrix `A` is block-diagonal so it has a sparse inverse. The `SparseSparse` package overrides `Base.inv` so that it inverts sparse matrices and produces a sparse inverse, as follows.\n```julia\njulia\u003e using SparseSparse\n       inv(A)\n\n4×4 SparseMatrixCSC{Float64, Int64} with 8 stored entries:\n -2.5   1.5    ⋅     ⋅ \n  2.0  -1.0    ⋅     ⋅ \n   ⋅     ⋅   -4.5   3.5\n   ⋅     ⋅    4.0  -3.0\n```\n \nThe implementation is based on `SparseSparse.Factorization`:\n```julia\njulia\u003e Factorization(A)\nSparseSparse.Factorization(...)\n```\nThe `Factorization` object is as follows:\n```julia\nstruct Factorization{Tv,Ti\u003c:Integer} \n    L::Union{Missing,SparseMatrixCSC{Tv,Ti}}\n    U::Union{Missing,SparseMatrixCSC{Tv,Ti}}\n    p::Union{Missing,Vector{Ti}}\n    q::Union{Missing,Vector{Ti}}\nend\n```\nFields `L` and `U` are sparse lower and upper triangular matrices, and vectors `p` and `q` are permutations.\nAll these fields are optional and may take on the value `missing` when that particular term is omitted. Factorizations can be used to solve linear problems via\n```julia\nfunction Base.:\\(A::Factorization, B::SparseMatrixCSC)\n```\n\nThe underlying LU decomposition is computed using the stdlib's `lu` (or `ldlt` or `cholesky`). The problem is that stdlib refuses to compute `L\\B` when `B` is itself sparse. The core of module `SparseSparse` is the following function:\n\n```julia\nfunction solve(L::SparseMatrixCSC{Tv,Ti},B::SparseMatrixCSC{Tv,Ti};solvemode=detect,numthreads=min(B.n,nthreads())) where {Tv,Ti\u003c:Integer}\n```\n\nThis function is able to solve lower or upper triangular sparse systems with sparse right-hand-sides. The algorithm is similar to the one described in Tim Davis's book and implemented in SuiteSparse [here](https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/dev/CXSparse/Source/cs_spsolve.c). The `SparseSparse` implementation also uses multithreading to speed up the solution time. The parameter `solvemode` should be either `lower=1`, `upper=2` or `detect=3`.\n\n# Benchmarks\n\nHere is a numerical experiment, calculating the inverse of a **doubly sparse matrix.**\n\n```julia\njulia\u003e using Random\n       A = blockdiag([sparse(randn(10,10)) for k=1:100]...)\n       P = randperm(1000)\n       Q = randperm(1000)\n       A = A[P,Q]\n1000×1000 SparseMatrixCSC{Float64, Int64} with 10000 stored entries:\n[...]\n\njulia\u003e using BenchmarkTools\n       @benchmark inv(Matrix(A))\nBenchmarkTools.Trial: 155 samples with 1 evaluation.\n Range (min … max):  26.345 ms … 42.085 ms  ┊ GC (min … max): 0.00% … 13.34%\n Time  (median):     31.265 ms              ┊ GC (median):    0.00%\n Time  (mean ± σ):   32.239 ms ±  3.650 ms  ┊ GC (mean ± σ):  6.67% ±  8.61%\n\n        ▃  ▁█▆ █▃▁▄▁▄ ▄            ▄ ▃▁▃  ▁                    \n  ▇▄▄▄▆▆█▇▇███▆██████▇█▇▇▄▄▆▆▆▇▄▄▄▁█▆███▄▆█▄▆▄▄▆▆▇▁▄▇▆▁▁▁▄▁▁▄ ▄\n  26.3 ms         Histogram: frequency by time        40.9 ms \u003c\n\n Memory estimate: 15.76 MiB, allocs estimate: 7.\n\njulia\u003e @benchmark(inv(A))\nBenchmarkTools.Trial: 1859 samples with 1 evaluation.\n Range (min … max):  1.744 ms … 13.188 ms  ┊ GC (min … max):  0.00% … 67.60%\n Time  (median):     2.105 ms              ┊ GC (median):     0.00%\n Time  (mean ± σ):   2.670 ms ±  1.735 ms  ┊ GC (mean ± σ):  11.39% ± 13.40%\n\n  ▆█▇▆▅▃▁                                                     \n  ██████████████▇▇▆▇▄▅▁▄▄▄▄▄▅▄▅▄▁▅▅▄▅▆▅▆▆▆▄▆▆▆▇▅▇▄▆▅▄▄▆▄▄▅▆▄ █\n  1.74 ms      Histogram: log(frequency) by time     10.6 ms \u003c\n\n Memory estimate: 3.69 MiB, allocs estimate: 585.\n```\n\nIn the case of this 1000x1000 matrix, the `SparseSparse` method is approximately 12x faster and requires 76% less memory, compared to using dense algorithms.\n\n# Applications\n\nWe now briefly sketch an application of `SparseSparse` on Woodbury and $H$ matrices. Note that these matrix types are not part of the `SparseSparse` module.\n\nThe [Woodbury matrix identity](https://en.wikipedia.org/wiki/Woodbury_matrix_identity) states that the inverse of $A+UCV$ is $$(A+UCV)^{-1} = A^{-1}-A^{-1}U(C^{-1}+VA^{-1}U)^{-1}VA^{-1}.$$ We can implement this in Julia as\n```julia\nstruct Woodbury A; U; C; V end\n```\nThen, the inverse of a Woodbury matrix is\n```julia\nfunction Base.inv(X::Woodbury)\n    Ainv = inv(X.A)\n    Woodbury(Ainv,Ainv*X.U,-inv(inv(X.C)+X.V*Ainv*X.U),X.V*Ainv) \nend\n```\nSuch matrices have a complete algebra of operations; we can add, subtract and multiply them, and thanks to the Woodbury identity, we can also invert them.\n\nThis is related to Hierarchical matrices (see the book by Hackbusch). Consider a matrix\n\n$$\\left[\\begin{array}{cc}\nX \u0026 Y \\\\\\ Z \u0026 W\n\\end{array}\n\\right]\n\\approx\n\\left[\\begin{array}{cc}\nX \u0026 U_1 S_1 V_1 \\\\\\ U_2 S_2 V_2 \u0026 W\n\\end{array}\n\\right].$$\n\nHere, $Y \\approx U_1S_1V_1$ is a low rank approximation obtained by sparse SVD, e.g. using `Arpack`. This approximation can efficiently be represented by a Woodbury decomposition. The decomposition is applied recursively to sub-blocks $X$ and $W$, resulting in an $H$-matrix. Here's an example with the 1d Laplacian:\n\n```julia\njulia\u003e n = 4\n       A = spdiagm(0=\u003e2*ones(n),1=\u003e-ones(n-1),-1=\u003e-ones(n-1))\n       H = hmat(A;r=1)\n       Matrix(H)\n4×4 Matrix{Float64}:\n  2.0  -1.0   0.0   0.0\n -1.0   2.0  -1.0  -1.11022e-16\n  0.0  -1.0   2.0  -1.0\n  0.0   0.0  -1.0   2.0\n```\nIn this case, the $H$-matrix representation of $A$ happens to be exact.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsloisel%2Fsparsesparse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsloisel%2Fsparsesparse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsloisel%2Fsparsesparse/lists"}