Skip to content
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

Add mapreduce_single function #25051

Merged
merged 1 commit into from
Jan 4, 2018
Merged

Add mapreduce_single function #25051

merged 1 commit into from
Jan 4, 2018

Conversation

simonbyrne
Copy link
Contributor

Since the demise of r_promote in #22825, there is now a type-instability in mapreduce if the operator does not give an element of the same type as the input. This arose during my implementation of Kahan summation using a reduction operator, see: JuliaMath/KahanSummation.jl#7

This adds a mapreduce_single function which defines what the result should be in these cases.

@simonbyrne
Copy link
Contributor Author

This has the benefit of being able to get rid of the funny composition behaviour to get the widening behaviour of sum/prod.

@ararslan ararslan requested a review from stevengj December 13, 2017 06:53
@simonbyrne simonbyrne changed the title Add mapreduce_single function WIP: Add mapreduce_single function Dec 13, 2017
@simonbyrne simonbyrne changed the title WIP: Add mapreduce_single function Add mapreduce_single function Dec 13, 2017
@simonbyrne
Copy link
Contributor Author

Okay, I think this should be ready. I tried to avoid touching the reducedim code too much, but I suspect something like this could help clean up that as well.

@simonbyrne simonbyrne added the arrays [a, r, r, a, y, s] label Dec 13, 2017
@simonbyrne
Copy link
Contributor Author

@TotalVerb You did the last deep dive into this code: would you mind having a quick look over it?

@simonbyrne simonbyrne added the triage This should be discussed on a triage call label Dec 13, 2017
base/reduce.jl Outdated
reduce_single(op, x) = x
reduce_single(::typeof(+), x::Bool) = Int(x)

reduce_single(::typeof(add_sum), x) = reduce_single(+, x)
Copy link
Contributor

@TotalVerb TotalVerb Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although adding + zero(x) for add and * one(x) was ugly, I think it is more general. There are theoretically other types than Bool that are not the type of their "additive monoid" or "multiplicative monoid". Char is now one of these for multiplication. Of course, Irrational is one of these, but that doesn't support zero either. The extra add or multiply should be easily optimized by LLVM for all types except already-slow ones like BigInt, anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zero(pi) and one('a') both currently throw errors, so they won't work either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, let's leave it as is then. one('a') should theoretically return "" though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll add a fix for that.

@@ -372,7 +428,7 @@ In the former case, the integers are widened to system word size and therefore
the result is 128. In the latter case, no such widening happens and integer
overflow results in -128.
"""
sum(f::Callable, a) = mapreduce(promote_sys_size_add ∘ f, +, a)
sum(f, a) = mapreduce(f, add_sum, a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch: it looks like this Callable is not necessary for avoiding ambiguity any more. The same change should apply for prod.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

reducedim_initarray(A, region, real(zero(eltype(A))))
global reducedim_init(f::Union{typeof(abs),typeof(abs2)}, op::typeof(*), A::T, region) =
reducedim_initarray(A, region, real(one(eltype(A))))
global reducedim_init(f, op::Union{typeof(+),typeof(add_sum)}, A::T, region) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not safe to generalize this to f ∉ [identity, abs, abs2] because we need the reduction operator to be type stable over whatever is produced by the f used for mapreduce. Nevertheless, it's a nice simplification to have, but I think we will need to continue hardcoding the list of acceptable map functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't mapreduce_single(f, op, zero(eltype(A))) should handle that?

Copy link
Contributor

@TotalVerb TotalVerb Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, for some choices of f, this assumes that mr_single(f, +, 0) + f(0), etc., will be of the same concrete type of mr_single(f, +, 0), which one can design f to make it fail (for example, in the case of *, if f return a dimensionful quantity, like 1m, which has different concrete type than 1m^2). This seems to be where _reducedim_init is more general and guards against. Of course it is also possible to thwart _reducedim_init with badly behaving f, so 😕.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I'm not actually sure _reducedim_init does what it should in these type unstable cases, so maybe it's best to leave cleanup of _reducedim_init to later.

@@ -422,7 +478,7 @@ julia> prod(1:20)
2432902008176640000
```
"""
prod(a) = mapreduce(promote_sys_size_mul, *, a)
prod(a) = mapreduce(identity, mul_prod, a)

## maximum & minimum

Copy link
Contributor

@TotalVerb TotalVerb Dec 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth changing the f(a1) in the below function to mapreduce_single(f, op, a1) for consistency, although no reasonable types I can think of should have a lattice structure outside the type itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -133,7 +141,7 @@ function mapfoldr(f, op, itr)
if isempty(itr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated comment, which can be done as part of this PR or in a later one: Since we have #24187 Iterators.Reverse now, I think we can write these mapfoldr functions generically. As it stands it only works with arrays, and this generalization may allow it to work with arbitrary reversible iterators.

@StefanKarpinski
Copy link
Member

How about calling this reduce1? Terrible name? Somehow I like it better than reduce_single.

@simonbyrne
Copy link
Contributor Author

How about calling this reduce1? Terrible name? Somehow I like it better than reduce_single.

Because it deals with singleton case, in the same way that reduce_empty deals with the empty case.

Perhaps it would be best to change it to reduce_first, since it is used in a couple of places to get the first element of the reduction.

@StefanKarpinski
Copy link
Member

I would say go with mapreduce_first then, seems like a clearer name.

@StefanKarpinski StefanKarpinski removed the triage This should be discussed on a triage call label Dec 14, 2017
@StefanKarpinski StefanKarpinski added this to the 1.0 milestone Dec 14, 2017
@simonbyrne
Copy link
Contributor Author

Okay done.

One question: should reduce/foldl/foldr always call reduce_first on the first element when more than 1 element? This is not true currently (some just unwind the loop of the first 2 elements).

@simonbyrne
Copy link
Contributor Author

Also, I didn't touch accumulate, cumsum, etc., as that is way beyond most mere mortals.

@JeffBezanson
Copy link
Member

LGTM.

Rebase?

Since the demise of `r_promote` in #22825, there is now a type-instability in `mapreduce` if the operator does not give an element of the same type as the input. This arose during my implementation of Kahan summation using a reduction operator, see: JuliaMath/KahanSummation.jl#7

This adds a `mapreduce_single` function which defines what the result should be in these cases.
simonbyrne added a commit to JuliaMath/FixedPointNumbers.jl that referenced this pull request Jan 16, 2018
The promotion machinery for reduction in sum/prod was changed in JuliaLang/julia#25051. This updates the use.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arrays [a, r, r, a, y, s]
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants