Skip to content

Commit 84ba361

Browse files
authored
Merge pull request #12 from tkoolen/broadcast
Add `broadcast!` with `AbstractVector` destination
2 parents 622daf7 + ce829ab commit 84ba361

File tree

4 files changed

+163
-15
lines changed

4 files changed

+163
-15
lines changed

.travis.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@ notifications:
1515
git:
1616
depth: 99999999
1717

18-
## uncomment the following lines to allow failures on nightly julia
19-
## (tests will run but not make your overall status red)
20-
#matrix:
21-
# allow_failures:
22-
# - julia: nightly
18+
matrix:
19+
allow_failures:
20+
- julia: nightly
2321

2422
## uncomment and modify the following lines to manually install system packages
2523
#addons:

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Build Status](https://travis-ci.org/tkoolen/TypeSortedCollections.jl.svg?branch=master)](https://travis-ci.org/tkoolen/TypeSortedCollections.jl)
44
[![codecov.io](http://codecov.io/github/tkoolen/TypeSortedCollections.jl/coverage.svg?branch=master)](http://codecov.io/github/tkoolen/TypeSortedCollections.jl?branch=master)
55

6-
TypeSortedCollections provides the `TypeSortedCollection` type, which can be used to store type-heterogeneous data in a way that allows operations on the data to be performed in a type-stable manner. It does so by sorting a type-heterogeneous input collection by type upon construction, and storing these elements in a `Tuple` of concretely typed `Vector`s, one for each type. TypeSortedCollections provides type stable methods for `map!`, `foreach`, and `mapreduce` that take at least one `TypeSortedCollection`.
6+
TypeSortedCollections provides the `TypeSortedCollection` type, which can be used to store type-heterogeneous data in a way that allows operations on the data to be performed in a type-stable manner. It does so by sorting a type-heterogeneous input collection by type upon construction, and storing these elements in a `Tuple` of concretely typed `Vector`s, one for each type. TypeSortedCollections provides type stable methods for `map!`, `foreach`, `broadcast!`, and `mapreduce` that take at least one `TypeSortedCollection`.
77

88
An example:
99
```julia
@@ -40,7 +40,7 @@ Note that construction of a `TypeSortedCollection` is of course not type stable,
4040
See also [FunctionWrappers.jl](https://github.com/yuyichao/FunctionWrappers.jl) for a solution to the related problem of storing and calling multiple callables in a type-stable manner, and [Unrolled.jl](https://github.com/cstjean/Unrolled.jl) for a macro-based solution.
4141

4242
# Iteration order
43-
By default, `TypeSortedCollection`s do not preserve iteration order, in the sense that the order in which elements are processed in `map!`, `foreach`, and `mapreduce` will not be the same as if these functions were called on the original type-heterogeneous vector:
43+
By default, `TypeSortedCollection`s do not preserve iteration order, in the sense that the order in which elements are processed in `map!`, `foreach`, `broadcast!`, and `mapreduce` will not be the same as if these functions were called on the original type-heterogeneous vector:
4444
```julia
4545
julia> xs = Number[1.; 2; 3.];
4646

@@ -75,7 +75,7 @@ julia> results = similar(xs);
7575
julia> map!(identity, results, sortedxs) # results of applying `identity` end up in the right location
7676
3-element Array{Number,1}:
7777
1.0
78-
2
78+
2
7979
3.0
8080
```
8181

@@ -105,3 +105,24 @@ julia> map!(*, results, sortedxs, sortedys)
105105
9.0
106106
16.0
107107
```
108+
109+
# Broadcasting
110+
Broadcasting (in place) is implemented for `AbstractVector` return types:
111+
```julia
112+
julia> x = 4;
113+
114+
julia> ys = Number[1.; 2; 3];
115+
116+
julia> sortedys = TypeSortedCollection(ys);
117+
118+
julia> results = similar(ys, Float64);
119+
120+
julia> results .= x .* sortedys
121+
3-element Array{Float64,1}:
122+
4.0
123+
8.0
124+
12.0
125+
126+
julia> @allocated results .= x .* sortedys
127+
0
128+
```

src/TypeSortedCollections.jl

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,28 +91,35 @@ end
9191
Base.@pure num_types(::Type{<:TypeSortedCollection{<:Any, N}}) where {N} = N
9292
num_types(x::TypeSortedCollection) = num_types(typeof(x))
9393

94+
const TSCOrAbstractVector{N} = Union{<:TypeSortedCollection{<:Any, N}, AbstractVector}
95+
9496
Base.isempty(x::TypeSortedCollection) = all(isempty, x.data)
9597
Base.empty!(x::TypeSortedCollection) = foreach(empty!, x.data)
9698
Base.length(x::TypeSortedCollection) = mapreduce(length, +, 0, x.data)
9799
Base.indices(x::TypeSortedCollection) = x.indices # semantics are a little different from Array, but OK
98100

99101
# Trick from StaticArrays:
100-
@inline first_tsc(a1::TypeSortedCollection, as::Union{<:TypeSortedCollection, AbstractVector}...) = a1
101-
@inline first_tsc(a1, as::Union{<:TypeSortedCollection, AbstractVector}...) = first_tsc(as...)
102+
@inline first_tsc(a1::TypeSortedCollection, as...) = a1
103+
@inline first_tsc(a1, as...) = first_tsc(as...)
104+
105+
Base.@pure first_tsc_type(a1::Type{<:TypeSortedCollection}, as::Type...) = a1
106+
Base.@pure first_tsc_type(a1::Type, as::Type...) = first_tsc_type(as...)
102107

103108
# inspired by Base.ith_all
104109
@inline _getindex_all(::Val, j, vecindex) = ()
105110
Base.@propagate_inbounds _getindex_all(vali::Val{i}, j, vecindex, a1, as...) where {i} = (_getindex(vali, j, vecindex, a1), _getindex_all(vali, j, vecindex, as...)...)
111+
@inline _getindex(::Val, j, vecindex, a) = a # for anything that's not an AbstractVector or TypeSortedCollection, don't index (for use in broadcast!)
106112
@inline _getindex(::Val, j, vecindex, a::AbstractVector) = a[vecindex]
107113
@inline _getindex(::Val{i}, j, vecindex, a::TypeSortedCollection) where {i} = a.data[i][j]
108114
@inline _setindex!(::Val, j, vecindex, a::AbstractVector, val) = a[vecindex] = val
109115
@inline _setindex!(::Val{i}, j, vecindex, a::TypeSortedCollection, val) where {i} = a.data[i][j] = val
110116

111117
@inline lengths_match(a1) = true
112-
@inline lengths_match(a1, a2, as...) = length(a1) == length(a2) && lengths_match(a2, as...)
118+
@inline lengths_match(a1::TSCOrAbstractVector, a2::TSCOrAbstractVector, as...) = length(a1) == length(a2) && lengths_match(a2, as...)
119+
@inline lengths_match(a1::TSCOrAbstractVector, a2, as...) = lengths_match(a1, as...) # case: a2 is not indexable: skip it
113120
@noinline lengths_match_fail() = throw(DimensionMismatch("Lengths of input collections do not match."))
114121

115-
@inline indices_match(::Val, indices::Vector{Int}, ::AbstractVector) = true
122+
@inline indices_match(::Val, indices::Vector{Int}, ::Any) = true
116123
@inline function indices_match(::Val{i}, indices::Vector{Int}, tsc::TypeSortedCollection) where {i}
117124
tsc_indices = tsc.indices[i]
118125
length(indices) == length(tsc_indices) || return false
@@ -124,7 +131,7 @@ end
124131
@inline indices_match(vali::Val, indices::Vector{Int}, a1, as...) = indices_match(vali, indices, a1) && indices_match(vali, indices, as...)
125132
@noinline indices_match_fail() = throw(ArgumentError("Indices of TypeSortedCollections do not match."))
126133

127-
@generated function Base.map!(f, dest::Union{TypeSortedCollection{<:Any, N}, AbstractArray}, args::Union{TypeSortedCollection{<:Any, N}, AbstractArray}...) where {N}
134+
@generated function Base.map!(f, dest::TSCOrAbstractVector{N}, args::TSCOrAbstractVector{N}...) where {N}
128135
expr = Expr(:block)
129136
push!(expr.args, :(Base.@_inline_meta))
130137
push!(expr.args, :(leading_tsc = first_tsc(dest, args...)))
@@ -134,7 +141,7 @@ end
134141
push!(expr.args, quote
135142
let inds = leading_tsc.indices[$i]
136143
@boundscheck indices_match($vali, inds, dest, args...) || indices_match_fail()
137-
for j in linearindices(inds)
144+
@inbounds for j in linearindices(inds)
138145
vecindex = inds[j]
139146
_setindex!($vali, j, vecindex, dest, f(_getindex_all($vali, j, vecindex, args...)...))
140147
end
@@ -147,7 +154,7 @@ end
147154
end
148155
end
149156

150-
@generated function Base.foreach(f, As::Union{<:TypeSortedCollection{<:Any, N}, AbstractVector}...) where {N}
157+
@generated function Base.foreach(f, As::TSCOrAbstractVector{N}...) where {N}
151158
expr = Expr(:block)
152159
push!(expr.args, :(Base.@_inline_meta))
153160
push!(expr.args, :(leading_tsc = first_tsc(As...)))
@@ -187,4 +194,36 @@ end
187194
end
188195
end
189196

197+
## broadcast!
198+
Base.Broadcast._containertype(::Type{<:TypeSortedCollection}) = TypeSortedCollection
199+
Base.Broadcast.promote_containertype(::Type{TypeSortedCollection}, _) = TypeSortedCollection
200+
Base.Broadcast.promote_containertype(_, ::Type{TypeSortedCollection}) = TypeSortedCollection
201+
Base.Broadcast.promote_containertype(::Type{TypeSortedCollection}, ::Type{Array}) = TypeSortedCollection # handle ambiguities with `Array`
202+
Base.Broadcast.promote_containertype(::Type{Array}, ::Type{TypeSortedCollection}) = TypeSortedCollection # handle ambiguities with `Array`
203+
204+
@generated function Base.Broadcast.broadcast_c!(f, ::Type, ::Type{TypeSortedCollection}, dest::AbstractVector, A, Bs...)
205+
T = first_tsc_type(A, Bs...)
206+
N = num_types(T)
207+
expr = Expr(:block)
208+
push!(expr.args, :(Base.@_inline_meta)) # TODO: good idea?
209+
push!(expr.args, :(leading_tsc = first_tsc(A, Bs...)))
210+
push!(expr.args, :(@boundscheck lengths_match(dest, A, Bs...) || lengths_match_fail()))
211+
for i = 1 : N
212+
vali = Val(i)
213+
push!(expr.args, quote
214+
let inds = leading_tsc.indices[$i]
215+
@boundscheck indices_match($vali, inds, A, Bs...) || indices_match_fail()
216+
@inbounds for j in linearindices(inds)
217+
vecindex = inds[j]
218+
_setindex!($vali, j, vecindex, dest, f(_getindex_all($vali, j, vecindex, A, Bs...)...))
219+
end
220+
end
221+
end)
222+
end
223+
quote
224+
$expr
225+
dest
226+
end
227+
end
228+
190229
end # module

test/runtests.jl

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ g(x::Int64, y1::Float64, y2::Int64) = x * y1 * y2
99
g(x::Float64, y1::Float64, y2::Int64) = x + y1 + y2
1010
g(x::Int64, y1::Float64, y2::Float64) = x * y1 - y2
1111
g(x::Float64, y1::Float64, y2::Float64) = x + y1 - y2
12+
g(x::Int64, y1::Int64, y2::Float64) = x - y1 * y2
1213
end
1314

1415
@testset "ambiguities" begin
@@ -148,3 +149,92 @@ end
148149
foreach(x -> push!(results, x), sortedx2)
149150
@test all(x .== results)
150151
end
152+
153+
@testset "broadcast!" begin
154+
x = Number[3.; 4; 5]
155+
sortedx = TypeSortedCollection(x)
156+
y1 = rand(length(x))
157+
y2 = rand(Int)
158+
results = similar(x, Float64)
159+
broadcast!(M.g, results, sortedx, y1, y2)
160+
@test all(results .== M.g.(x, y1, y2))
161+
162+
results = similar(x, Float64)
163+
results .= M.g.(sortedx, y1, y2)
164+
@test all(results .== M.g.(x, y1, y2))
165+
166+
@test (@allocated broadcast!(M.g, results, sortedx, y1, y2)) == 0
167+
end
168+
169+
@testset "broadcast! with scalars and TSC as second arg" begin
170+
x = 3
171+
y = Number[3.; 4; 5]
172+
z = 5.
173+
sortedy = TypeSortedCollection(y)
174+
results = similar(y, Float64)
175+
results .= M.g.(x, sortedy, z)
176+
@test all(results .== M.g.(x, y, z))
177+
@test (@allocated results .= M.g.(x, sortedy, z)) == 0
178+
end
179+
180+
@testset "broadcast! consecutive scalars" begin
181+
x = 3
182+
y = 4.
183+
z = Number[3.; 4; 5.]
184+
sortedz = TypeSortedCollection(z)
185+
results = similar(z, Float64)
186+
results .= M.g.(x, y, sortedz)
187+
@test all(results .== M.g.(x, y, z))
188+
@test (@allocated results .= M.g.(x, y, sortedz)) == 0
189+
end
190+
191+
@testset "broadcast! Array first" begin
192+
x = rand(Int, 3)
193+
y = Number[3.; 4; 5.]
194+
z = rand()
195+
sortedy = TypeSortedCollection(y)
196+
results = similar(y, Float64)
197+
results .= M.g.(x, sortedy, z)
198+
@test all(results .== M.g.(x, y, z))
199+
@test (@allocated results .= M.g.(x, sortedy, z)) == 0
200+
end
201+
202+
@testset "broadcast! indices mismatch" begin
203+
x = Number[3.; 4; 5]
204+
sortedx = TypeSortedCollection(x)
205+
y1 = rand()
206+
y2 = Number[8; 9; Float32(7)]
207+
sortedy2 = TypeSortedCollection(y2)
208+
results = similar(x, Float64)
209+
@test_throws ArgumentError broadcast!(M.g, results, sortedx, y1, sortedy2)
210+
end
211+
212+
@testset "broadcast! length mismatch" begin
213+
x = Number[3.; 4; 5]
214+
sortedx = TypeSortedCollection(x)
215+
y1 = rand(length(x) + 1)
216+
y2 = rand(length(x))
217+
results = similar(x, Float64)
218+
@test_throws DimensionMismatch results .= M.g.(sortedx, y1, y2)
219+
220+
y1 = rand()
221+
y2 = rand(length(x) + 1)
222+
@test_throws DimensionMismatch results .= M.g.(sortedx, y1, y2)
223+
224+
results = rand(length(x) + 1)
225+
y1 = rand()
226+
y2 = rand(Int)
227+
@test_throws DimensionMismatch results .= M.g.(sortedx, y1, y2)
228+
end
229+
230+
@testset "broadcast! matching indices" begin
231+
x = Number[3.; 4; 5]
232+
sortedx = TypeSortedCollection(x)
233+
y1 = rand()
234+
y2 = [7.; 8.; 9.]
235+
sortedy2 = TypeSortedCollection(y2, indices(sortedx))
236+
results = similar(x, Float64)
237+
broadcast!(M.g, results, sortedx, y1, sortedy2)
238+
@test all(results .== M.g.(x, y1, y2))
239+
@test (@allocated broadcast!(M.g, results, sortedx, y1, sortedy2)) == 0
240+
end

0 commit comments

Comments
 (0)