Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite using Derive and new interface #3

Merged
merged 22 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 10 additions & 17 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,39 +1,32 @@
name = "SparseArraysBase"
uuid = "0d5efcca-f356-4864-8770-e1ed8d78f208"
authors = ["ITensor developers <[email protected]> and contributors"]
version = "0.1.0"
version = "0.2.0"

[deps]
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
BroadcastMapConversion = "4a4adec5-520f-4750-bb37-d5e66b4ddeb2"
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Derive = "a07dfc7f-7d04-4eb5-84cc-a97f051f655a"
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
NestedPermutedDimsArrays = "2c2a8ec4-3cfc-4276-aa3e-1307b4294e58"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138"
VectorInterface = "409d34a3-91d5-4945-b6ec-7529ddf182d8"

[compat]
Accessors = "0.1.38"
Adapt = "4.1.1"
Aqua = "0.8.9"
ArrayLayouts = "1.10.4"
Compat = "4.16.0"
ArrayLayouts = "1.11.0"
BroadcastMapConversion = "0.1.0"
Derive = "0.3.0"
Dictionaries = "0.4.3"
LinearAlgebra = "1.10"
MacroTools = "0.5.13"
SparseArrays = "1.10"
SafeTestsets = "0.1"
Suppressor = "0.2"
Test = "1.10"
VectorInterface = "0.5.0"
julia = "1.10"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Aqua", "Test"]
test = ["Aqua", "Test", "Suppressor", "SafeTestsets"]
121 changes: 119 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,127 @@ julia> Pkg.add("SparseArraysBase")
## Examples

````julia
using SparseArraysBase: SparseArraysBase
using SparseArraysBase:
SparseArrayDOK,
SparseMatrixDOK,
SparseVectorDOK,
eachstoredindex,
getstoredindex,
getunstoredindex,
isstored,
setstoredindex!,
setunstoredindex!,
storedlength,
storedpairs,
storedvalues,
zero!
using Test: @test, @test_throws

a = SparseArrayDOK{Float64}(2, 2)
````

AbstractArray interface:

````julia
@test iszero(a)
@test iszero(sum(a))
@test iszero(storedlength(a))

a[1, 2] = 12
@test a == [0 12; 0 0]
@test a[1, 1] == 0
@test a[2, 1] == 0
@test a[1, 2] == 12
@test a[2, 2] == 0
````

SparseArraysBase interface:

````julia
using Dictionaries: IndexError
@test issetequal(eachstoredindex(a), [CartesianIndex(1, 2)])
@test getstoredindex(a, 1, 2) == 12
@test_throws IndexError getstoredindex(a, 1, 1)
@test getunstoredindex(a, 1, 1) == 0
@test getunstoredindex(a, 1, 2) == 0
@test !isstored(a, 1, 1)
@test isstored(a, 1, 2)
@test setstoredindex!(copy(a), 21, 1, 2) == [0 21; 0 0]
@test_throws IndexError setstoredindex!(copy(a), 21, 2, 1)
@test setunstoredindex!(copy(a), 21, 1, 2) == [0 21; 0 0]
@test storedlength(a) == 1
@test issetequal(storedpairs(a), [CartesianIndex(1, 2) => 12])
@test issetequal(storedvalues(a), [12])
@test sum(a) == 12
@test isreal(a)
@test !iszero(a)
@test mapreduce(x -> 2x, +, a) == 24
````

AbstractArray functionality:

````julia
b = a .+ 2 .* a'
@test b isa SparseMatrixDOK{Float64}
@test b == [0 12; 24 0]
@test storedlength(b) == 2
@test sum(b) == 36
@test isreal(b)
@test !iszero(b)
@test mapreduce(x -> 2x, +, b) == 72

b = permutedims(a, (2, 1))
@test b isa SparseMatrixDOK{Float64}
@test b[1, 1] == a[1, 1]
@test b[2, 1] == a[1, 2]
@test b[1, 2] == a[2, 1]
@test b[2, 2] == a[2, 2]

b = a * a'
@test b isa SparseMatrixDOK{Float64}
@test b == [144 0; 0 0]
@test storedlength(b) == 1
````

