Njsmith: Unfortunately, large parts of your reply make no sense in the
context of what I wrote, so I'm going to try again to explain the point. I
understand your idea that you would like numpy.select to primarily follow
the rules of broadcasting. I am trying to explain to you that there are
currently at least two good reasons why it should not.

I am not trying to make you agree with my view of what the code in
numpy.select should do. I am trying to get you to see that the problem is
more complex than you present it as being, and that there should be some
decision making taking place with a clear view of the options and their
pros and cons.


I wrote: " Is this really a case of 0+0, or a case of 0/0, or something
inbetween? One situation is naturally silent in maths, the other requires
an error and warning message which comes from the semantics of 'dividing'. "

You wrote:  " It' a case of np.array([]) + np.array([])"

- OK, that's your assertion - so tell me more about why you think
'addition' is the correct operator to draw inspiration from here, why not
division or some other operator? Is the idea of selection as simple as the
idea of addition?


You wrote: "According to this logic, the sum of zero elements should be an
error too, but that's not how mathematicians think :-)"

To which I can only say - wtf?

Did you read the first paragraph, or any of the messages in the thread that
precede it, where I specifically point out there are at least two ways of
treating the select operator, and the question is which mode of working
should be chosen, and why?

Are the semantics of the idea of selection akin to situations like
addition/multiplication (where anything x empty -> result universally) or
division (where anything x empty -> error, since the semantics of dividing
something up into 0 pieces is undefined in the real world and results in a
warning)? Please don't reply right away with more assertions - stop and
think about it for a while.

In one case the operation is commutative, in the other it is not; and that
property derives from the real world concept of what it means to divide
something by another thing.

When I write 0+0 and 0/0 in the top paragraph, I am not saying that the
empty set is equivalent to zero. I am trying to persuade you to think for a
moment about the fact that when implementing mathematical operations, some
operations still require thought and occasionally special treatment even
when it seems trivial to extend their definitions over all possible inputs.

Focusing on select. It is all very well saying 'hey let's just broadcast!',
but that leads to crazy situations like C1 and C2 that have no
correspondance with the intuition of selection in real life. The case of C1
and C2 show that reversing the parameters leads to wildly different program
behaviour. Does that follow a normal understanding of the concept of
'selection' in real life?

Using sebergs example - if someone gives me 'two piles of stones' - one of
which is an empty pile - and tells me to pick the best stone, in real life
I don't return an program error and crash, or give a normal result, based
on the order they present the piles! If selection doesn't behave that way
in the real world, why should it behave that way in numpy?

Real world selection upon groups of piles of stones, where one pile is a
'pile of nothing' results in someone saying 'wtf? there's nothing there to
select?? are you sure you know what you're talking about?'.

So, should numpy generate a warning or error like that or not?


