Skip to content

Commit 379c248

Browse files
authored
Merge pull request #17265 from Sacha0/unarybcastspmat
De-eval-ify vectorized unary functions over SparseMatrixCSCs, and then transition them to compact broadcast syntax
2 parents c20a8bc + bd7da66 commit 379c248

File tree

3 files changed

+155
-145
lines changed

3 files changed

+155
-145
lines changed

base/deprecated.jl

+13
Original file line numberDiff line numberDiff line change
@@ -852,4 +852,17 @@ function convert(::Type{UpperTriangular}, A::Bidiagonal)
852852
end
853853
end
854854

855+
# Deprecate vectorized unary functions over sparse matrices in favor of compact broadcast syntax (#17265).
856+
for f in (:sin, :sinh, :sind, :asin, :asinh, :asind,
857+
:tan, :tanh, :tand, :atan, :atanh, :atand,
858+
:sinpi, :cosc, :ceil, :floor, :trunc, :round, :real, :imag,
859+
:log1p, :expm1, :abs, :abs2, :conj,
860+
:log, :log2, :log10, :exp, :exp2, :exp10, :sinc, :cospi,
861+
:cos, :cosh, :cosd, :acos, :acosd,
862+
:cot, :coth, :cotd, :acot, :acotd,
863+
:sec, :sech, :secd, :asech,
864+
:csc, :csch, :cscd, :acsch)
865+
@eval @deprecate $f(A::SparseMatrixCSC) $f.(A)
866+
end
867+
855868
# End deprecations scheduled for 0.6

base/sparse/sparsematrix.jl

+102-118
Original file line numberDiff line numberDiff line change
@@ -1305,134 +1305,118 @@ end
13051305

13061306
## Unary arithmetic and boolean operators
13071307