Examples go here.
Second column.

````julia
b = a[1:2, 2]
@test b isa SparseVectorDOK{Float64}
@test b == [12, 0]
@test storedlength(b) == 1

a = SparseArrayDOK{Float64}(2, 2)
a .= 2
for I in eachindex(a)
@test a[I] == 2
end
@test storedlength(a) == length(a)

a = SparseArrayDOK{Float64}(2, 2)
fill!(a, 2)
for I in eachindex(a)
@test a[I] == 2
end
@test storedlength(a) == length(a)

a = SparseArrayDOK{Float64}(2, 2)
fill!(a, 0)
@test iszero(a)
@test iszero(storedlength(a))

a = SparseArrayDOK{Float64}(2, 2)
a[1, 2] = 12
zero!(a)
@test iszero(a)
@test iszero(storedlength(a))

a = SparseArrayDOK{Float64}(2, 2)
a[1, 2] = 12
b = zero(a)
@test iszero(b)
@test iszero(storedlength(b))
````

---

Expand Down
125 changes: 1 addition & 124 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,124 +1 @@
# To-do

- Remove Compat.jl from dependencies and test dependencies.
- Unregistered dependencies: BroadcastMapConversion, NestedPermutedDimsArrays, TypeParameterAccessors.
- Create SparseArraysBaseLinearAlgebraExt, SparseArraysBaseNestedPermutedDimsArraysExt, SparseArraysBaseVectorInterfaceExt.
- Change [sources] entries from paths to urls.

# SparseArraysBase

SparseArraysBase is a package that aims to expand on the sparse array functionality that is currently in Julia Base.
While SparseArrays.jl is centered mostly around `SparseMatrixCSC` and the SuiteSparse library, here we wish to broaden the scope a bit, and consider generic sparse arrays.
Abstractly, the mental model can be considered as a storage object that holds the stored values, and a bijection between the array indices and the indices of the storage.
For now, we focus on providing efficient implementations of Dictionary of Key (DOK) type sparse storage formats, but may expand upon this in the future.
As a result, for typical linear algebra routines, we still expect `SparseMatrixCSC` to be the object of choice.

The design consists of roughly three components:
- `AbstractSparseArray` interface functions
- Overloaded Julia base methods
- `SparseArrayDOK` struct that implements this

## AbstractSparseArray

The first part consists of typical functions that are useful in the context of sparse arrays.
The minimal interface, which enables the usage of the rest of this package, consists of the following functions:

| Signature | Description | Default |
|-----------|-------------|---------|
| `sparse_storage(a::AbstractArray)` | Returns the storage object of the sparse array | `a` |
| `storage_index_to_index(a::AbstractArray, I)` | Converts a storage index to an array index | `I` |
| `index_to_storage_index(a::AbstractArray, I)` | Converts an array index to a storage index | `I` |

Using these primitives, several convenience functions are defined to facilitate the writing of sparse array algorithms.

| Signature | Description | Default |
|-----------|-------------|---------|
| `storage_indices(a)` | Returns the indices of the storage | `eachindex(sparse_storage(a))` |
| `stored_indices(a)` | Returns the indices of the stored values | `Iterators.map(Base.Fix1(storage_index_to_index, a), storage_indices(a))` |
| `stored_length(a)` | Returns the number of stored values | `length(storage_indices(a))` |

<!-- TODO: `getindex!`, `increaseindex!`, `sparse_map`, expose "zero" functionality? -->

Interesting to note here is that the design is such that we can define sparse arrays without having to subtype `AbstractSparseArray`.
To achieve this, each function `f` is defined in terms of `sparse_f`, rather than directly overloading `f`.
<!--
TODO:
In order to opt-in to the sparse array functionality, one needs to dispatch the functions through `sparse_f` instead of `f`.
For convenience, you can automatically dispatch all functions through `sparse_f` by using the following macro:

```julia
@abstractsparsearray MySparseArrayType
```
-->

## Overloaded Julia base methods

The second part consists of overloading Julia base methods to work with sparse arrays.
In particular, specialised implementations exist for the following functions:

