Skip to content

Commit 0c539a9

Browse files
committed
Test generic sparse map[!]/broadcast[!] for sparse vectors/matrices.
Condense and systematize existing tests for generic sparse map[!]/broadcast[!], and extend to sparse vectors and vector/matrix combinations. Relocate new test code to a separate file test/sparse/higherorderfns.jl corresponding to base/sparse/higherorderfns.jl. (Test/sparse/sparsevector.jl is hypothetically confined to SparseVectors, and test/sparse/sparse.jl mostly dedicated to SparseMatrixCSCs.) Move older tests of sparse broadcast[!] into that new file as well.
1 parent 8a19b56 commit 0c539a9

File tree

3 files changed

+255
-238
lines changed

3 files changed

+255
-238
lines changed

test/choosetests.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function choosetests(choices = [])
102102
end
103103

104104

105-
sparsetests = ["sparse/sparse", "sparse/sparsevector"]
105+
sparsetests = ["sparse/sparse", "sparse/sparsevector", "sparse/higherorderfns"]
106106
if Base.USE_GPL_LIBS
107107
append!(sparsetests, ["sparse/umfpack", "sparse/cholmod", "sparse/spqr"])
108108
end

test/sparse/higherorderfns.jl

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# This file is a part of Julia. License is MIT: http://julialang.org/license
2+
#
3+
# These tests cover the higher order functions specialized for sparse arrays defined in
4+
# base/sparse/higherorderfns.jl, particularly map[!]/broadcast[!] for SparseVectors and
5+
# SparseMatrixCSCs at present.
6+
7+
@testset "map[!] implementation specialized for a single (input) sparse vector/matrix" begin
8+
N, M = 10, 12
9+
# (also the implementation for broadcast[!] over a single (input) sparse vector/matrix)
10+
for shapeA in ((N,), (N, M))
11+
A = sprand(shapeA..., 0.4); fA = Array(A)
12+
# --> test map entry point
13+
@test map(sin, A) == sparse(map(sin, fA))
14+
@test map(cos, A) == sparse(map(cos, fA))
15+
# --> test map! entry point
16+
fX = copy(fA); X = sparse(fX)
17+
map!(sin, X, A); X = sparse(fX) # warmup for @allocated
18+
@test (@allocated map!(sin, X, A)) == 0
19+
@test map!(sin, X, A) == sparse(map!(sin, fX, fA))
20+
@test map!(cos, X, A) == sparse(map!(cos, fX, fA))
21+
@test_throws DimensionMismatch map!(sin, X, spzeros((shapeA .- 1)...))
22+
end
23+
end
24+
25+
@testset "map[!] implementation specialized for a pair of (input) sparse vectors/matrices" begin
26+
N, M = 10, 12
27+
f(x, y) = x + y + 1
28+
for shapeA in ((N,), (N, M))
29+
A, Bo = sprand(shapeA..., 0.3), sprand(shapeA..., 0.3)
30+
B = ndims(Bo) == 1 ? SparseVector{Float32, Int32}(Bo) : SparseMatrixCSC{Float32,Int32}(Bo)
31+
# use different types to check internal type stability via allocation tests below
32+
fA, fB = map(Array, (A, B))
33+
# --> test map entry point
34+
@test map(+, A, B) == sparse(map(+, fA, fB))
35+
@test map(*, A, B) == sparse(map(*, fA, fB))
36+
@test map(f, A, B) == sparse(map(f, fA, fB))
37+
@test_throws DimensionMismatch map(+, A, spzeros((shapeA .- 1)...))
38+
# --> test map! entry point
39+
fX = map(+, fA, fB); X = sparse(fX)
40+
map!(+, X, A, B); X = sparse(fX) # warmup for @allocated
41+
@test (@allocated map!(+, X, A, B)) == 0
42+
@test map!(+, X, A, B) == sparse(map!(+, fX, fA, fB))
43+
fX = map(*, fA, fB); X = sparse(fX)
44+
map!(*, X, A, B); X = sparse(fX) # warmup for @allocated
45+
@test (@allocated map!(*, X, A, B)) == 0
46+
@test map!(*, X, A, B) == sparse(map!(*, fX, fA, fB))
47+
@test map!(f, X, A, B) == sparse(map!(f, fX, fA, fB))
48+
@test_throws DimensionMismatch map!(f, X, A, spzeros((shapeA .- 1)...))
49+
end
50+
end
51+
52+
@testset "map[!] implementation capable of handling >2 (input) sparse vectors/matrices" begin
53+
N, M = 10, 12
54+
f(x, y, z) = x + y + z + 1
55+
for shapeA in ((N,), (N, M))
56+
A, B, Co = sprand(shapeA..., 0.2), sprand(shapeA..., 0.2), sprand(shapeA..., 0.2)
57+
C = ndims(Co) == 1 ? SparseVector{Float32,Int32}(Co) : SparseMatrixCSC{Float32,Int32}(Co)
58+
# use different types to check internal type stability via allocation tests below
59+
fA, fB, fC = map(Array, (A, B, C))
60+
# --> test map entry point
61+
@test map(+, A, B, C) == sparse(map(+, fA, fB, fC))
62+
@test map(*, A, B, C) == sparse(map(*, fA, fB, fC))
63+
@test map(f, A, B, C) == sparse(map(f, fA, fB, fC))
64+
@test_throws DimensionMismatch map(+, A, B, spzeros(N, M - 1))
65+
# --> test map! entry point
66+
fX = map(+, fA, fB, fC); X = sparse(fX)
67+
map!(+, X, A, B, C); X = sparse(fX) # warmup for @allocated
68+
@test (@allocated map!(+, X, A, B, C)) == 0
69+
@test map!(+, X, A, B, C) == sparse(map!(+, fX, fA, fB, fC))
70+
fX = map(*, fA, fB, fC); X = sparse(fX)
71+
map!(*, X, A, B, C); X = sparse(fX) # warmup for @allocated
72+
@test (@allocated map!(*, X, A, B, C)) == 0
73+
@test map!(*, X, A, B, C) == sparse(map!(*, fX, fA, fB, fC))
74+
@test map!(f, X, A, B, C) == sparse(map!(f, fX, fA, fB, fC))
75+
@test_throws DimensionMismatch map!(f, X, A, B, spzeros((shapeA .- 1)...))
76+
end
77+
end
78+
79+
@testset "broadcast[!] implementation specialized for a single (input) sparse vector/matrix" begin
80+
# broadcast[!] for a single sparse vector/matrix falls back to map[!], tested extensively
81+
# above. here we simply lightly exercise the relevant broadcast[!] entry points.
82+
N, M, p = 10, 12, 0.4
83+
a, A = sprand(N, p), sprand(N, M, p)
84+
fa, fA = Array(a), Array(A)
85+
@test broadcast(sin, a) == sparse(broadcast(sin, fa))
86+
@test broadcast(sin, A) == sparse(broadcast(sin, fA))
87+
@test broadcast!(sin, copy(a), a) == sparse(broadcast!(sin, copy(fa), fa))
88+
@test broadcast!(sin, copy(A), A) == sparse(broadcast!(sin, copy(fA), fA))
89+
end
90+
91+
@testset "broadcast[!] implementation specialized for pairs of (input) sparse vectors/matrices" begin
92+
N, M, p = 10, 12, 0.3
93+
f(x, y) = x + y + 1
94+
mats = (sprand(N, M, p), sprand(N, 1, p), sprand(1, M, p), sprand(1, 1, 1.0), spzeros(1, 1))
95+
vecs = (sprand(N, p), sprand(1, 1.0), spzeros(1))
96+
tens = (mats..., vecs...)
97+
for Xo in tens
98+
X = ndims(Xo) == 1 ? SparseVector{Float32,Int32}(Xo) : SparseMatrixCSC{Float32,Int32}(Xo)
99+
# use different types to check internal type stability via allocation tests below
100+
shapeX, fX = size(X), Array(X)
101+
for Y in tens
102+
fY = Array(Y)
103+
# --> test broadcast entry point
104+
@test broadcast(+, X, Y) == sparse(broadcast(+, fX, fY))
105+
@test broadcast(*, X, Y) == sparse(broadcast(*, fX, fY))
106+
@test broadcast(f, X, Y) == sparse(broadcast(f, fX, fY))
107+
try Base.Broadcast.broadcast_indices(spzeros((shapeX .- 1)...), Y)
108+
catch @test_throws DimensionMismatch broadcast(+, spzeros((shapeX .- 1)...), Y) end
109+
# --> test broadcast! entry point / +-like zero-preserving op
110+
fZ = broadcast(+, fX, fY); Z = sparse(fZ)
111+
broadcast!(+, Z, X, Y); Z = sparse(fZ) # warmup for @allocated
112+
@test (@allocated broadcast!(+, Z, X, Y)) == 0
113+
@test broadcast!(+, Z, X, Y) == sparse(broadcast!(+, fZ, fX, fY))
114+
# --> test broadcast! entry point / *-like zero-preserving op
115+
fZ = broadcast(*, fX, fY); Z = sparse(fZ)
116+
broadcast!(*, Z, X, Y); Z = sparse(fZ) # warmup for @allocated
117+
@test (@allocated broadcast!(*, Z, X, Y)) == 0
118+
@test broadcast!(*, Z, X, Y) == sparse(broadcast!(*, fZ, fX, fY))
119+
# --> test broadcast! entry point / not zero-preserving op
120+
fZ = broadcast(f, fX, fY); Z = sparse(fZ)
121+
broadcast!(f, Z, X, Y); Z = sparse(fZ) # warmup for @allocated
122+
@test (@allocated broadcast!(f, Z, X, Y)) == 0
123+
@test broadcast!(f, Z, X, Y) == sparse(broadcast!(f, fZ, fX, fY))
124+
# --> test shape checks for both braodcast and broadcast! entry points
125+
try Base.Broadcast.check_broadcast_indices(indices(Z), spzeros((shapeX .- 1)...), Y)
126+
catch @test_throws DimensionMismatch broadcast!(f, Z, spzeros((shapeX .- 1)...), Y) end
127+
end
128+
end
129+
end
130+
131+
@testset "broadcast[!] implementation capable of handling >2 (input) sparse vectors/matrices" begin
132+
N, M, p = 10, 12, 0.3
133+
f(x, y, z) = x + y + z + 1
134+
mats = (sprand(N, M, p), sprand(N, 1, p), sprand(1, M, p), sprand(1, 1, 1.0), spzeros(1, 1))
135+
vecs = (sprand(N, p), sprand(1, 1.0), spzeros(1))
136+
tens = (mats..., vecs...)
137+
for Xo in tens
138+
X = ndims(Xo) == 1 ? SparseVector{Float32,Int32}(Xo) : SparseMatrixCSC{Float32,Int32}(Xo)
139+
# use different types to check internal type stability via allocation tests below
140+
shapeX, fX = size(X), Array(X)
141+
for Y in tens, Z in tens
142+
fY, fZ = Array(Y), Array(Z)
143+
# --> test broadcast entry point
144+
@test broadcast(+, X, Y, Z) == sparse(broadcast(+, fX, fY, fZ))
145+
@test broadcast(*, X, Y, Z) == sparse(broadcast(*, fX, fY, fZ))
146+
@test broadcast(f, X, Y, Z) == sparse(broadcast(f, fX, fY, fZ))
147+
try Base.Broadcast.broadcast_indices(spzeros((shapeX .- 1)...), Y, Z)
148+
catch @test_throws DimensionMismatch broadcast(+, spzeros((shapeX .- 1)...), Y, Z) end
149+
# --> test broadcast! entry point / +-like zero-preserving op
150+
fQ = broadcast(+, fX, fY, fZ); Q = sparse(fQ)
151+
broadcast!(+, Q, X, Y, Z); Q = sparse(fQ) # warmup for @allocated
152+
@test (@allocated broadcast!(+, Q, X, Y, Z)) == 0
153+
@test broadcast!(+, Q, X, Y, Z) == sparse(broadcast!(+, fQ, fX, fY, fZ))
154+
# --> test broadcast! entry point / *-like zero-preserving op
155+
fQ = broadcast(*, fX, fY, fZ); Q = sparse(fQ)
156+
broadcast!(*, Q, X, Y, Z); Q = sparse(fQ) # warmup for @allocated
157+
@test (@allocated broadcast!(*, Q, X, Y, Z)) == 0
158+
@test broadcast!(*, Q, X, Y, Z) == sparse(broadcast!(*, fQ, fX, fY, fZ))
159+
# --> test broadcast! entry point / not zero-preserving op
160+
fQ = broadcast(f, fX, fY, fZ); Q = sparse(fQ)
161+
broadcast!(f, Q, X, Y, Z); Q = sparse(fQ) # warmup for @allocated
162+
@test_broken (@allocated broadcast!(f, Z, X, Y)) == 0
163+
# the preceding test allocates 16 bytes in the entry point for broadcast!, but
164+
# none of the earlier tests of the same code path allocate. no allocation shows
165+
# up with --track-allocation=user. allocation shows up on the first line of the
166+
# entry point for broadcast! with --track-allocation=all, but that first line
167+
# almost certainly should not allocate. so not certain what's going on.
168+
@test broadcast!(f, Q, X, Y, Z) == sparse(broadcast!(f, fQ, fX, fY, fZ))
169+
# --> test shape checks for both braodcast and broadcast! entry points
170+
try Base.Broadcast.check_broadcast_indices(indices(Q), spzeros((shapeX .- 1)...), Y, Z)
171+
catch @test_throws DimensionMismatch broadcast!(f, Q, spzeros((shapeX .- 1)...), Y, Z) end
172+
end
173+
end
174+
end
175+
176+
# Older tests of sparse broadcast, now largely covered by the tests above
177+
@testset "assorted tests of sparse broadcast over two input arguments" begin
178+
N, p = 10, 0.3
179+
A, B, CF = sprand(N, N, p), sprand(N, N, p), rand(N, N)
180+
AF, BF, C = Array(A), Array(B), sparse(CF)
181+
182+
@test A .* B == AF .* BF
183+
@test A[1,:] .* B == AF[1,:] .* BF
184+
@test A[:,1] .* B == AF[:,1] .* BF
185+
@test A .* B[1,:] == AF .* BF[1,:]
186+
@test A .* B[:,1] == AF .* BF[:,1]
187+
188+
@test A .* B == AF .* BF
189+
@test A[1,:] .* BF == AF[1,:] .* BF
190+
@test A[:,1] .* BF == AF[:,1] .* BF
191+
@test A .* BF[1,:] == AF .* BF[1,:]
192+
@test A .* BF[:,1] == AF .* BF[:,1]
193+
194+
@test A .* B == AF .* BF
195+
@test AF[1,:] .* B == AF[1,:] .* BF
196+
@test AF[:,1] .* B == AF[:,1] .* BF
197+
@test AF .* B[1,:] == AF .* BF[1,:]
198+
@test AF .* B[:,1] == AF .* BF[:,1]
199+
200+
@test A .* B == AF .* BF
201+
@test A[1,:] .* B == AF[1,:] .* BF
202+
@test A[:,1] .* B == AF[:,1] .* BF
203+
@test A .* B[1,:] == AF .* BF[1,:]
204+
@test A .* B[:,1] == AF .* BF[:,1]
205+
206+
@test A .* 3 == AF .* 3
207+
@test 3 .* A == 3 .* AF
208+
#@test A[1,:] .* 3 == AF[1,:] .* 3
209+
@test all(A[1,:] .* 3 .== AF[1,:] .* 3)
210+
#@test A[:,1] .* 3 == AF[:,1] .* 3
211+
@test all(A[:,1] .* 3 .== AF[:,1] .* 3)
212+
#TODO: simple comparation with == returns false because the left side is a (two-dimensional) SparseMatrixCSC
213+
# while the right side is a Vector
214+
215+
@test A .- 3 == AF .- 3
216+
@test 3 .- A == 3 .- AF
217+
@test A .- B == AF .- BF
218+
@test A - AF == zeros(AF)
219+
@test AF - A == zeros(AF)
220+
@test A[1,:] .- B == AF[1,:] .- BF
221+
@test A[:,1] .- B == AF[:,1] .- BF
222+
@test A .- B[1,:] == AF .- BF[1,:]
223+
@test A .- B[:,1] == AF .- BF[:,1]
224+
225+
@test A .+ 3 == AF .+ 3
226+
@test 3 .+ A == 3 .+ AF
227+
@test A .+ B == AF .+ BF
228+
@test A + AF == AF + A
229+
@test (A .< B) == (AF .< BF)
230+
@test (A .!= B) == (AF .!= BF)
231+
232+
@test A ./ 3 == AF ./ 3
233+
@test A .\ 3 == AF .\ 3
234+
@test 3 ./ A == 3 ./ AF
235+
@test 3 .\ A == 3 .\ AF
236+
@test A .\ C == AF .\ CF
237+
@test A ./ C == AF ./ CF
238+
@test A ./ CF[:,1] == AF ./ CF[:,1]
239+
@test A .\ CF[:,1] == AF .\ CF[:,1]
240+
@test BF ./ C == BF ./ CF
241+
@test BF .\ C == BF .\ CF
242+
243+
@test A .^ 3 == AF .^ 3
244+
@test 3 .^ A == 3 .^ AF
245+
@test A .^ BF[:,1] == AF .^ BF[:,1]
246+
@test BF[:,1] .^ A == BF[:,1] .^ AF
247+
248+
@test spzeros(0,0) + spzeros(0,0) == zeros(0,0)
249+
@test spzeros(0,0) * spzeros(0,0) == zeros(0,0)
250+
@test spzeros(1,0) .+ spzeros(2,1) == zeros(2,0)
251+
@test spzeros(1,0) .* spzeros(2,1) == zeros(2,0)
252+
@test spzeros(1,2) .+ spzeros(0,1) == zeros(0,2)
253+
@test spzeros(1,2) .* spzeros(0,1) == zeros(0,2)
254+
end

0 commit comments

Comments
 (0)