- 
                Notifications
    You must be signed in to change notification settings 
- Fork 152
Implement accumulate and friends #702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3a26ce9
              801d95c
              4985bcf
              4ca0144
              df1caa8
              df87a52
              b665233
              3db20ef
              6bf5e05
              5a7963d
              8db9ad6
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -285,3 +285,53 @@ end | |
| @inbounds return similar_type(a, T, Size($Snew))(tuple($(exprs...))) | ||
| end | ||
| end | ||
|  | ||
| struct _InitialValue end | ||
|  | ||
| _maybe_val(dims::Integer) = Val(Int(dims)) | ||
| _maybe_val(dims) = dims | ||
| _valof(::Val{D}) where D = D | ||
|  | ||
| @inline Base.accumulate(op::F, a::StaticVector; dims = :, init = _InitialValue()) where {F} = | ||
| _accumulate(op, a, _maybe_val(dims), init) | ||
|  | ||
| @inline Base.accumulate(op::F, a::StaticArray; dims, init = _InitialValue()) where {F} = | ||
| _accumulate(op, a, _maybe_val(dims), init) | ||
|  | ||
| @inline function _accumulate(op::F, a::StaticArray, dims::Union{Val,Colon}, init) where {F} | ||
| # Adjoin the initial value to `op`: | ||
| rf(x, y) = x isa _InitialValue ? Base.reduce_first(op, y) : op(x, y) | ||
|  | ||
| if isempty(a) | ||
| T = return_type(rf, Tuple{typeof(init), eltype(a)}) | ||
| return similar_type(a, T)() | ||
| end | ||
|  | ||
| # StaticArrays' `reduce` is `foldl`: | ||
|         
                  c42f marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| results = _reduce( | ||
| a, | ||
| dims, | ||
| (init = (similar_type(a, Union{}, Size(0))(), init),), | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing I noticed here is that for length-0 input, the result has eltype of  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 
 I don't think it's possible to solve this in general as the output value depends on the function  As all static arrays (including  My preference is: 
 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's possible to solve this in general as the output value depends on the function  This is what #664 hacks around for  
      
  
  Member
      
  Author
   There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see. I misread the last half of your first comment. But I think one difficulty of  op(::A, ::A) :: B
op(::B, ::A) :: C
op(::C, ::A) :: D
op(::D, ::A) :: D  # finally "stabilized"But, even though  osc_op(::Nothing, ::Any) = missing
osc_op(::Missing, ::Any) = nothingSo, maybe we should use something like this? function f(op, acc, x)
    T = typeof(acc)
    while true
        acc = op(acc, x)
        T = promote_type(T, typeof(acc))
        non_existing_value && return T
        # `non_existing_value` modeling truncation arbitrary-sized input.
    end
end
return_type(f, Tuple{typeof(op), typeof(init), eltype(a)})Impressively,  I'm not sure if we need to go this far, though.  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, it's confusing to know what the empty case should do here and it's worse than map. With your fix we have julia> accumulate((a,b)->(a+b)/2, SA[1,2])
2-element SArray{Tuple{2},Float64,1,2} with indices SOneTo(2):
 1.0
 1.5
julia> accumulate((a,b)->(a+b)/2, SA[1])
1-element SArray{Tuple{1},Int64,1,1} with indices SOneTo(1):
 1
julia> accumulate((a,b)->(a+b)/2, SA{Int}[])
0-element SArray{Tuple{0},Int64,1,0} with indices SOneTo(0)It seems like we must make a somewhat arbitrary choice for the length-0 case with  julia> accumulate((a,b)->(a+b)/2, SA{Int}[1], init=0)
1-element SArray{Tuple{1},Float64,1,1} with indices SOneTo(1):
 0.5
julia> accumulate((a,b)->(a+b)/2, SA{Int}[], init=0)
0-element SArray{Tuple{0},Float64,1,0} with indices SOneTo(0)Overall do you feel like this is an improvement on using  We do have oddities like julia> cumsum(Int8[1])
1-element Array{Int64,1}:
 1
julia> cumsum(SA{Int8}[1])
1-element SArray{Tuple{1},Int8,1,1} with indices SOneTo(1):
 1
julia> cumsum(SA{Int8}[1,2])
2-element SArray{Tuple{2},Int64,1,2} with indices SOneTo(2):
 1
 3I think that inconsistency between here and Base might be harmless though, and it's certainly not clear how to "fix" it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 
 That's a tough question... 😅  Personally, I think we should be using  But I think the consistency of the API within a package and with respect to  | ||
| ) do (ys, acc), x | ||
| y = rf(acc, x) | ||
| # Not using `push(ys, y)` here since we need to widen element type as | ||
| # we iterate. | ||
| (vcat(ys, SA[y]), y) | ||
|         
                  c42f marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| end | ||
| dims === (:) && return first(results) | ||
|  | ||
| ys = map(first, results) | ||
| # Now map over all indices of `a`. Since `_map` needs at least | ||
| # one `StaticArray` to be passed, we pass `a` here, even though | ||
| # the values of `a` are not used. | ||
| data = _map(a, CartesianIndices(a)) do _, CI | ||
|         
                  c42f marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
| D = _valof(dims) | ||
| I = Tuple(CI) | ||
| J = setindex(I, 1, D) | ||
| ys[J...][I[D]] | ||
| end | ||
| return similar_type(a, eltype(data))(data) | ||
| end | ||
|  | ||
| @inline Base.cumsum(a::StaticArray; kw...) = accumulate(Base.add_sum, a; kw...) | ||
| @inline Base.cumprod(a::StaticArray; kw...) = accumulate(Base.mul_prod, a; kw...) | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| using StaticArrays, Test | ||
|  | ||
| @testset "accumulate" begin | ||
| @testset "cumsum(::$label)" for (label, T) in [ | ||
| # label, T | ||
| ("SVector", SVector), | ||
| ("MVector", MVector), | ||
| ("SizedVector", SizedVector), | ||
| ] | ||
| @testset "$label" for (label, a) in [ | ||
| ("[1, 2, 3]", T{3}(SA[1, 2, 3])), | ||
| ("[]", T{0,Int}(())), | ||
| ] | ||
| @test cumsum(a) == cumsum(collect(a)) | ||
| @test cumsum(a) isa similar_type(a) | ||
| @inferred cumsum(a) | ||
| end | ||
| @test eltype(cumsum(T{0,Int8}(()))) == eltype(cumsum(Int8[])) | ||
| @test eltype(cumsum(T{1,Int8}((1)))) == eltype(cumsum(Int8[1])) | ||
| @test eltype(cumsum(T{2,Int8}((1, 2)))) == eltype(cumsum(Int8[1, 2])) | ||
| end | ||
|  | ||
| @testset "cumsum(::$label; dims=2)" for (label, T) in [ | ||
| # label, T | ||
| ("SMatrix", SMatrix), | ||
| ("MMatrix", MMatrix), | ||
| ("SizedMatrix", SizedMatrix), | ||
| ] | ||
| @testset "$label" for (label, a) in [ | ||
| ("[1 2; 3 4; 5 6]", T{3,2}(SA[1 2; 3 4; 5 6])), | ||
| ("0 x 2 matrix", T{0,2,Float64}()), | ||
| ("2 x 0 matrix", T{2,0,Float64}()), | ||
| ] | ||
| @test cumsum(a; dims = 2) == cumsum(collect(a); dims = 2) | ||
| @test cumsum(a; dims = 2) isa similar_type(a) | ||
| v"1.1" <= VERSION < v"1.2" && continue | ||
| @inferred cumsum(a; dims = Val(2)) | ||
| end | ||
| end | ||
|  | ||
| @testset "cumsum(a::SArray; dims=$i); ndims(a) = $d" for d in 1:4, i in 1:d | ||
| shape = Tuple(1:d) | ||
| a = similar_type(SArray, Int, Size(shape))(1:prod(shape)) | ||
| @test cumsum(a; dims = i) == cumsum(collect(a); dims = i) | ||
| @test cumsum(a; dims = i) isa SArray | ||
| v"1.1" <= VERSION < v"1.2" && continue | ||
| @inferred cumsum(a; dims = Val(i)) | ||
| end | ||
|  | ||
| @testset "cumprod" begin | ||
| a = SA[1, 2, 3] | ||
| @test cumprod(a)::SArray == cumprod(collect(a)) | ||
| @inferred cumprod(a) | ||
|  | ||
| @test eltype(cumsum(SA{Int8}[])) == eltype(cumsum(Int8[])) | ||
| @test eltype(cumsum(SA{Int8}[1])) == eltype(cumsum(Int8[1])) | ||
| @test eltype(cumsum(SA{Int8}[1, 2])) == eltype(cumsum(Int8[1, 2])) | ||
| end | ||
|  | ||
| @testset "empty vector with init" begin | ||
| a = SA{Int}[] | ||
| right(_, x) = x | ||
| @test accumulate(right, a; init = Val(1)) === SA{Int}[] | ||
| @inferred accumulate(right, a; init = Val(1)) | ||
| end | ||
| end | 
Uh oh!
There was an error while loading. Please reload this page.