1308-
macro _unary_op_nz2z_z2z(op,A,Tv,Ti)
1309-
esc(quote
1310-
nfilledA = nnz($A)
1311-
colptrB = Array{$Ti}($A.n+1)
1312-
rowvalB = Array{$Ti}(nfilledA)
1313-
nzvalB = Array{$Tv}(nfilledA)
1314-
1315-
nzvalA = $A.nzval
1316-
colptrA = $A.colptr
1317-
rowvalA = $A.rowval
1318-
1319-
k = 0 # number of additional zeros introduced by op(A)
1320-
@inbounds for i = 1 : $A.n
1321-
colptrB[i] = colptrA[i] - k
1322-
for j = colptrA[i] : colptrA[i+1]-1
1323-
opAj = $(op)(nzvalA[j])
1324-
if opAj == 0
1325-
k += 1
1326-
else
1327-
rowvalB[j - k] = rowvalA[j]
1328-
nzvalB[j - k] = opAj
1329-
end
1330-
end
1331-
end
1332-
colptrB[end] = $A.colptr[end] - k
1333-
deleteat!(rowvalB, colptrB[end]:nfilledA)
1334-
deleteat!(nzvalB, colptrB[end]:nfilledA)
1335-
return SparseMatrixCSC($A.m, $A.n, colptrB, rowvalB, nzvalB)
1336-
end) # quote
1337-
end
1338-
1339-
# Operations that may map nonzeros to zero, and zero to zero
1340-
# Result is sparse
1341-
for op in (:ceil, :floor, :trunc, :round,
1342-
:sin, :tan, :asin, :atan,
1343-
:sinh, :tanh, :asinh, :atanh,
1344-
:sinpi, :cosc,
1345-
:sind, :tand, :asind, :atand)
1346-
@eval begin
1347-
$(op){Tv,Ti}(A::SparseMatrixCSC{Tv,Ti}) = @_unary_op_nz2z_z2z($op,A,Tv,Ti)
1348-
end # quote
1349-
end # macro
1350-
1351-
for op in (:real, :imag)
1352-
@eval begin
1353-
($op){Tv<:Complex,Ti}(A::SparseMatrixCSC{Tv,Ti}) = @_unary_op_nz2z_z2z($op,A,Tv.parameters[1],Ti)
1354-
end # quote
1355-
end # macro
1356-
real{Tv<:Number,Ti}(A::SparseMatrixCSC{Tv,Ti}) = copy(A)
1357-
imag{Tv<:Number,Ti}(A::SparseMatrixCSC{Tv,Ti}) = spzeros(Tv, Ti, A.m, A.n)
1358-
1359-
for op in (:ceil, :floor, :trunc, :round)
1360-
@eval begin
1361-
($op){T,Tv,Ti}(::Type{T},A::SparseMatrixCSC{Tv,Ti}) = @_unary_op_nz2z_z2z($op,A,T,Ti)
1362-
end # quote
1363-
end # macro
1364-
1365-
1366-
# Operations that map nonzeros to nonzeros, and zeros to zeros
1367-
# Result is sparse
1368-
for op in (:-, :log1p, :expm1)
1369-
@eval begin
1370-
1371-
function ($op)(A::SparseMatrixCSC)
1372-
B = similar(A)
1373-
nzvalB = B.nzval
1374-
nzvalA = A.nzval
1375-
@simd for i=1:length(nzvalB)
1376-
@inbounds nzvalB[i] = ($op)(nzvalA[i])
1377-
end
1378-
return B
1379-
end
1380-
1381-
end
1382-
end
1383-
1384-
function abs{Tv<:Complex,Ti}(A::SparseMatrixCSC{Tv,Ti})
1385-
T = Tv.parameters[1]
1386-
(T <: Integer) && (T = (T <: BigInt) ? BigFloat : Float64)
1387-
@_unary_op_nz2z_z2z(abs,A,T,Ti)
1388-
end
1389-
abs2{Tv<:Complex,Ti}(A::SparseMatrixCSC{Tv,Ti}) = @_unary_op_nz2z_z2z(abs2,A,Tv.parameters[1],Ti)
1390-
for op in (:abs, :abs2)
1391-
@eval begin
1392-
function ($op){Tv<:Number,Ti}(A::SparseMatrixCSC{Tv,Ti})
1393-
B = similar(A)
1394-
nzvalB = B.nzval
1395-
nzvalA = A.nzval
1396-
@simd for i=1:length(nzvalB)
1397-
@inbounds nzvalB[i] = ($op)(nzvalA[i])
1308+
"""
1309+
Helper macro for the unary broadcast definitions below. Takes parent method `fp` and a set
1310+
of desired child methods `fcs`, and builds an expression defining each of the child methods
1311+
such that `broadcast(::typeof(fc), A::SparseMatrixCSC) = fp(fc, A)`.
1312+
"""
1313+
macro _enumerate_childmethods(fp, fcs...)
1314+
fcexps = Expr(:block)
1315+
for fc in fcs
1316+
push!(fcexps.args, :( broadcast(::typeof($(esc(fc))), A::SparseMatrixCSC) = $(esc(fp))($(esc(fc)), A) ) )
1317+
end
1318+
return fcexps
1319+
end
1320+
1321+
# Operations that map zeros to zeros and may map nonzeros to zeros, yielding a sparse matrix
1322+
"""
1323+
Takes unary function `f` that maps zeros to zeros and may map nonzeros to zeros, and returns
1324+
a new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in
1325+
`A` and retaining only the resulting nonzeros.
1326+
"""
1327+
function _broadcast_unary_nz2z_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCSC{TvA,TiA}, ::Type{TvB})
1328+
Bcolptr = Array{TiA}(A.n + 1)
1329+
Browval = Array{TiA}(nnz(A))
1330+
Bnzval = Array{TvB}(nnz(A))
1331+
Bk = 1
1332+
@inbounds for j in 1:A.n
1333+
Bcolptr[j] = Bk
1334+
for Ak in nzrange(A, j)
1335+
x = f(A.nzval[Ak])
1336+
if x != 0
1337+
Browval[Bk] = A.rowval[Ak]
1338+
Bnzval[Bk] = x
1339+
Bk += 1
13981340
end
1399-
return B
14001341
end
14011342
end
1402-
end
1403-
1343+
Bcolptr[A.n + 1] = Bk
1344+
resize!(Browval, Bk - 1)
1345+
resize!(Bnzval, Bk - 1)
1346+
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
1347+
end
1348+
function _broadcast_unary_nz2z_z2z{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1349+
_broadcast_unary_nz2z_z2z_T(f, A, Tv)
1350+
end
1351+
@_enumerate_childmethods(_broadcast_unary_nz2z_z2z,
1352+
sin, sinh, sind, asin, asinh, asind,
1353+
tan, tanh, tand, atan, atanh, atand,
1354+
sinpi, cosc, ceil, floor, trunc, round)
1355+
broadcast(::typeof(real), A::SparseMatrixCSC) = copy(A)
1356+
broadcast{Tv,Ti}(::typeof(imag), A::SparseMatrixCSC{Tv,Ti}) = spzeros(Tv, Ti, A.m, A.n)
1357+
broadcast{TTv}(::typeof(real), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(real, A, TTv)
1358+
broadcast{TTv}(::typeof(imag), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2z_z2z_T(imag, A, TTv)
1359+
ceil{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(ceil, A, To)
1360+
floor{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(floor, A, To)
1361+
trunc{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(trunc, A, To)
1362+
round{To}(::Type{To}, A::SparseMatrixCSC) = _broadcast_unary_nz2z_z2z_T(round, A, To)
1363+
1364+
# Operations that map zeros to zeros and map nonzeros to nonzeros, yielding a sparse matrix
1365+
"""
1366+
Takes unary function `f` that maps zeros to zeros and nonzeros to nonzeros, and returns a
1367+
new `SparseMatrixCSC{TiA,TvB}` `B` generated by applying `f` to each nonzero entry in `A`.
1368+
"""
1369+
function _broadcast_unary_nz2nz_z2z_T{TvA,TiA,TvB}(f::Function, A::SparseMatrixCSC{TvA,TiA}, ::Type{TvB})
1370+
Bcolptr = Vector{TiA}(A.n + 1)
1371+
Browval = Vector{TiA}(nnz(A))
1372+
Bnzval = Vector{TvB}(nnz(A))
1373+
copy!(Bcolptr, 1, A.colptr, 1, A.n + 1)
1374+
copy!(Browval, 1, A.rowval, 1, nnz(A))
1375+
@inbounds @simd for k in 1:nnz(A)
1376+
Bnzval[k] = f(A.nzval[k])
1377+
end
1378+
return SparseMatrixCSC(A.m, A.n, Bcolptr, Browval, Bnzval)
1379+
end
1380+
function _broadcast_unary_nz2nz_z2z{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1381+
_broadcast_unary_nz2nz_z2z_T(f, A, Tv)
1382+
end
1383+
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2z,
1384+
log1p, expm1, abs, abs2, conj)
1385+
broadcast{TTv}(::typeof(abs2), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2nz_z2z_T(abs2, A, TTv)
1386+
broadcast{TTv}(::typeof(abs), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2nz_z2z_T(abs, A, TTv)
1387+
broadcast{TTv<:Integer}(::typeof(abs), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2nz_z2z_T(abs, A, Float64)
1388+
broadcast{TTv<:BigInt}(::typeof(abs), A::SparseMatrixCSC{Complex{TTv}}) = _broadcast_unary_nz2nz_z2z_T(abs, A, BigFloat)
14041389
function conj!(A::SparseMatrixCSC)
1405-
nzvalA = A.nzval
1406-
@simd for i=1:length(nzvalA)
1407-
@inbounds nzvalA[i] = conj(nzvalA[i])
1390+
@inbounds @simd for k in 1:nnz(A)
1391+
A.nzval[k] = conj(A.nzval[k])
14081392
end
14091393
return A
14101394
end
14111395

1412-
conj(A::SparseMatrixCSC) = conj!(copy(A))
1413-
1414-
# Operations that map nonzeros to nonzeros, and zeros to nonzeros
1415-
# Result is dense
1416-
for op in (:cos, :cosh, :acos, :sec, :csc, :cot, :acot, :sech,
1417-
:csch, :coth, :asech, :acsch, :cospi, :sinc, :cosd,
1418-
:cotd, :cscd, :secd, :acosd, :acotd, :log, :log2, :log10,
1419-
:exp, :exp2, :exp10)
1420-
@eval begin
1421-
1422-
function ($op){Tv}(A::SparseMatrixCSC{Tv})
1423-
B = fill($(op)(zero(Tv)), size(A))
1424-
@inbounds for col = 1 : A.n
1425-
for j = A.colptr[col] : A.colptr[col+1]-1
1426-
row = A.rowval[j]
1427-
nz = A.nzval[j]
1428-
B[row,col] = $(op)(nz)
1429-
end
1430-
end
1431-
return B
1396+
# Operations that map both zeros and nonzeros to zeros, yielding a dense matrix
1397+
"""
1398+
Takes unary function `f` that maps both zeros and nonzeros to nonzeros, constructs a new
1399+
`Matrix{TvB}` `B`, populates all entries of `B` with the result of a single `f(one(zero(Tv)))`
1400+
call, and then, for each stored entry in `A`, calls `f` on the entry's value and stores the
1401+
result in the corresponding location in `B`.
1402+
"""
1403+
function _broadcast_unary_nz2nz_z2nz{Tv}(f::Function, A::SparseMatrixCSC{Tv})
1404+
B = fill(f(zero(Tv)), size(A))
1405+
@inbounds for j in 1:A.n
1406+
for k in nzrange(A, j)
1407+
i = A.rowval[k]
1408+
x = A.nzval[k]
1409+
B[i,j] = f(x)
14321410
end
1433-
14341411
end
1412+
return B
14351413
end
1414+
@_enumerate_childmethods(_broadcast_unary_nz2nz_z2nz,
1415+
log, log2, log10, exp, exp2, exp10, sinc, cospi,
1416+
cos, cosh, cosd, acos, acosd,
1417+
cot, coth, cotd, acot, acotd,
1418+
sec, sech, secd, asech,
1419+
csc, csch, cscd, acsch)
14361420

14371421

14381422
## Broadcasting kernels specialized for returning a SparseMatrixCSC
@@ -1734,7 +1718,7 @@ end # macro
17341718
(.^)(A::SparseMatrixCSC, B::Number) =
17351719
B==0 ? sparse(ones(typeof(one(eltype(A)).^B), A.m, A.n)) :
17361720
SparseMatrixCSC(A.m, A.n, copy(A.colptr), copy(A.rowval), A.nzval .^ B)
1737-
(.^)(::Irrational{:e}, B::SparseMatrixCSC) = exp(B)
1721+
(.^)(::Irrational{:e}, B::SparseMatrixCSC) = exp.(B)
17381722
(.^)(A::Number, B::SparseMatrixCSC) = (.^)(A, full(B))
17391723
(.^)(A::SparseMatrixCSC, B::Array) = (.^)(full(A), B)
17401724
(.^)(A::Array, B::SparseMatrixCSC) = (.^)(A, full(B))

test/sparsedir/sparse.jl

+40-27
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ end
286286

287287
# conj
288288
cA = sprandn(5,5,0.2) + im*sprandn(5,5,0.2)
289-
@test full(conj(cA)) == conj(full(cA))
289+
@test full(conj.(cA)) == conj(full(cA))
290290

291291
# Test SparseMatrixCSC [c]transpose[!] and permute[!] methods
292292
let smalldim = 5, largedim = 10, nzprob = 0.4
@@ -461,22 +461,47 @@ end
461461
@test maximum(sparse(-ones(3,3))) == -1
462462
@test minimum(sparse(ones(3,3))) == 1
463463

464-
# Unary functions
465-
a = sprand(5,15, 0.5)
466-
afull = full(a)
467-
for op in (:sin, :cos, :tan, :ceil, :floor, :abs, :abs2)
468-
@eval begin
469-
@test ($op)(afull) == full($(op)(a))
470-
end
471-
end
472-
473-
for op in (:ceil, :floor)
474-
@eval begin
475-
@test ($op)(Int,afull) == full($(op)(Int,a))
464+
# Test unary functions with specialized broadcast over SparseMatrixCSCs
465+
let
466+
A = sprand(5, 15, 0.5)
467+
C = A + im*A
468+
Afull = full(A)
469+
Cfull = full(C)
470+
# Test representatives of [unary functions that map zeros to zeros and may map nonzeros to zeros]
471+
@test sin.(Afull) == full(sin.(A))
472+
@test tan.(Afull) == full(tan.(A)) # should be redundant with sin test
473+
@test ceil.(Afull) == full(ceil.(A))
474+
@test floor.(Afull) == full(floor.(A)) # should be redundant with ceil test
475+
@test real.(Afull) == full(real.(A))
476+
@test imag.(Afull) == full(imag.(A))
477+
@test real.(Cfull) == full(real.(C))
478+
@test imag.(Cfull) == full(imag.(C))
479+
# Test representatives of [unary functions that map zeros to zeros and nonzeros to nonzeros]
480+
@test expm1.(Afull) == full(expm1.(A))
481+
@test abs.(Afull) == full(abs.(A))
482+
@test abs2.(Afull) == full(abs2.(A))
483+
@test abs.(Cfull) == full(abs.(C))
484+
@test abs2.(Cfull) == full(abs2.(C))
485+
# Test representatives of [unary functions that map both zeros and nonzeros to nonzeros]
486+
@test cos.(Afull) == full(cos.(A))
487+
# Test representatives of remaining vectorized-nonbroadcast unary functions
488+
@test ceil(Int, Afull) == full(ceil(Int, A))
489+
@test floor(Int, Afull) == full(floor(Int, A))
490+
# Tests of real, imag, abs, and abs2 for SparseMatrixCSC{Int,X}s previously elsewhere
491+
for T in (Int, Float16, Float32, Float64, BigInt, BigFloat)
492+
R = rand(T[1:100;], 2, 2)
493+
I = rand(T[1:100;], 2, 2)
494+
D = R + I*im
495+
S = sparse(D)
496+
@test R == real.(S)
497+
@test I == imag.(S)
498+
@test real.(sparse(R)) == R
499+
@test nnz(imag.(sparse(R))) == 0
500+
@test abs.(S) == abs(D)
501+
@test abs2.(S) == abs2(D)
476502
end
477503
end
478504

479-
480505
# getindex tests
481506
ni = 23
482507
nj = 32
@@ -872,7 +897,7 @@ end
872897
@test_throws ArgumentError sparsevec(Dict(-1=>1,1=>2))
873898

874899
# issue #8976
875-
@test conj(sparse([1im])) == sparse(conj([1im]))
900+
@test conj.(sparse([1im])) == sparse(conj([1im]))
876901
@test conj!(sparse([1im])) == sparse(conj!([1im]))
877902

878903
# issue #9525
@@ -1038,18 +1063,6 @@ end
10381063
x = speye(100)
10391064
@test_throws BoundsError x[-10:10]
10401065

1041-
for T in (Int, Float16, Float32, Float64, BigInt, BigFloat)
1042-
let R=rand(T[1:100;],2,2), I=rand(T[1:100;],2,2)
1043-
D = R + I*im
1044-
S = sparse(D)
1045-
@test R == real(S)
1046-
@test I == imag(S)
1047-
@test real(sparse(R)) == R
1048-
@test nnz(imag(sparse(R))) == 0
1049-
@test abs(S) == abs(D)
1050-
@test abs2(S) == abs2(D)
1051-
end
1052-
end
10531066

10541067
# issue #10407
10551068
@test maximum(spzeros(5, 5)) == 0.0

0 commit comments

Comments
 (0)