Skip to content

Commit 9aa32bd

Browse files
authored
Merge pull request #26658 from JuliaLang/sb/accumulate2
Change accumulation promotion behaviour
2 parents 367bc96 + 82c8f45 commit 9aa32bd

File tree

5 files changed

+407
-402
lines changed

5 files changed

+407
-402
lines changed

base/accumulate.jl

Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
# accumulate_pairwise slightly slower then accumulate, but more numerically
2+
# stable in certain situations (e.g. sums).
3+
# it does double the number of operations compared to accumulate,
4+
# though for cheap operations like + this does not have much impact (20%)
5+
function _accumulate_pairwise!(op::Op, c::AbstractVector{T}, v::AbstractVector, s, i1, n)::T where {T,Op}
6+
@inbounds if n < 128
7+
s_ = v[i1]
8+
c[i1] = op(s, s_)
9+
for i = i1+1:i1+n-1
10+
s_ = op(s_, v[i])
11+
c[i] = op(s, s_)
12+
end
13+
else
14+
n2 = n >> 1
15+
s_ = _accumulate_pairwise!(op, c, v, s, i1, n2)
16+
s_ = op(s_, _accumulate_pairwise!(op, c, v, op(s, s_), i1+n2, n-n2))
17+
end
18+
return s_
19+
end
20+
21+
function accumulate_pairwise!(op::Op, result::AbstractVector, v::AbstractVector) where Op
22+
li = linearindices(v)
23+
li != linearindices(result) && throw(DimensionMismatch("input and output array sizes and indices must match"))
24+
n = length(li)
25+
n == 0 && return result
26+
i1 = first(li)
27+
@inbounds result[i1] = v1 = reduce_first(op,v[i1])
28+
n == 1 && return result
29+
_accumulate_pairwise!(op, result, v, v1, i1+1, n-1)
30+
return result
31+
end
32+
33+
function accumulate_pairwise(op, v::AbstractVector{T}) where T
34+
out = similar(v, promote_op(op, T, T))
35+
return accumulate_pairwise!(op, out, v)
36+
end
37+
38+
39+
"""
40+
cumsum!(B, A; dims::Integer)
41+
42+
Cumulative sum of `A` along the dimension `dims`, storing the result in `B`. See also [`cumsum`](@ref).
43+
"""
44+
cumsum!(B::AbstractArray{T}, A; dims::Integer) where {T} =
45+
accumulate!(add_sum, B, A, dims=dims)
46+
47+
function cumsum!(out::AbstractArray, v::AbstractVector; dims::Integer=1)
48+
# we dispatch on the possibility of numerical stability issues
49+
_cumsum!(out, v, dims, ArithmeticStyle(eltype(out)))
50+
end
51+
52+
function _cumsum!(out::AbstractArray{T}, v, dim, ::ArithmeticRounds) where {T}
53+
dim == 1 ? accumulate_pairwise!(add_sum, out, v) : copyto!(out, v)
54+
end
55+
function _cumsum!(out::AbstractArray, v, dim, ::ArithmeticUnknown)
56+
_cumsum!(out, v, dim, ArithmeticRounds())
57+
end
58+
function _cumsum!(out::AbstractArray{T}, v, dim, ::ArithmeticStyle) where {T}
59+
dim == 1 ? accumulate!(add_sum, out, v) : copyto!(out, v)
60+
end
61+
62+
"""
63+
cumsum(A; dims::Integer)
64+
65+
Cumulative sum along the dimension `dims`. See also [`cumsum!`](@ref)
66+
to use a preallocated output array, both for performance and to control the precision of the
67+
output (e.g. to avoid overflow).
68+
69+
# Examples
70+
```jldoctest
71+
julia> a = [1 2 3; 4 5 6]
72+
2×3 Array{Int64,2}:
73+
1 2 3
74+
4 5 6
75+
76+
julia> cumsum(a, dims=1)
77+
2×3 Array{Int64,2}:
78+
1 2 3
79+
5 7 9
80+
81+
julia> cumsum(a, dims=2)
82+
2×3 Array{Int64,2}:
83+
1 3 6
84+
4 9 15
85+
```
86+
"""
87+
function cumsum(A::AbstractArray{T}; dims::Union{Nothing,Integer}=nothing) where T
88+
if dims === nothing
89+
depwarn("`cumsum(A::AbstractArray)` is deprecated, use `cumsum(A, dims=1)` instead.", :cumsum)
90+
dims = 1
91+
end
92+
out = similar(A, promote_op(add_sum, T, T))
93+
cumsum!(out, A, dims=dims)
94+
end
95+
96+
"""
97+
cumsum(x::AbstractVector)
98+
99+
Cumulative sum a vector. See also [`cumsum!`](@ref)
100+
to use a preallocated output array, both for performance and to control the precision of the
101+
output (e.g. to avoid overflow).
102+
103+
# Examples
104+
```jldoctest
105+
julia> cumsum([1, 1, 1])
106+
3-element Array{Int64,1}:
107+
1
108+
2
109+
3
110+
111+
julia> cumsum([fill(1, 2) for i in 1:3])
112+
3-element Array{Array{Int64,1},1}:
113+
[1, 1]
114+
[2, 2]
115+
[3, 3]
116+
```
117+
"""
118+
cumsum(x::AbstractVector) = cumsum(x, dims=1)
119+
120+
121+
"""
122+
cumprod!(B, A; dims::Integer)
123+
124+
Cumulative product of `A` along the dimension `dims`, storing the result in `B`.
125+
See also [`cumprod`](@ref).
126+
"""
127+
cumprod!(B::AbstractArray{T}, A; dims::Integer) where {T} =
128+
accumulate!(add_sum, B, A, dims=dims)
129+
130+
"""
131+
cumprod!(y::AbstractVector, x::AbstractVector)
132+
133+
Cumulative product of a vector `x`, storing the result in `y`.
134+
See also [`cumprod`](@ref).
135+
"""
136+
cumprod!(y::AbstractVector, x::AbstractVector) = cumprod!(y, x, dims=1)
137+
138+
"""
139+
cumprod(A; dims::Integer)
140+
141+
Cumulative product along the dimension `dim`. See also
142+
[`cumprod!`](@ref) to use a preallocated output array, both for performance and
143+
to control the precision of the output (e.g. to avoid overflow).
144+
145+
# Examples
146+
```jldoctest
147+
julia> a = [1 2 3; 4 5 6]
148+
2×3 Array{Int64,2}:
149+
1 2 3
150+
4 5 6
151+
152+
julia> cumprod(a, dims=1)
153+
2×3 Array{Int64,2}:
154+
1 2 3
155+
4 10 18
156+
157+
julia> cumprod(a, dims=2)
158+
2×3 Array{Int64,2}:
159+
1 2 6
160+
4 20 120
161+
```
162+
"""
163+
function cumprod(A::AbstractArray; dims::Union{Nothing,Integer}=nothing)
164+
if dims === nothing
165+
depwarn("`cumprod(A::AbstractArray)` is deprecated, use `cumprod(A, dims=1)` instead.", :cumprod)
166+
dims = 1
167+
end
168+
return accumulate(mul_prod, A, dims=dims)
169+
end
170+
171+
"""
172+
cumprod(x::AbstractVector)
173+
174+
Cumulative product of a vector. See also
175+
[`cumprod!`](@ref) to use a preallocated output array, both for performance and
176+
to control the precision of the output (e.g. to avoid overflow).
177+
178+
# Examples
179+
```jldoctest
180+
julia> cumprod(fill(1//2, 3))
181+
3-element Array{Rational{Int64},1}:
182+
1//2
183+
1//4
184+
1//8
185+
186+
julia> cumprod([fill(1//3, 2, 2) for i in 1:3])
187+
3-element Array{Array{Rational{Int64},2},1}:
188+
[1//3 1//3; 1//3 1//3]
189+
[2//9 2//9; 2//9 2//9]
190+
[4//27 4//27; 4//27 4//27]
191+
```
192+
"""
193+
cumprod(x::AbstractVector) = cumprod(x, dims=1)
194+
195+
196+
"""
197+
accumulate(op, A; dims::Integer)
198+
199+
Cumulative operation `op` along the dimension `dims`. See also
200+
[`accumulate!`](@ref) to use a preallocated output array, both for performance and
201+
to control the precision of the output (e.g. to avoid overflow). For common operations
202+
there are specialized variants of `accumulate`, see:
203+
[`cumsum`](@ref), [`cumprod`](@ref)
204+
205+
# Examples
206+
```jldoctest
207+
julia> accumulate(+, fill(1, 3, 3), dims=1)
208+
3×3 Array{Int64,2}:
209+
1 1 1
210+
2 2 2
211+
3 3 3
212+
213+
julia> accumulate(+, fill(1, 3, 3), dims=2)
214+
3×3 Array{Int64,2}:
215+
1 2 3
216+
1 2 3
217+
1 2 3
218+
```
219+
"""
220+
function accumulate(op, A; dims::Integer)
221+
out = similar(A, promote_op(op, eltype(A), eltype(A)))
222+
accumulate!(op, out, A, dims=dims)
223+
end
224+
225+
"""
226+
accumulate(op, x::AbstractVector)
227+
228+
Cumulative operation `op` on a vector. See also
229+
[`accumulate!`](@ref) to use a preallocated output array, both for performance and
230+
to control the precision of the output (e.g. to avoid overflow). For common operations
231+
there are specialized variants of `accumulate`, see:
232+
[`cumsum`](@ref), [`cumprod`](@ref)
233+
234+
# Examples
235+
```jldoctest
236+
julia> accumulate(+, [1,2,3])
237+
3-element Array{Int64,1}:
238+
1
239+
3
240+
6
241+
242+
julia> accumulate(*, [1,2,3])
243+
3-element Array{Int64,1}:
244+
1
245+
2
246+
6
247+
```
248+
"""
249+
accumulate(op, x::AbstractVector) = accumulate(op, x, dims=1)
250+
251+
"""
252+
accumulate!(op, B, A; dims::Integer)
253+
254+
Cumulative operation `op` on `A` along the dimension `dims`, storing the result in `B`.
255+
See also [`accumulate`](@ref).
256+
257+
# Examples
258+
```jldoctest
259+
julia> A = [1 2; 3 4];
260+
261+
julia> B = [0 0; 0 0];
262+
263+
julia> accumulate!(-, B, A, dims=1);
264+
265+
julia> B
266+
2×2 Array{Int64,2}:
267+
1 2
268+
-2 -2
269+
270+
julia> accumulate!(-, B, A, dims=2);
271+
272+
julia> B
273+
2×2 Array{Int64,2}:
274+
1 -1
275+
3 -1
276+
```
277+
"""
278+
function accumulate!(op, B, A; dims::Integer)
279+
dim = dims
280+
dim > 0 || throw(ArgumentError("dim must be a positive integer"))
281+
inds_t = axes(A)
282+
axes(B) == inds_t || throw(DimensionMismatch("shape of B must match A"))
283+
dim > ndims(A) && return copyto!(B, A)
284+
isempty(inds_t[dim]) && return B
285+
if dim == 1
286+
# We can accumulate to a temporary variable, which allows
287+
# register usage and will be slightly faster
288+
ind1 = inds_t[1]
289+
@inbounds for I in CartesianIndices(tail(inds_t))
290+
tmp = reduce_first(op, A[first(ind1), I])
291+
B[first(ind1), I] = tmp
292+
for i_1 = first(ind1)+1:last(ind1)
293+
tmp = op(tmp, A[i_1, I])
294+
B[i_1, I] = tmp
295+
end
296+
end
297+
else
298+
R1 = CartesianIndices(axes(A)[1:dim-1]) # not type-stable
299+
R2 = CartesianIndices(axes(A)[dim+1:end])
300+
_accumulate!(op, B, A, R1, inds_t[dim], R2) # use function barrier
301+
end
302+
return B
303+
end
304+
305+
"""
306+
accumulate!(op, y, x::AbstractVector)
307+
308+
Cumulative operation `op` on a vector `x`, storing the result in `y`.
309+
See also [`accumulate`](@ref).
310+
311+
# Examples
312+
``jldoctest
313+
julia> x = [1, 0, 2, 0, 3];
314+
315+
julia> y = [0, 0, 0, 0, 0];
316+
317+
julia> accumulate!(+, y, x);
318+
319+
julia> y
320+
5-element Array{Int64,1}:
321+
1
322+
1
323+
3
324+
3
325+
6
326+
```
327+
"""
328+
function accumulate!(op::Op, y, x::AbstractVector) where Op
329+
isempty(x) && return y
330+
v1 = first(x)
331+
_accumulate1!(op, y, v1, x, 1)
332+
end
333+
334+
@noinline function _accumulate!(op, B, A, R1, ind, R2)
335+
# Copy the initial element in each 1d vector along dimension `dim`
336+
ii = first(ind)
337+
@inbounds for J in R2, I in R1
338+
B[I, ii, J] = reduce_first(op, A[I, ii, J])
339+
end
340+
# Accumulate
341+
@inbounds for J in R2, i in first(ind)+1:last(ind), I in R1
342+
B[I, i, J] = op(B[I, i-1, J], A[I, i, J])
343+
end
344+
B
345+
end
346+
347+
"""
348+
accumulate(op, v0, x::AbstractVector)
349+
350+
Like `accumulate`, but using a starting element `v0`. The first entry of the result will be
351+
`op(v0, first(A))`.
352+
353+
# Examples
354+
```jldoctest
355+
julia> accumulate(+, 100, [1,2,3])
356+
3-element Array{Int64,1}:
357+
101
358+
103
359+
106
360+
361+
julia> accumulate(min, 0, [1,2,-1])
362+
3-element Array{Int64,1}:
363+
0
364+
0
365+
-1
366+
```
367+
"""
368+
function accumulate(op, v0, x::AbstractVector)
369+
T = promote_op(op, typeof(v0), eltype(x))
370+
out = similar(x, T)
371+
accumulate!(op, out, v0, x)
372+
end
373+
374+
function accumulate!(op, y, v0, x::AbstractVector)
375+
isempty(x) && return y
376+
v1 = op(v0, first(x))
377+
_accumulate1!(op, y, v1, x, 1)
378+
end
379+
380+
function _accumulate1!(op, B, v1, A::AbstractVector, dim::Integer)
381+
dim > 0 || throw(ArgumentError("dim must be a positive integer"))
382+
inds = linearindices(A)
383+
inds == linearindices(B) || throw(DimensionMismatch("linearindices of A and B don't match"))
384+
dim > 1 && return copyto!(B, A)
385+
i1 = inds[1]
386+
cur_val = reduce_first(op, v1)
387+
B[i1] = cur_val
388+
@inbounds for i in inds[2:end]
389+
cur_val = op(cur_val, A[i])
390+
B[i] = cur_val
391+
end
392+
return B
393+
end

0 commit comments

Comments
 (0)