Skip to content

Commit c73feea

Browse files
committed
Merge pull request #15423 from martinholters/sparsecopy
Specialized copy! for sparse matrices and vectors
2 parents ad101c9 + e0e9d4c commit c73feea

File tree

4 files changed

+198
-13
lines changed

4 files changed

+198
-13
lines changed

base/sparse/sparsematrix.jl

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,20 +193,51 @@ end
193193
copy(S::SparseMatrixCSC) =
194194
SparseMatrixCSC(S.m, S.n, copy(S.colptr), copy(S.rowval), copy(S.nzval))
195195

196-
function copy!{TvA, TiA, TvB, TiB}(A::SparseMatrixCSC{TvA,TiA},
197-
B::SparseMatrixCSC{TvB,TiB})
198-
# If the two matrices have the same size then all the
199-
# elements in A will be overwritten and we can simply copy the
200-
# internal fields of B to A.
201-
if size(A) == size(B)
202-
copy!(A.colptr, B.colptr)
196+
function copy!(A::SparseMatrixCSC, B::SparseMatrixCSC)
197+
# If the two matrices have the same length then all the
198+
# elements in A will be overwritten.
199+
if length(A) == length(B)
203200
resize!(A.nzval, length(B.nzval))
204201
resize!(A.rowval, length(B.rowval))
205-
copy!(A.rowval, B.rowval)
206-
copy!(A.nzval, B.nzval)
202+
if size(A) == size(B)
203+
# Simple case: we can simply copy the internal fields of B to A.
204+
copy!(A.colptr, B.colptr)
205+
copy!(A.rowval, B.rowval)
206+
else
207+
# This is like a "reshape B into A".
208+
sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, A.n, B.colptr, B.rowval, B.m, B.n)
209+
end
207210
else
208-
invoke(Base.copy!, Tuple{AbstractMatrix{TvA}, AbstractMatrix{TvB}}, A, B)
211+
length(A) >= length(B) || throw(BoundsError())
212+
lB = length(B)
213+
nnzA = nnz(A)
214+
nnzB = nnz(B)
215+
# Up to which col, row, and ptr in rowval/nzval will A be overwritten?
216+
lastmodcolA = div(lB - 1, A.m) + 1
217+
lastmodrowA = mod(lB - 1, A.m) + 1
218+
lastmodptrA = A.colptr[lastmodcolA]
219+
while lastmodptrA < A.colptr[lastmodcolA+1] && A.rowval[lastmodptrA] <= lastmodrowA
220+
lastmodptrA += 1
221+
end
222+
lastmodptrA -= 1
223+
if lastmodptrA >= nnzB
224+
# A will have fewer non-zero elements; unmodified elements are kept at the end.
225+
deleteat!(A.rowval, nnzB+1:lastmodptrA)
226+
deleteat!(A.nzval, nnzB+1:lastmodptrA)
227+
else
228+
# A will have more non-zero elements; unmodified elements are kept at the end.
229+
resize!(A.rowval, nnzB + nnzA - lastmodptrA)
230+
resize!(A.nzval, nnzB + nnzA - lastmodptrA)
231+
copy!(A.rowval, nnzB+1, A.rowval, lastmodptrA+1, nnzA-lastmodptrA)
232+
copy!(A.nzval, nnzB+1, A.nzval, lastmodptrA+1, nnzA-lastmodptrA)
233+
end
234+
# Adjust colptr accordingly.
235+
@inbounds for i in 2:length(A.colptr)
236+
A.colptr[i] += nnzB - lastmodptrA
237+
end
238+
sparse_compute_reshaped_colptr_and_rowval(A.colptr, A.rowval, A.m, lastmodcolA-1, B.colptr, B.rowval, B.m, B.n)
209239
end
240+
copy!(A.nzval, B.nzval)
210241
return A
211242
end
212243

base/sparse/sparsevector.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,60 @@ convert{Tv,TvS,TiS}(::Type{SparseVector{Tv}}, s::SparseVector{TvS,TiS}) =
320320
SparseVector{Tv,TiS}(s.n, s.nzind, convert(Vector{Tv}, s.nzval))
321321

322322

