Skip to content

Commit 6613377

Browse files
committed
Make broadcast over a single SparseMatrixCSC (and possibly broadcast scalars) more generic.
1 parent ce6a0c3 commit 6613377

File tree

2 files changed

+64
-91
lines changed

2 files changed

+64
-91
lines changed

base/sparse/sparsematrix.jl

+60-82
Original file line numberDiff line numberDiff line change
@@ -1390,31 +1390,19 @@ function one{T}(S::SparseMatrixCSC{T})
13901390
speye(T, m)
13911391
end
13921392

1393-
## Unary arithmetic and boolean operators
13941393

1395-
"""
1396-
Helper macro for the unary broadcast definitions below. Takes parent method `fp` and a set
1397-
of desired child methods `fcs`, and builds an expression defining each of the child methods
1398-
such that `broadcast(::typeof(fc), A::SparseMatrixCSC) = fp(fc, A)`.
1399-
"""
1400-
macro _enumerate_childmethods(fp, fcs...)
1401-
fcexps = Expr(:block)
1402-
for fc in fcs
1403-
push!(fcexps.args, :( $(esc(:broadcast))(::typeof($(esc(fc))), A::SparseMatrixCSC) = $(esc(fp))($(esc(fc)), A) ) )
1404-
end
1405-
return fcexps
1406-
end
1394+
## Broadcast operations involving a single sparse matrix and possibly broadcast scalars
14071395

