Skip to content

Commit 0837ba4

Browse files
Sacha0stevengj
authored andcommitted
Make broadcast over a single SparseMatrixCSC (and possibly broadcast scalars) more generic. (#19239)
1 parent 6803f9a commit 0837ba4

File tree

2 files changed

+56
-90
lines changed

2 files changed

+56
-90
lines changed

base/sparse/sparsematrix.jl

+52-82
Original file line numberDiff line numberDiff line change
@@ -1398,31 +1398,19 @@ end
13981398

13991399
sparse(S::UniformScaling, m::Integer, n::Integer=m) = speye_scaled(S.λ, m, n)
14001400

1401-
## Unary arithmetic and boolean operators
14021401

1403-
"""
1404-
Helper macro for the unary broadcast definitions below. Takes parent method `fp` and a set
1405-
of desired child methods `fcs`, and builds an expression defining each of the child methods
1406-
such that `broadcast(::typeof(fc), A::SparseMatrixCSC) = fp(fc, A)`.
1407-
"""
1408-
macro _enumerate_childmethods(fp, fcs...)
1409-
fcexps = Expr(:block)
1410-
for fc in fcs
1411-
push!(fcexps.args, :( $(esc(:broadcast))(::typeof($(esc(fc))), A::SparseMatrixCSC) = $(esc(fp))($(esc(fc)), A) ) )
1412-
end
1413-
return fcexps
1414-
end
1402+
## Broadcast operations involving a single sparse matrix and possibly broadcast scalars
14151403

1416-
# Operations that map zeros to zeros and may map nonzeros to zeros, yielding a sparse matrix
1417-
"""
1418-
Takes unary function `f` that maps zeros to zeros and may map nonzeros to zeros, and returns
1419-
a new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in
1420-
`A` and retaining only the resulting nonzeros.
1421-
"""
1422-
function _broadcast_unary_nz2z_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCSC{TvA,TiA}, ::Type{TvB})
1423-
Bcolptr = Array{TiA}(A.n + 1)
1424-
Browval = Array{TiA}(nnz(A))
1425-
Bnzval = Array{TvB}(nnz(A))
1404+
function broadcast{Tf}(f::Tf, A::SparseMatrixCSC)
1405+
fofzero = f(zero(eltype(A)))
1406+
fpreszero = fofzero == zero(fofzero)
1407+
return fpreszero ? _broadcast_zeropres(f, A) : _broadcast_notzeropres(f, fofzero, A)
1408+
end
1409+
"Returns a `SparseMatrixCSC` storing only the nonzero entries of `broadcast(f, Matrix(A))`."
1410+
function _broadcast_zeropres{Tf}(f::Tf, A::SparseMatrixCSC)
1411+
Bcolptr = similar(A.colptr, A.n + 1)
1412+
Browval = similar(A.rowval, nnz(A))
1413+
Bnzval = similar(A.nzval, Base.Broadcast.promote_eltype_op(f, A), nnz(A))
14261414
Bk = 1
14271415
@inbounds for j in 1:A.n
14281416
Bcolptr[j] = Bk
@@ -1440,69 +1428,51 @@ function _broadcast_unary_nz2z_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCS
14401428
resize!(Bnzval, Bk - 1)
14411429
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
14421430
end
1443-
function _broadcast_unary_nz2z_z2z{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1444-
_broadcast_unary_nz2z_z2z_T(f, A, Tv)
1445-
end
1446-
@_enumerate_childmethods(_broadcast_unary_nz2z_z2z,
1447-
sin, sinh, sind, asin, asinh, asind,
1448-
tan, tanh, tand, atan, atanh, atand,
1449-
sinpi, cosc, ceil, floor, trunc, round)
1450-
broadcast(::typeof(real), A::SparseMatrixCSC) = copy(A)
1451-
broadcast{Tv,Ti}(::typeof(imag), A::SparseMatrixCSC{Tv,Ti}) = spzeros(Tv, Ti, A.m, A.n)
1452-
broadcast{TTv}(::typeof(real), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(real, A, TTv)
1453-
broadcast{TTv}(::typeof(imag), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(imag, A, TTv)
1454-
ceil{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(ceil, A, To)
1455-
floor{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(floor, A, To)
1456-
trunc{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(trunc, A, To)
1457-
round{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(round, A, To)
1458-
1459-
# Operations that map zeros to zeros and map nonzeros to nonzeros, yielding a sparse matrix
1460-
"""
1461-
Takes unary function `f` that maps zeros to zeros and nonzeros to nonzeros, and returns a
1462-
new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in `A`.
1463-
"""
1464-
function _broadcast_unary_nz2nz_z2z{TvA,TiA,Tf<:Function}(f::Tf, A::SparseMatrixCSC{TvA,TiA})
1465-
Bcolptr = Vector{TiA}(A.n + 1)
1466-
Browval = Vector{TiA}(nnz(A))
1467-
copy!(Bcolptr, 1, A.colptr, 1, A.n + 1)
1468-
copy!(Browval, 1, A.rowval, 1, nnz(A))
1469-
Bnzval = broadcast(f, A.nzval)
1470-
resize!(Bnzval, nnz(A))
1471-
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
1472-
end
1473-
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2z,
1474-
log1p, expm1, abs, abs2, conj)
1475-
function conj!(A::SparseMatrixCSC)
1476-
@inbounds @simd for k in 1:nnz(A)
1477-
A.nzval[k] = conj(A.nzval[k])
1478-
end
1479-
return A
1480-
end
1481-
1482-
# Operations that map both zeros and nonzeros to zeros, yielding a dense matrix
14831431
"""
1484-
Takes unary function `f` that maps both zeros and nonzeros to nonzeros, constructs a new
1485-
`Matrix{TvB}` `B`, populates all entries of `B` with the result of a single `f(one(zero(Tv)))`
1486-
call, and then, for each stored entry in `A`, calls `f` on the entry's value and stores the
1487-
result in the corresponding location in `B`.
1432+
Returns a (dense) `SparseMatrixCSC` with `fillvalue` stored in place of each unstored
1433+
entry in `A` and `f(A[i,j])` stored in place of each stored entry `A[i,j]` in `A`.
14881434
"""
1489-
function _broadcast_unary_nz2nz_z2nz{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1490-
B = fill(f(zero(Tv)), size(A))
1491-
@inbounds for j in 1:A.n
1492-
for k in nzrange(A, j)
1493-
i = A.rowval[k]
1494-
x = A.nzval[k]
1495-
B[i,j] = f(x)
1496-
end
1435+
function _broadcast_notzeropres{Tf}(f::Tf, fillvalue, A::SparseMatrixCSC)
1436+
nnzB = A.m * A.n
1437+
# Build structure
1438+
Bcolptr = similar(A.colptr, A.n + 1)
1439+
copy!(Bcolptr, 1:A.m:(nnzB + 1))
1440+
Browval = similar(A.rowval, nnzB)
1441+
for k in 1:A.m:(nnzB - A.m + 1)
1442+
copy!(Browval, k, 1:A.m)
1443+
end
1444+
# Populate values
1445+
Bnzval = fill(fillvalue, nnzB)
1446+
@inbounds for (j, jo) in zip(1:A.n, 0:A.m:(nnzB - 1)), k in nzrange(A, j)
1447+
Bnzval[jo + A.rowval[k]] = f(A.nzval[k])
14971448
end
1498-
return B
1449+
# NOTE: Combining the fill call into the loop above to avoid multiple sweeps over /
1450+
# nonsequential access of Bnzval does not appear to improve performance
1451+
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
14991452
end
1500-
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2nz,
1501-
log, log2, log10, exp, exp2, exp10, sinc, cospi,
1502-
cos, cosh, cosd, acos, acosd,
1503-
cot, coth, cotd, acot, acotd,
1504-
sec, sech, secd, asech,
1505-
csc, csch, cscd, acsch)
1453+
1454+
# Cover common broadcast operations involving a single sparse matrix and one or more
1455+
# broadcast scalars.
1456+
#
1457+
# TODO: The minimal snippet below is not satisfying: A better solution would achieve
1458+
# the same for (1) all broadcast scalar types (Base.Broadcast.containertype(x) == Any?) and
1459+
# (2) any combination (number, order, type mixture) of broadcast scalars.
1460+
#
1461+
broadcast{Tf}(f::Tf, x::Union{Number,Bool}, A::SparseMatrixCSC) = broadcast(y -> f(x, y), A)
1462+
broadcast{Tf}(f::Tf, A::SparseMatrixCSC, y::Union{Number,Bool}) = broadcast(x -> f(x, y), A)
1463+
# NOTE: The following two method definitions work around #19096. These definitions should
1464+
# be folded into the two preceding definitions on resolution of #19096.
1465+
broadcast{Tf,T}(f::Tf, ::Type{T}, A::SparseMatrixCSC) = broadcast(y -> f(T, y), A)
1466+
broadcast{Tf,T}(f::Tf, A::SparseMatrixCSC, ::Type{T}) = broadcast(x -> f(x, T), A)
1467+
1468+
# TODO: The following definitions should be deprecated.
1469+
ceil{To}(::Type{To}, A::SparseMatrixCSC) = ceil.(To, A)
1470+
floor{To}(::Type{To}, A::SparseMatrixCSC) = floor.(To, A)
1471+
trunc{To}(::Type{To}, A::SparseMatrixCSC) = trunc.(To, A)
1472+
round{To}(::Type{To}, A::SparseMatrixCSC) = round.(To, A)
1473+
1474+
# TODO: More appropriate location?
1475+
conj!(A::SparseMatrixCSC) = (broadcast!(conj, A.nzval, A.nzval); A)
15061476

15071477

15081478
## Broadcasting kernels specialized for returning a SparseMatrixCSC

test/sparse/sparse.jl

+4-8
Original file line numberDiff line numberDiff line change
@@ -1627,14 +1627,10 @@ let A = sparse(UInt32[1,2,3], UInt32[1,2,3], [1.0,2.0,3.0])
16271627
@test A[1,1:3] == A[1,:] == [1,0,0]
16281628
end
16291629

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
1630+
# Check that `broadcast` methods specialized for unary operations over `SparseMatrixCSC`s
1631+
# are called. (Issue #18705.) EDIT: #19239 unified broadcast over a single sparse matrix,
1632+
# eliminating the former operation classes.
1633+
@test isa(sin.(spdiagm(1.0:5.0)), SparseMatrixCSC)
16381634

16391635
# 19225
16401636
let X = sparse([1 -1; -1 1])

0 commit comments

Comments
 (0)