From 6d308d8d66a15aa4ad9bc5ced0df3c0f56bcd4fb Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 27 Oct 2026 17:04:08 +0000 Subject: [PATCH 1/4] Fix C .= A .op B broadcasts (#124) f was only set for ^, so other operators threw UndefVarError, and the scalar fallback passed apply! its arguments in the wrong order. Co-Authored-By: Claude Opus 5.5 --- src/operations/broadcasts.jl | 6 ++++-- test/operations/broadcasting.jl | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/operations/broadcasts.jl b/src/operations/broadcasts.jl index 53367686..c0acd384 100644 --- a/src/operations/broadcasts.jl +++ b/src/operations/broadcasts.jl @@ -138,6 +138,7 @@ mutatingop(::typeof(apply)) = apply! else left = first(bc.args) right = last(bc.args) + f = bc.f # handle annoyances with the pow operator if left isa Base.RefValue{typeof(^)} f = ^ @@ -204,7 +205,7 @@ mutatingop(::typeof(apply)) = apply! add = mutatingop(defaultadd(f)) return add(C, left, right, f) else - return apply!(C, f, left, right; accum=second) + return apply!(f, C, left, right; accum=second) end end end @@ -263,6 +264,7 @@ end else left = first(bc.args) right = last(bc.args) + f = bc.f # handle annoyances with the pow operator if left isa Base.RefValue{typeof(^)} f = ^ @@ -329,7 +331,7 @@ end add = mutatingop(defaultadd(f)) return add(C, left, right, f) else - return apply!(C, f, left, right; accum=second) + return apply!(f, C, left, right; accum=second) end end end diff --git a/test/operations/broadcasting.jl b/test/operations/broadcasting.jl index c17c6e84..aea70a50 100644 --- a/test/operations/broadcasting.jl +++ b/test/operations/broadcasting.jl @@ -15,6 +15,14 @@ B .= sqrt.(A .^ 2) @test B == A + # C .= A .op B where C is neither operand (#124) + C = GBVector(zeros(3)) + C .= GBVector([1.0, 2, 3]) .+ GBVector([4.0, 5, 6]) + @test Vector(C) == [5.0, 7, 9] + D = GBMatrix(zeros(2, 2)) + D .= A .- 1 + @test Matrix(D) == [0 2; 1 3] + u = rand(1000) v = GBVector(u) @test sin.(u) ≈ Vector(sin.(v)) From 10e9766bbc73b9b44987b12a874280f3d3467271 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 27 Oct 2026 17:04:08 +0000 Subject: [PATCH 2/4] Accept non-Int64 indices and convert eltypes from SparseArrays (#144) GBMatrix/GBVector from a SparseMatrixCSC or SparseVector widen narrower index types and convert values to the target eltype. unsafepack! on such inputs now throws instead of handing GraphBLAS 32-bit indices. Co-Authored-By: Claude Opus 5.5 --- src/mem.jl | 14 ++++++++++---- src/pack.jl | 6 ++++++ src/types.jl | 6 +++--- test/gbarray.jl | 8 ++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/mem.jl b/src/mem.jl index 5fb7cbd4..08abbed7 100644 --- a/src/mem.jl +++ b/src/mem.jl @@ -21,10 +21,16 @@ function _copytoraw(A::DenseVecOrMat{T}) where {T} return unsafe_wrap(Array, ptr, size(A)) end -function _copytoraw(A::SparseMatrixCSC) - return _copytoraw(getcolptr(A)), _copytoraw(getrowval(A)), _copytoraw(getnzval(A)) +# GraphBLAS indices are 64-bit, so narrower index types (e.g. Int32) are widened first. +_int64(v::Vector{Int64}) = v +_int64(v::AbstractVector{<:Integer}) = convert(Vector{Int64}, v) + +function _copytoraw(A::SparseMatrixCSC, ::Type{T} = eltype(A)) where {T} + return _copytoraw(_int64(getcolptr(A))), _copytoraw(_int64(getrowval(A))), + _copytoraw(convert(Vector{T}, getnzval(A))) end -function _copytoraw(v::SparseVector) - return _copytoraw([1, length(v.nzind) + 1]), _copytoraw(v.nzind), _copytoraw(v.nzval) +function _copytoraw(v::SparseVector, ::Type{T} = eltype(v)) where {T} + return _copytoraw([1, length(v.nzind) + 1]), _copytoraw(_int64(v.nzind)), + _copytoraw(convert(Vector{T}, v.nzval)) end diff --git a/src/pack.jl b/src/pack.jl index 5dcad284..7e9996de 100644 --- a/src/pack.jl +++ b/src/pack.jl @@ -277,14 +277,20 @@ function unsafepack!( end function unsafepack!(A::AbstractGBArray, S::SparseMatrixCSC, shallow::Bool = true; decrementindices = true) + _checkindices(S) unsafepack!(A, getcolptr(S), getrowval(S), getnzval(S), shallow; decrementindices) end +# GraphBLAS reads indices as 64-bit, so narrower ones can't be shared without a copy. +_checkindices(::Union{SparseMatrixCSC{<:Any, Int64}, SparseVector{<:Any, Int64}}) = nothing +_checkindices(S) = throw(ArgumentError( + "unsafepack! needs Int64 indices, got $(eltype(rowvals(S))). Use the GBMatrix/GBVector constructor to copy instead.")) unsafepack!( A::AbstractGBArray, S::Transpose{<:Any, <:SparseMatrixCSC}, shallow; decrementindices = true ) = transpose(unsafepack!(A, parent(S), shallow; decrementindices)) function unsafepack!(A::AbstractGBArray, s::SparseVector, shallow::Bool = true; decrementindices = true) + _checkindices(s) ptrvec = [1, length(s.nzind) + 1] ptrvec = shallow ? ptrvec : _copytoraw(ptrvec) # TODO: potential segfault when ptrvec goes out. unsafepack!(A, ptrvec, s.nzind, s.nzval, shallow; decrementindices) diff --git a/src/types.jl b/src/types.jl index ff355997..a5db3259 100644 --- a/src/types.jl +++ b/src/types.jl @@ -527,7 +527,7 @@ macro gbmatrixtype(typename) fill = defaultfill(F) ) where {T, F} C = $typename{T, F}(size(A, 1), 1; fill) - return unsafepack!(C, _copytoraw(A)..., false) + return unsafepack!(C, _copytoraw(A, T)..., false) end $typename{T}( A::SparseVector; @@ -543,7 +543,7 @@ macro gbmatrixtype(typename) fill = defaultfill(F) ) where {T, F} C = $typename{T, F}(size(A)...; fill) - return unsafepack!(C, _copytoraw(A)..., false) + return unsafepack!(C, _copytoraw(A, T)..., false) end $typename{T}( A::SparseMatrixCSC; @@ -739,7 +739,7 @@ macro gbvectortype(typename) fill = defaultfill(F) ) where {T, F} C = $typename{T, F}(size(A, 1); fill) - return unsafepack!(C, _copytoraw(A)..., false) + return unsafepack!(C, _copytoraw(A, T)..., false) end $typename{T}( A::SparseVector; diff --git a/test/gbarray.jl b/test/gbarray.jl index 5e158158..f3e9053a 100644 --- a/test/gbarray.jl +++ b/test/gbarray.jl @@ -52,6 +52,14 @@ vnz = findnz(v) xnz = findnz(x) @test vnz == xnz + + # narrower indices and eltype conversion (#144) + S = SparseMatrixCSC{Float64, Int32}(sparse([1, 3], [1, 2], [1.0, 2.0], 3, 3)) + @test SparseMatrixCSC(GBMatrix(S)) == S + @test eltype(GBMatrix{Float32}(S)) == Float32 && SparseMatrixCSC(GBMatrix{Float32}(S)) == S + s = SparseVector{Float64, Int32}(5, Int32[2, 4], [1.0, 2.0]) + @test SparseVector(GBVector(s)) == s + @test_throws ArgumentError SuiteSparseGraphBLAS.unsafepack!(GBMatrix{Float64}(3, 3), S) end end From 337d8ff1edc6edfff2afd122b7d62583467c5ea4 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 27 Oct 2026 17:05:47 +0000 Subject: [PATCH 3/4] Document GBMatrix/GBVector constructors and missing utilities - GBMatrix/GBVector docstrings: list constructors from dims, dense arrays, SparseMatrixCSC/SparseVector, and COO with `fill`/`combine`. The scalar (iso) COO forms take no `combine`. - Add docstrings for getfill, gbget, gbrandn and list them on the Utilities page; fix the gbset option list rendering. Fixes #101, fixes #103. Co-Authored-By: Claude Opus 5.5 --- docs/src/utilities.md | 3 +++ src/abstractgbarray.jl | 5 +++++ src/options.jl | 21 +++++++++++++++------ src/random.jl | 6 ++++++ src/types.jl | 37 ++++++++++++++++++++++++------------- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/docs/src/utilities.md b/docs/src/utilities.md index dced66fc..8e6542a8 100644 --- a/docs/src/utilities.md +++ b/docs/src/utilities.md @@ -1,10 +1,12 @@ ```@docs setfill setfill! +getfill sparsitystatus format setstorageorder! gbset +gbget SuiteSparseGraphBLAS.jit! Descriptor SuiteSparseGraphBLAS.set_lib! @@ -15,4 +17,5 @@ SuiteSparseGraphBLAS.xtype SuiteSparseGraphBLAS.ytype SuiteSparseGraphBLAS.ztype gbrand +gbrandn ``` \ No newline at end of file diff --git a/src/abstractgbarray.jl b/src/abstractgbarray.jl index 776133c5..85e942f0 100644 --- a/src/abstractgbarray.jl +++ b/src/abstractgbarray.jl @@ -1094,6 +1094,11 @@ function setfill(A::AbstractGBArray, x) # aliasing form. return B end +""" + getfill(A::AbstractGBArray) + +Return the fill value of `A`, the value of entries that are not stored. +""" getfill(A::AbstractGBArray) = A.fill getfill(A::LinearAlgebra.AdjOrTrans{<:Any, <:AbstractGBArray}) = getfill(parent(A)) diff --git a/src/options.jl b/src/options.jl index 98c853d0..29991d64 100644 --- a/src/options.jl +++ b/src/options.jl @@ -3,12 +3,15 @@ gbset(option, value) Set an option either for a specific GBArray, or globally. The commonly used options are: - - `:format = [RowMajor() | ColMajor()]`: The global default or array specific - column major or row major ordering. - - `:nthreads = [Integer]`: The global number of OpenMP threads to use. - - `:burble = [Bool]`: Print diagnostic output. - - `:sparsity_control = [:full | :bitmap | :sparse | :hypersparse]`: Set the sparsity of a - single GBArray. + +- `:format = [RowMajor() | ColMajor()]`: The global default or array specific + column major or row major ordering. +- `:nthreads = [Integer]`: The global number of OpenMP threads to use. +- `:burble = [Bool]`: Print diagnostic output. +- `:sparsity_control = [:full | :bitmap | :sparse | :hypersparse]`: Set the sparsity of a + single GBArray. + +See also: [`gbget`](@ref). """ function gbset end using .LibGraphBLAS: GrB_Descriptor, GrB_Info, GrB_Desc_Field, GrB_Desc_Value, GrB_OUTP, GrB_MASK, GrB_INP0, GrB_INP1, @@ -93,6 +96,12 @@ function gbset(option, value) return nothing end +""" + gbget(A::GBArray, option) + gbget(option) + +Get the value of an option for a specific GBArray, or globally. See [`gbset`](@ref). +""" function gbget(option) option = option_toconst(option) return GxB_Global_Option_get(option) diff --git a/src/random.jl b/src/random.jl index 249c96ab..68264500 100644 --- a/src/random.jl +++ b/src/random.jl @@ -73,6 +73,12 @@ function gbrand( ) end +""" + gbrandn(type, nrows, ncols, density; kwargs...)::GBMatrix + gbrandn(rng::AbstractRNG, type, nrows, ncols, density; kwargs...)::GBMatrix + +Like [`gbrand`](@ref), but with normally distributed values drawn by `randn`. +""" function gbrandn( rng::AbstractRNG, type::DataType, nrows::Integer, ncols::Integer, density::AbstractFloat; symmetric=false, pattern=false, skewsymmetric=false, hermitian=false, nodiagonal=false diff --git a/src/types.jl b/src/types.jl index a5db3259..63faa6ef 100644 --- a/src/types.jl +++ b/src/types.jl @@ -772,19 +772,24 @@ end GBVector{T, F} <: AbstractSparseArray{T, UInt64, 1} One-dimensional GraphBLAS array with elements of type T. `F` is the type of the fill-value, -which is typically `Missing` or `T`. +which is typically `Nothing`, `Missing`, or `T`. Internal representation is specified as opaque, but may be either a dense vector, bitmap vector, or compressed sparse vector. See also: [`GBMatrix`](@ref). -# Construction Signatures +# Constructors GBVector{T, F}(n::Integer; fill = defaultfill(F)) GBVector{T}(n::Integer; fill = defaultfill(T)) - GBVector(I::AbstractVector, X::AbstractVector{T}, n; fill=defaultfill(T), combine=+) - GBVector(I::AbstractVector, x::T, n; fill=defaultfill(T), combine=+) - GBVector(v::Union{<:AbstractGBVector, <:AbstractVector}; fill = defaultfill(eltype(v))) + GBVector(v::AbstractVector; fill = defaultfill(eltype(v))) + GBVector(I::AbstractVector, X::AbstractVector{T}, n; fill = defaultfill(T), combine = +) + GBVector(I::AbstractVector, x::T, n; fill = defaultfill(T)) + +The first two create an empty vector. `v` may be a dense `Vector`, a `SparseVector`, or +another `GBVector`. The coordinate forms store `X[k]` (or the scalar `x`) at `I[k]`, +merging duplicates with `combine`; `n` defaults to `maximum(I)`. `fill` is the value of +entries that are not stored, `zero(T)` by default. All constructors, no matter their input, may specify parameters for element type `T` as well as a fill type `F`, conversions are handled internally. @@ -812,7 +817,7 @@ GBVector{T}( GBMatrix{T, F} <: AbstractSparseArray{T, UInt64, 2} Two-dimensional GraphBLAS array with elements of type `T`. `F` is the type of the fill-value, -which is typically `Missing` or `T`. +which is typically `Nothing`, `Missing`, or `T`. Internal representation is specified as opaque, but in this implementation is stored as one of the following in either row or column orientation: @@ -823,13 +828,19 @@ the following in either row or column orientation: The storage type is automatically determined by the library. -#Signatures +# Constructors GBMatrix{T, F}(nrows::Integer, ncols::Integer; fill = defaultfill(F)) GBMatrix{T}(nrows::Integer, ncols::Integer; fill = defaultfill(T)) - GBMatrix(I::AbstractVector, J::AbstractVector, X::AbstractVector{T}, dims...; fill=defaultfill(T), combine=+) - GBMatrix(I::AbstractVector, J::AbstractVector, x::T, dims...; fill=defaultfill(T), combine=+) - GBMatrix(A::Union{<:AbstractGBArray, <:AbstractMatrix}; fill = defaultfill(eltype(A))) + GBMatrix(A::AbstractMatrix; fill = defaultfill(eltype(A))) + GBMatrix(I::AbstractVector, J::AbstractVector, X::AbstractVector{T}, nrows, ncols; fill = defaultfill(T), combine = +) + GBMatrix(I::AbstractVector, J::AbstractVector, x::T, nrows, ncols; fill = defaultfill(T)) + +The first two create an empty matrix. `A` may be a dense `Matrix`, a `SparseMatrixCSC`, or +another GraphBLAS array. The coordinate (COO) forms store `X[k]` (or the scalar `x`) at +`(I[k], J[k])`, merging duplicates with `combine`; `nrows` and `ncols` default to +`maximum(I)` and `maximum(J)`. `fill` is the value of entries that are not stored, +`zero(T)` by default. All constructors, no matter their input, may specify an element type `T` as well as a fill type `F`, conversions are handled internally. @@ -861,18 +872,18 @@ GBMatrix{T}( OrientedGBMatrix{T, F, O} <: AbstractSparseArray{T, UInt64, 2} Two-dimensional GraphBLAS array with elements of type `T`. `F` is the type of the fill-value, -which is typically `Missing` or `T`. +which is typically `Nothing`, `Missing`, or `T`. Exactly the same as [`GBMatrix`](@ref), except the memory orientation is static: either `StorageOrders.RowMajor()` (default) or `StorageOrders.ColMajor()`. The aliases `GBMatrixC` and `GBMatrixR` are the preferred construction methods. -#Signatures +# Constructors GBMatrix[R | C]{T, F}(nrows::Integer, ncols::Integer; fill = defaultfill(F)) GBMatrix[R | C]{T}(nrows::Integer, ncols::Integer; fill = defaultfill(T)) GBMatrix[R | C](I::AbstractVector, J::AbstractVector, X::AbstractVector{T}, dims...; fill=defaultfill(T), combine=+) - GBMatrix[R | C](I::AbstractVector, J::AbstractVector, x::T, dims...; fill=defaultfill(T), combine=+) + GBMatrix[R | C](I::AbstractVector, J::AbstractVector, x::T, dims...; fill=defaultfill(T)) GBMatrix[R | C](A::Union{<:AbstractGBArray, <:AbstractMatrix}; fill = defaultfill(eltype(A))) All constructors, no matter their input, may specify an element type `T` From 8c6d7ee4d3254a190fade24129afc5a37fe1e9a0 Mon Sep 17 00:00:00 2001 From: "Viral B. Shah" Date: Tue, 27 Oct 2026 17:06:59 +0000 Subject: [PATCH 4/4] Fix scalar-valued GBVector/GBMatrix constructors GBVector(I, x, n) referenced an undefined J, and the dims-plus-scalar constructors passed fill positionally. Co-Authored-By: Claude Opus 5.5 --- src/types.jl | 6 +++--- test/gbarray.jl | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/types.jl b/src/types.jl index 63faa6ef..930b84a4 100644 --- a/src/types.jl +++ b/src/types.jl @@ -470,7 +470,7 @@ macro gbmatrixtype(typename) $typename{T}(dims::Dims{2}, x; fill::F = defaultfill(T)) where {T, F} = $typename{T, F}(dims, x; fill) $typename(dims::Dims{2}, x::T; fill = defaultfill(T)) where T = - $typename{T}(dims, x, fill) + $typename{T}(dims, x; fill) $typename{T}(nrows, ncols, x; fill::F = defaultfill(T)) where {T, F} = $typename{T, F}((nrows, ncols), x; fill) @@ -683,7 +683,7 @@ macro gbvectortype(typename) function $typename( I::AbstractVector, x::T, n; fill = defaultfill(T)) where {T} - $typename{T}(I, J, x, n; fill) + $typename{T}(I, x, n; fill) end $typename(I::AbstractVector, x::T; fill = defaultfill(T)) where T = $typename{T}(I, x, maximum(I); fill) @@ -696,7 +696,7 @@ macro gbvectortype(typename) $typename{T}(dims::Dims{1}, x; fill::F = defaultfill(T)) where {T, F} = $typename{T, F}(dims, x; fill) $typename(dims::Dims{1}, x::T; fill = defaultfill(T)) where T = - $typename{T}(dims, x, fill) + $typename{T}(dims, x; fill) $typename{T}(nrows, x; fill = defaultfill(T)) where T = $typename{T}((nrows,), x; fill) diff --git a/test/gbarray.jl b/test/gbarray.jl index f3e9053a..4cfb49c0 100644 --- a/test/gbarray.jl +++ b/test/gbarray.jl @@ -60,6 +60,11 @@ s = SparseVector{Float64, Int32}(5, Int32[2, 4], [1.0, 2.0]) @test SparseVector(GBVector(s)) == s @test_throws ArgumentError SuiteSparseGraphBLAS.unsafepack!(GBMatrix{Float64}(3, 3), S) + + # scalar-valued constructors + @test Vector(GBVector([1, 3], 2.0, 4)) == [2.0, 0, 2, 0] + @test Vector(GBVector((3,), 1.0)) == ones(3) + @test Matrix(GBMatrix((2, 2), 1.0)) == ones(2, 2) end end