OK, forget real life for a moment, maybe you're not bothered by real world
operations. Does the approach of 'lets just broadcast' result in a function
that matches the function spec at (
http://docs.scipy.org/doc/numpy/reference/generated/numpy.select.html)?
Nope! See the example in G.

If your idea of what this function should be is 'let's just use naive
broadcasting and ignore possible user errors and possibly undefined
behaviour if it were the real world', then there has to be a change to the
documentation/specification to make clear you are accomodating the idea of
selection only as far as you can within the idea of broadcasting, rather
than accomodating the idea of broadcasting only as far as you can within
the idea of selection.

For example - here's the function spec:

"The output at position m is the m-th element of the array in
*choicelist* where
the m-th element of the corresponding array in *condlist* is True."

Is that spec actually true, in all the examples I gave? No way!

If numpy is going to implement selection as 'a function *inspired* by the
idea of selection *with the clear proviso that output datatypes and errors
follow numpy broadcasting rules applied to the ordering of parameters*' -
well that's totally fine with me, but it needs to be described that way in
the spec and examples should be given to clarify edge cases. There probably
ought to be a bit of discussion about whether this is the ideal behaviour
for the function, and whether it's better to change the code or the spec -
since you're proposing to make the function fail to match its present and
past written specification.

In short:

In the real world, people do not select as numpy's code currently selects.
In the software spec, the function does not select as numpy's code
currently selects.

For example - you ignored my example in G where I noted that the current
behaviour of numpy directly contradicts the specification of the function.
Why?

Graeme.




On Mon, Feb 24, 2014 at 4:41 AM, njsmith <notifications@github.com> wrote:

> In numpy/lib/function_base.py:
>
> >          else:
> > -            S = S*ones(asarray(pfac).shape, S.dtype)
> > -    return choose(S, tuple(choicelist))
> > +            cond_sizes['scalar bool']=True
> > +
> > +    for i in range(0,len(choicelist)):
> > +        if type(choicelist[i]) is np.ndarray:
> > +            choice_sizes[choicelist[i].shape]=True
> > +
> > +    sizes=cond_sizes.copy()
> > +    sizes.update(choice_sizes)
> > +
> > +    # Test for ndarrays with no data.
> > +    if (0,) in sizes.keys():
> > +        raise ValueError('ndarray containing no items is present')
>
>  It seems you and I are both in agreement that generating an integer as
> output in the case np.select([], []) seems wrong. Yes. The second thing is
> that while "(empty ndarray) x (empty ndarray) must produce (empty ndarray)"
> looks cute and natural, the human concept of selection according to rules -
> when there aren't any rules or choices - isn't as well defined as say, the
> intellectual concept of merging two empty lists. I'm arguing for having a
> think here about the meaning and error/warning output of this function from
> first principles rather than simply 'definition by existing
> implementation'. Is this really a case of 0+0, or a case of 0/0, or
> something inbetween? One situation is naturally silent in maths, the other
> requires an error and warning message which comes from the semantics of
> 'dividing'. Here, I think we need at least some warning output, which comes
> from the semantics of selection.
> If you really set on having this discussion, then the numpy-discussion
> list is the right place to do it. But I can tell you that based on my
> knowledge and experience, the conclusion will definitely be that the empty
> ndarray case should not be treated specially in any way, and should simply
> return an empty ndarray. This is neither 0 + 0 nor 0/0. It' a case of
> np.array([]) + np.array([]), which returns np.array([]).
>  The third thing, which the highlighted code in the pull request was
> intended to address in a consistent way, was these situations, which I
> think need to be defined (as meaningful or erroneous) and tested. C -
> condlist contains 'normal' items to select upon along with an ndarray with
> no items/dimension. D - choicelist contains 'normal' items to select upon
> along with an ndarray with no items (in some position). E - condlist
> contains multiple ndarrays that contain no items/dimensions (similar to C,
> but test separately). F - choicelist contains multiple ndarrays that
> contain no items/dimensions (similar to D, but test separately). G -
> combinations of the above.
> You're making this too complicated :-). All the entries in choicelist and
> condlist need to broadcast against each other. This is the only rule you
> need.
>  Case C1 + output (sorry, numpy 1.6.2 is all I have to hand here, and it's
> almost 3 in the morning) np.select([np.array([1], dtype=bool),np.array([],
> dtype=bool)], [np.array([1]), np.array([2])]); ValueError:
> non-broadcastable output operand with shape (1) doesn't match the broadcast
> shape (0) OK.. that makes sense! You gave weird input, you get an error.
> Now let's simply swap the arguments around... we'll still get the same
> error/output since the underlying problem is still the same? Nope. Case C2
> + output np.select([np.array([], dtype=bool),np.array([1], dtype=bool)],
> [np.array([2]), np.array([1])]); array([], dtype=float64) Completely
> different program behaviour when we swap the arguments around. (This is an
> example of why it's a really good idea to have a little bit of logic that
> warns the user when there are peculiar empty arguments to a function that
> really needs arguments or generates an error - it picks up on strange
> implementation artefacts that would otherwise cause silent bugs in your
> code/data).
> Yep, the C1 case is indicating a bug in your code -- [1] and [] are
> broadcastable against each other, so after broadcasting both of these cases
> become equivalent to np.select([np.array([], dtype=bool),np.array([],
> dtype=bool)], [np.array([]), np.array([])]), and should return an array
> with shape (0,). If, however, you replace [1] with [1, 2], then all of
> these cases are errors. It's a special fact about dimensions that have
> exactly 1 entry along them that they can broadcast to become any length,
> including 0.
>  Case C3 + output - multiple empty condlists np.select([np.array([],
> dtype=bool),np.array([], dtype=bool)], [np.array([1]), np.array([2])]);
> array([], dtype=float64) (Looks like an error to me. The datatype & content
> of the output does not correspond to the available options presented as
> choices in the choicelist.) To make this post shorter, I'll jump to some of
> the trickier ones to show how nasty the problem can get. Case G1 - 2 empty
> condlists/choicelists and 1 'meaningful' condlist with corresponding
> 'meaningful' choicelist at list position 1. np.select([np.array([],
> dtype=bool),np.array([1], dtype=bool), np.array([], dtype=bool)],
> [np.array([]), np.array([1]), np.array([])]); After reading the documented
> specification for np.select, what is the correct output here, and how well
> does it correspond to your human intuitions about selecting things?
> Again, according to all the rules that are used everywhere in numpy, this
> is just a case of calling select() with a bunch of empty arrays. Try:
> np.array([]) + np.array([1]) np.array([]) + np.array([1, 2])
>  So...to sum up - *if* it's meaningful to make selections upon empty
> condlists/choicelists or upon lists that can contain one or more empty
> ndarrays, the behaviour should be well specified, consistent and behaviour
> *should correspond to a natural human understanding about selecting things
> as well as possible* so that user aren't shocked or misled. Datatypes and
> selected values should be consistent and well defined by specification, not
> implementation.
> Making np.select inconsistent with the entire rest of numpy is not a way
> to help users feel less shocked and misled. These cases look weird when
> you're just coming up with weird edge-case input. But the broadcasting
> rules are simple, consistent, and very useful. The division_by_self
> function below is also well defined and returns
>  consistent output (1) across inputs. It is also *wrong* in terms of the
> human concepts that go with the idea of dividing a number by itself. In
> mathematics it was decided that it's better to generate an error in the
> case of division by zero rather than make something up. I think the same is
> true of selection by zero.
> According to this logic, the sum of zero elements should be an error too,
> but that's not how mathematicians think :-)
>
> --
> Reply to this email directly or view it on GitHub<https://github.com/numpy/numpy/pull/3537/files#r9980463>
> .
>