1408-
# Operations that map zeros to zeros and may map nonzeros to zeros, yielding a sparse matrix
1409-
"""
1410-
Takes unary function `f` that maps zeros to zeros and may map nonzeros to zeros, and returns
1411-
a new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in
1412-
`A` and retaining only the resulting nonzeros.
1413-
"""
1414-
function _broadcast_unary_nz2z_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCSC{TvA,TiA}, ::Type{TvB})
1415-
Bcolptr = Array{TiA}(A.n + 1)
1416-
Browval = Array{TiA}(nnz(A))
1417-
Bnzval = Array{TvB}(nnz(A))
1396+
function broadcast{Tf}(f::Tf, A::SparseMatrixCSC)
1397+
fofzero = f(zero(eltype(A)))
1398+
fpreszero = fofzero == zero(fofzero)
1399+
return fpreszero ? _broadcast_zeropres(f, A) : _broadcast_notzeropres(f, A)
1400+
end
1401+
"Returns a `SparseMatrixCSC` storing only the nonzero entries of `broadcast(f, Matrix(A))`."
1402+
function _broadcast_zeropres{Tf}(f::Tf, A::SparseMatrixCSC)
1403+
Bcolptr = similar(A.colptr, A.n + 1)
1404+
Browval = similar(A.rowval, nnz(A))
1405+
Bnzval = similar(A.nzval, promote_eltype_op(f, A), nnz(A))
14181406
Bk = 1
14191407
@inbounds for j in 1:A.n
14201408
Bcolptr[j] = Bk
@@ -1432,69 +1420,59 @@ function _broadcast_unary_nz2z_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCS
14321420
resize!(Bnzval, Bk - 1)
14331421
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
14341422
end
1435-
function _broadcast_unary_nz2z_z2z{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1436-
_broadcast_unary_nz2z_z2z_T(f, A, Tv)
1437-
end
1438-
@_enumerate_childmethods(_broadcast_unary_nz2z_z2z,
1439-
sin, sinh, sind, asin, asinh, asind,
1440-
tan, tanh, tand, atan, atanh, atand,
1441-
sinpi, cosc, ceil, floor, trunc, round)
1442-
broadcast(::typeof(real), A::SparseMatrixCSC) = copy(A)
1443-
broadcast{Tv,Ti}(::typeof(imag), A::SparseMatrixCSC{Tv,Ti}) = spzeros(Tv, Ti, A.m, A.n)
1444-
broadcast{TTv}(::typeof(real), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(real, A, TTv)
1445-
broadcast{TTv}(::typeof(imag), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(imag, A, TTv)
1446-
ceil{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(ceil, A, To)
1447-
floor{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(floor, A, To)
1448-
trunc{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(trunc, A, To)
1449-
round{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(round, A, To)
1450-
1451-
# Operations that map zeros to zeros and map nonzeros to nonzeros, yielding a sparse matrix
1452-
"""
1453-
Takes unary function `f` that maps zeros to zeros and nonzeros to nonzeros, and returns a
1454-
new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in `A`.
1455-
"""
1456-
function _broadcast_unary_nz2nz_z2z{TvA,TiA,Tf<:Function}(f::Tf, A::SparseMatrixCSC{TvA,TiA})
1457-
Bcolptr = Vector{TiA}(A.n + 1)
1458-
Browval = Vector{TiA}(nnz(A))
1459-
copy!(Bcolptr, 1, A.colptr, 1, A.n + 1)
1460-
copy!(Browval, 1, A.rowval, 1, nnz(A))
1461-
Bnzval = broadcast(f, A.nzval)
1462-
resize!(Bnzval, nnz(A))
1463-
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
1464-
end
1465-
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2z,
1466-
log1p, expm1, abs, abs2, conj)
1467-
function conj!(A::SparseMatrixCSC)
1468-
@inbounds @simd for k in 1:nnz(A)
1469-
A.nzval[k] = conj(A.nzval[k])
1470-
end
1471-
return A
1472-
end
1473-
1474-
# Operations that map both zeros and nonzeros to zeros, yielding a dense matrix
14751423
"""
1476-
Takes unary function `f` that maps both zeros and nonzeros to nonzeros, constructs a new
1477-
`Matrix{TvB}` `B`, populates all entries of `B` with the result of a single `f(one(zero(Tv)))`
1478-
call, and then, for each stored entry in `A`, calls `f` on the entry's value and stores the
1479-
result in the corresponding location in `B`.
1424+
Returns a (dense) `SparseMatrixCSC` with `f(zero(eltype(A)))` stored in place of each
1425+
unstored entry in `A` and `f(A[i,j])` stored in place of each stored entry `A[i,j]` in `A`.
14801426
"""
1481-
function _broadcast_unary_nz2nz_z2nz{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1482-
B = fill(f(zero(Tv)), size(A))
1483-
@inbounds for j in 1:A.n
1484-
for k in nzrange(A, j)
1485-
i = A.rowval[k]
1486-
x = A.nzval[k]
1487-
B[i,j] = f(x)
1488-
end
1427+
function _broadcast_notzeropres{Tf}(f::Tf, A::SparseMatrixCSC)
1428+
nnzB = A.m * A.n
1429+
# Build structure
1430+
Bcolptr = similar(A.colptr, A.n + 1)
1431+
copy!(Bcolptr, 1:A.m:(nnzB + 1))
1432+
Browval = similar(A.rowval, nnzB)
1433+
for k in 1:A.m:(nnzB - A.m + 1)
1434+
copy!(Browval, k, 1:A.m)
1435+
end
1436+
# Populate values
1437+
Bnzval = fill(f(zero(eltype(A))), nnzB)
1438+
@inbounds for (j, jo) in zip(1:A.n, 0:A.m:(nnzB - 1)), k in nzrange(A, j)
1439+
Bnzval[jo + A.rowval[k]] = f(A.nzval[k])
14891440
end
1490-
return B
1441+
# NOTE: Combining the fill call into the loop above to avoid multiple sweeps over /
1442+
# nonsequential access of Bnzval does not appear to improve performance
1443+
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
14911444
end
1492-
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2nz,
1493-
log, log2, log10, exp, exp2, exp10, sinc, cospi,
1494-
cos, cosh, cosd, acos, acosd,
1495-
cot, coth, cotd, acot, acotd,
1496-
sec, sech, secd, asech,
1497-
csc, csch, cscd, acsch)
1445+
1446+
# Cover common broadcast operations involving a single sparse matrix and one or more
1447+
# broadcast scalars.
1448+
#
1449+
# TODO: The minimal snippet below is not satisfying: A better solution would achieve
1450+
# the same for (1) all broadcast scalar types (Base.Broadcast.containertype(x) == Any?) and
1451+
# (2) any combination (number, order, type mixture) of broadcast scalars. One might achieve
1452+
# (2) via a generated function. But how one might simultaneously and gracefully achieve (1)
1453+
# eludes me. Thoughts much appreciated.
1454+
#
1455+
broadcast{Tf}(f::Tf, x::Union{Number,Bool}, A::SparseMatrixCSC) = broadcast(y -> f(x, y), A)
1456+
broadcast{Tf}(f::Tf, A::SparseMatrixCSC, y::Union{Number,Bool}) = broadcast(x -> f(x, y), A)
1457+
# NOTE: The following two method definitions work around #19096. These definitions should
1458+
# be folded into the two preceding definitions on resolution of #19096.
1459+
broadcast{Tf,T}(f::Tf, ::Type{T}, A::SparseMatrixCSC) = broadcast(y -> f(T, y), A)
1460+
broadcast{Tf,T}(f::Tf, A::SparseMatrixCSC, ::Type{T}) = broadcast(x -> f(x, T), A)
1461+
1462+
# TODO: The following definitions should be deprecated.
1463+
ceil{To}(::Type{To}, A::SparseMatrixCSC) = ceil.(To, A)
1464+
floor{To}(::Type{To}, A::SparseMatrixCSC) = floor.(To, A)
1465+
trunc{To}(::Type{To}, A::SparseMatrixCSC) = trunc.(To, A)
1466+
round{To}(::Type{To}, A::SparseMatrixCSC) = round.(To, A)
1467+
1468+
# TODO: Nix these two specializations, or keep them around?
1469+
broadcast{Tf<:typeof(real)}(::Tf, A::SparseMatrixCSC) = copy(A)
1470+
broadcast{Tf<:typeof(imag),Tv,Ti}(::Tf, A::SparseMatrixCSC{Tv,Ti}) = spzeros(Tv, Ti, A.m, A.n)
1471+
# NOTE: Avoiding ambiguities with the general method (broadcast{Tf}(f::Tf, A::SparseMatrixCSC))
1472+
# necessitates the less-than-graceful Tf<:typeof(fn) type parameters in the above definitions
1473+
1474+
# TODO: More appropriate location?
1475+
conj!(A::SparseMatrixCSC) = (broadcast!(conj, A.nzval); A)
14981476

14991477

15001478
## Broadcasting kernels specialized for returning a SparseMatrixCSC

test/sparse/sparse.jl

+4-9
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,6 @@ end
499499
@test R == real.(S)
500500
@test I == imag.(S)
501501
@test real.(sparse(R)) == R
502-
@test nnz(imag.(sparse(R))) == 0
503502
@test abs.(S) == abs.(D)
504503
@test abs2.(S) == abs2.(D)
505504
end
@@ -1627,14 +1626,10 @@ let A = sparse(UInt32[1,2,3], UInt32[1,2,3], [1.0,2.0,3.0])
16271626
@test A[1,1:3] == A[1,:] == [1,0,0]
16281627
end
16291628

1630-
# Check that `broadcast` methods specialized for unary operations over
1631-
# `SparseMatrixCSC`s are called. (Issue #18705.)
1632-
let
1633-
A = spdiagm(1.0:5.0)
1634-
@test isa(sin.(A), SparseMatrixCSC) # representative for _unary_nz2z_z2z class
1635-
@test isa(abs.(A), SparseMatrixCSC) # representative for _unary_nz2nz_z2z class
1636-
@test isa(exp.(A), Array) # representative for _unary_nz2nz_z2nz class
1637-
end
1629+
# Check that `broadcast` methods specialized for unary operations over `SparseMatrixCSC`s
1630+
# are called. (Issue #18705.) EDIT: #19239 unified broadcast over a single sparse matrix,
1631+
# eliminating the former operation classes.
1632+
@test isa(sin.(spdiagm(1.0:5.0)), SparseMatrixCSC)
16381633

16391634
# 19225
16401635
let X = sparse([1 -1; -1 1])

0 commit comments

Comments
 (0)