- `sparse_similar`
- `sparse_reduce`
- `sparse_map`
- `sparse_map!`
- `sparse_all`
- `sparse_any`
- `sparse_isequal`
- `sparse_fill!`
- `sparse_zero`, `sparse_zero!`, `sparse_iszero`
- `sparse_one`, `sparse_one!`, `sparse_isone`
- `sparse_reshape`, `sparse_reshape!`
- `sparse_cat`, `sparse_cat!`
- `sparse_copy!`, `sparse_copyto!`
- `sparse_permutedims`, `sparse_permutedims!`
- `sparse_mul!`, `sparse_dot`

## SparseArrayDOK

Finally, the `SparseArrayDOK` struct is provided as a concrete implementation of the `AbstractSparseArray` interface.
It is a dictionary of keys (DOK) type sparse array, which stores the values in a `Dictionaries.jl` dictionary, and maps the indices to the keys of the dictionary.
This model is particularly useful for sparse arrays with a small number of non-zero elements, or for arrays that are constructed incrementally, as it boasts fast random accesses and insertions.
The drawback is that sequential iteration is slower than for other sparse array types, leading to slower linear algebra operations.
For the purposes of `SparseArraysBase`, this struct will serve as the canonical example of a sparse array, and will be returned by default when new sparse arrays are created.

One particular feature of `SparseArrayDOK` is that it can be used in cases where the non-stored entries have to be constructed in a non-trivial way.
Typically, sparse arrays use `zero(eltype(a))` to construct the non-stored entries, but this is not always sufficient.
A concrete example is found in `BlockSparseArrays.jl`, where initialization of the non-stored entries requires the construction of a block of zeros of appropriate size.

<!-- TODO: update TODOs -->

## TODO
Still need to implement `Base` functions:
```julia
[x] sparse_zero(a::AbstractArray) = similar(a)
[x] sparse_iszero(a::AbstractArray) = iszero(nonzero_length(a)) # Uses `all`, make `sparse_all`?
[x] sparse_one(a::AbstractArray) = ...
[x] sparse_isreal(a::AbstractArray) = ... # Uses `all`, make `sparse_all`?
[x] sparse_isequal(a1::AbstractArray, a2::AbstractArray) = ...
[x] sparse_conj!(a::AbstractArray) = conj!(nonzeros(a))
[x] sparse_reshape(a::AbstractArray, dims) = ...
[ ] sparse_all(f, a::AbstractArray) = ...
[ ] sparse_getindex(a::AbstractArray, 1:2, 2:3) = ... # Slicing
```
`LinearAlgebra` functions:
```julia
[ ] sparse_mul!
[ ] sparse_lmul!
[ ] sparse_ldiv!
[ ] sparse_rdiv!
[ ] sparse_axpby!
[ ] sparse_axpy!
[ ] sparse_norm
[ ] sparse_dot/sparse_inner
[ ] sparse_adoint!
[ ] sparse_transpose!

# Using conversion to `SparseMatrixCSC`:
[ ] sparse_qr
[ ] sparse_eigen
[ ] sparse_svd
```
`TensorAlgebra` functions:
```julia
[ ] add!
[ ] contract!
```
- Updates for latest Derive.
4 changes: 1 addition & 3 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
[deps]
BroadcastMapConversion = "4a4adec5-520f-4750-bb37-d5e66b4ddeb2"
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
NestedPermutedDimsArrays = "2c2a8ec4-3cfc-4276-aa3e-1307b4294e58"
SparseArraysBase = "0d5efcca-f356-4864-8770-e1ed8d78f208"
TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138"
5 changes: 2 additions & 3 deletions examples/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[deps]
BroadcastMapConversion = "4a4adec5-520f-4750-bb37-d5e66b4ddeb2"
NestedPermutedDimsArrays = "2c2a8ec4-3cfc-4276-aa3e-1307b4294e58"
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
SparseArraysBase = "0d5efcca-f356-4864-8770-e1ed8d78f208"
TypeParameterAccessors = "7e5a90cf-f82e-492e-a09b-e3e26432c138"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Loading
Loading