323+
### copying
324+
function prep_sparsevec_copy_dest!(A::SparseVector, lB, nnzB)
325+
lA = length(A)
326+
lA >= lB || throw(BoundsError())
327+
# If the two vectors have the same length then all the elements in A will be overwritten.
328+
if length(A) == lB
329+
resize!(A.nzval, nnzB)
330+
resize!(A.nzind, nnzB)
331+
else
332+
nnzA = nnz(A)
333+
334+
lastmodindA = searchsortedlast(A.nzind, lB)
335+
if lastmodindA >= nnzB
336+
# A will have fewer non-zero elements; unmodified elements are kept at the end.
337+
deleteat!(A.nzind, nnzB+1:lastmodindA)
338+
deleteat!(A.nzval, nnzB+1:lastmodindA)
339+
else
340+
# A will have more non-zero elements; unmodified elements are kept at the end.
341+
resize!(A.nzind, nnzB + nnzA - lastmodindA)
342+
resize!(A.nzval, nnzB + nnzA - lastmodindA)
343+
copy!(A.nzind, nnzB+1, A.nzind, lastmodindA+1, nnzA-lastmodindA)
344+
copy!(A.nzval, nnzB+1, A.nzval, lastmodindA+1, nnzA-lastmodindA)
345+
end
346+
end
347+
end
348+
349+
function copy!(A::SparseVector, B::SparseVector)
350+
prep_sparsevec_copy_dest!(A, length(B), nnz(B))
351+
copy!(A.nzind, B.nzind)
352+
copy!(A.nzval, B.nzval)
353+
return A
354+
end
355+
356+
function copy!(A::SparseVector, B::SparseMatrixCSC)
357+
prep_sparsevec_copy_dest!(A, length(B), nnz(B))
358+
359+
ptr = 1
360+
@assert length(A.nzind) >= length(B.rowval)
361+
maximum(B.colptr)-1 <= length(B.rowval) || throw(BoundsError())
362+
@inbounds for col=1:length(B.colptr)-1
363+
offsetA = (col - 1) * B.m
364+
while ptr <= B.colptr[col+1]-1
365+
A.nzind[ptr] = B.rowval[ptr] + offsetA
366+
ptr += 1
367+
end
368+
end
369+
copy!(A.nzval, B.nzval)
370+
return A
371+
end
372+
373+
copy!{TvB, TiB}(A::SparseMatrixCSC, B::SparseVector{TvB,TiB}) =
374+
copy!(A, SparseMatrixCSC{TvB,TiB}(B.n, 1, TiB[1, length(B.nzind)+1], B.nzind, B.nzval))
375+
376+
323377
### Rand Construction
324378
sprand{T}(n::Integer, p::AbstractFloat, rfn::Function, ::Type{T}) = sprand(GLOBAL_RNG, n, p, rfn, T)
325379
function sprand{T}(r::AbstractRNG, n::Integer, p::AbstractFloat, rfn::Function, ::Type{T})

test/sparsedir/sparse.jl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,14 +253,30 @@ let
253253
@test pointer(A.nzval) != pointer(B.nzval)
254254
@test pointer(A.rowval) != pointer(B.rowval)
255255
@test pointer(A.colptr) != pointer(B.colptr)
256-
# Test size(A) != size(B)
257-
B = sprand(3, 3, 0.2)
256+
# Test size(A) != size(B), but length(A) == length(B)
257+
B = sprand(25, 1, 0.2)
258258
copy!(A, B)
259-
@test A[1:9] == B[:]
259+
@test A[:] == B[:]
260+
# Test various size(A) / size(B) combinations
261+
for mA in [5, 10, 20], nA in [5, 10, 20], mB in [5, 10, 20], nB in [5, 10, 20]
262+
A = sprand(mA,nA,0.4)
263+
Aorig = copy(A)
264+
B = sprand(mB,nB,0.4)
265+
if mA*nA >= mB*nB
266+
copy!(A,B)
267+
@assert(A[1:length(B)] == B[:])
268+
@assert(A[length(B)+1:end] == Aorig[length(B)+1:end])
269+
else
270+
@test_throws BoundsError copy!(A,B)
271+
end
272+
end
260273
# Test eltype(A) != eltype(B), size(A) != size(B)
274+
A = sprand(5, 5, 0.2)
275+
Aorig = copy(A)
261276
B = sparse(rand(Float32, 3, 3))
262277
copy!(A, B)
263278
@test A[1:9] == B[:]
279+
@test A[10:end] == Aorig[10:end]
264280
# Test eltype(A) != eltype(B), size(A) == size(B)
265281
A = sparse(rand(Float64, 3, 3))
266282
B = sparse(rand(Float32, 3, 3))

test/sparsedir/sparsevector.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,90 @@ let a = SparseVector(8, [2, 5, 6], Int32[12, 35, 72])
243243
@test sparsevec(ctranspose(ctranspose(acp))) == acp
244244
end
245245

