python numpy strange boolean arithmetic behaviour
Question: Why is it, in python/numpy:

    
    
    from numpy import asarray
    bools=asarray([False,True])
    
    print(bools)
    [False True]
    
    print(1*bools, 0+bools, 0-bools)    # False, True are valued as 0, 1
    [0 1] [0 1] [ 0 -1]
    
    print(-2*bools, -bools*2)           # !? expected same result!  :-/
    [0 -2] [2 0] 
    
    print(-bools)                       # this is the reason!
    [True False]
    

I consider it weird that `-bools` returns `logical_not(bools)`, because in all
other cases the behaviour is "arithmetic", not "logical".

One who wants to use an array of booleans as a 0/1 mask (or "characteristic
function") is forced to use somehow involute expressions such as `(0-bools)`
or `(-1)*bools`, and can easily incur into bugs if he forgets about this.

Why is it so, and what would be the best acceptable way to obtain the desired
behaviour? (beside commenting of course)


Answer: Its all about operator order and data types.

    
    
    >>> import numpy as np
    >>> B = np.array([0, 1], dtype=np.bool)
    >>> B
    array([False,  True], dtype=bool)
    

With numpy, boolean arrays are treated as that, boolean arrays. Every
operation applied to them, will first try to maintain the data type. That is
way:

    
    
    >>> -B
    array([ True, False], dtype=bool)
    

and

    
    
    >>> ~B
    array([ True, False], dtype=bool)
    

which are equivalent, return the element-wise negation of its elements. Note
however that using `-B` throws a warning, as the function is deprecated.

When you use things like:

    
    
    >>> B + 1
    array([1, 2])
    

`B` and `1` are first casted under the hood to the same data type. In data-
type promotions, the `boolean` array is always casted to a `numeric` array. In
the above case, `B` is casted to `int`, which is similar as:

    
    
    >>> B.astype(int) + 1
    array([1, 2])
    

In your example:

    
    
    >>> -B * 2
    array([2, 0])
    

First the array `B` is negated by the operator `-` and then multiplied by 2.
The desired behaviour can be adopted either by explicit data conversion, or
adding brackets to ensure proper operation order:

    
    
    >>> -(B * 2)
    array([ 0, -2])
    

or

    
    
    >>> -B.astype(int) * 2
    array([ 0, -2])
    

Note that `B.astype(int)` can be replaced without data-copy by
`B.view(np.int8)`, as boolean are represented by `characters` and have thus 8
bits, the data can be viewed as integer with the `.view` method without
needing to convert it.

    
    
    >>> B.view(np.int8)
    array([0, 1], dtype=int8)
    

So, in short, `B.view(np.int8)` or `B.astype(yourtype)` will always ensurs
that `B` is a `[0,1]` numeric array.