246+
let x1 = SparseVector(8, [2, 5, 6], [12.2, 1.4, 5.0])
247+
x2 = SparseVector(8, [3, 4], [1.2, 3.4])
248+
copy!(x2, x1)
249+
@test x2 == x1
250+
x2 = SparseVector(8, [2, 4, 8], [10.3, 7.4, 3.1])
251+
copy!(x2, x1)
252+
@test x2 == x1
253+
x2 = SparseVector(8, [1, 3, 4, 7], [0.3, 1.2, 3.4, 0.1])
254+
copy!(x2, x1)
255+
@test x2 == x1
256+
x2 = SparseVector(10, [3, 4], [1.2, 3.4])
257+
copy!(x2, x1)
258+
@test x2[1:8] == x1
259+
@test x2[9:10] == spzeros(2)
260+
x2 = SparseVector(10, [3, 4, 9], [1.2, 3.4, 17.8])
261+
copy!(x2, x1)
262+
@test x2[1:8] == x1
263+
@test x2[9] == 17.8
264+
@test x2[10] == 0
265+
x2 = SparseVector(10, [3, 4, 5, 6, 9], [8.3, 7.2, 1.2, 3.4, 17.8])
266+
copy!(x2, x1)
267+
@test x2[1:8] == x1
268+
@test x2[9] == 17.8
269+
@test x2[10] == 0
270+
x2 = SparseVector(6, [3, 4], [1.2, 3.4])
271+
@test_throws BoundsError copy!(x2, x1)
272+
end
273+
274+
let x1 = sparse([2, 1, 2], [1, 3, 3], [12.2, 1.4, 5.0], 2, 4)
275+
x2 = SparseVector(8, [3, 4], [1.2, 3.4])
276+
copy!(x2, x1)
277+
@test x2[:] == x1[:]
278+
x2 = SparseVector(8, [2, 4, 8], [10.3, 7.4, 3.1])
279+
copy!(x2, x1)
280+
@test x2[:] == x1[:]
281+
x2 = SparseVector(8, [1, 3, 4, 7], [0.3, 1.2, 3.4, 0.1])
282+
copy!(x2, x1)
283+
@test x2[:] == x1[:]
284+
x2 = SparseVector(10, [3, 4], [1.2, 3.4])
285+
copy!(x2, x1)
286+
@test x2[1:8] == x1[:]
287+
@test x2[9:10] == spzeros(2)
288+
x2 = SparseVector(10, [3, 4, 9], [1.2, 3.4, 17.8])
289+
copy!(x2, x1)
290+
@test x2[1:8] == x1[:]
291+
@test x2[9] == 17.8
292+
@test x2[10] == 0
293+
x2 = SparseVector(10, [3, 4, 5, 6, 9], [8.3, 7.2, 1.2, 3.4, 17.8])
294+
copy!(x2, x1)
295+
@test x2[1:8] == x1[:]
296+
@test x2[9] == 17.8
297+
@test x2[10] == 0
298+
x2 = SparseVector(6, [3, 4], [1.2, 3.4])
299+
@test_throws BoundsError copy!(x2, x1)
300+
end
301+
302+
let x1 = SparseVector(8, [2, 5, 6], [12.2, 1.4, 5.0])
303+
x2 = sparse([1, 2], [2, 2], [1.2, 3.4], 2, 4)
304+
copy!(x2, x1)
305+
@test x2[:] == x1[:]
306+
x2 = sparse([2, 2, 2], [1, 3, 4], [10.3, 7.4, 3.1], 2, 4)
307+
copy!(x2, x1)
308+
@test x2[:] == x1[:]
309+
x2 = sparse([1, 1, 2, 1], [1, 2, 2, 4], [0.3, 1.2, 3.4, 0.1], 2, 4)
310+
copy!(x2, x1)
311+
@test x2[:] == x1[:]
312+
x2 = sparse([1, 2], [2, 2], [1.2, 3.4], 2, 5)
313+
copy!(x2, x1)
314+
@test x2[1:8] == x1
315+
@test x2[9:10] == spzeros(2)
316+
x2 = sparse([1, 2, 1], [2, 2, 5], [1.2, 3.4, 17.8], 2, 5)
317+
copy!(x2, x1)
318+
@test x2[1:8] == x1
319+
@test x2[9] == 17.8
320+
@test x2[10] == 0
321+
x2 = sparse([1, 2, 1, 2, 1], [2, 2, 3, 3, 5], [8.3, 7.2, 1.2, 3.4, 17.8], 2, 5)
322+
copy!(x2, x1)
323+
@test x2[1:8] == x1
324+
@test x2[9] == 17.8
325+
@test x2[10] == 0
326+
x2 = sparse([1, 2], [2, 2], [1.2, 3.4], 2, 3)
327+
@test_throws BoundsError copy!(x2, x1)
328+
end
329+
246330
### Type conversion
247331

248332
let x = convert(SparseVector, sparse([2, 5, 6], [1, 1, 1], [1.25, -0.75, 3.5], 8, 1))

0 commit comments

Comments
 (0)