Skip to content

Conversation

@honno
Copy link
Contributor

@honno honno commented Mar 17, 2022

Fixes #21211 and includes a regression test. Testing for neg zeros is a bit annoying—let me know if I missed an existing utility for this.

Note I opted to just fallback on ndarray.__add__(). This is not ideal as we lose the checks against an inplace array having their dtype/shape modified, so I hard-coded those checks in (an introduced an additional test).

@seberg
Copy link
Member

seberg commented Mar 17, 2022

Are we sure we want to tape this over here and not look into why this is the case in NumPy proper? I would need to dig deeper to figure out which SIMD branches are used here (and whether this is expected, not expected, or we can do something about it easily).
Ping @seiko2plus in case you have a quick insight, thanks!

@seberg
Copy link
Member

seberg commented Mar 17, 2022

Ah, so the reason is actually the following:

  1. We choose to use the reduction special case here (because it somewhat fits).
  2. Reductions are calculated by doing result += sum(chunk) – this ends up being -0.0 + 0.0
  3. sum(chunk) uses 0 as initial value, but 0.0 + -0.0 is (correctly) 0.0

So effectively, NumPy calculates -0.0 + (0.0 + -0.0) == 0.0 because of the "inserted" 0.0.

In principle, it seems like the truly correct thing would be to define all sums using -0.0 as initial value but 0.0 as the identity in case of an empty sum. That should be possible, at least once we push through gh-20970.

Until then, a quick fix may be to avoid the reduction path for this type of input (usually at least). That won't fix sum([-0.0, -0.0]) if we want to fix it, but would change the case here.

@honno
Copy link
Contributor Author

honno commented Mar 17, 2022

Ah interesting consequence of a 0. initial value.

Are we sure we want to tape this over here and not look into why this is the case in NumPy proper?

Until then, a quick fix may be to avoid the reduction path for this type of input (usually at least). That won't fix sum([-0.0, -0.0]) if we want to fix it, but would change the case here.

Yep this PR is certainly not ideal. I'm pretty ignorant about NumPy internals—so if avoiding the reduction path is simple I'll need a pointer (have no idea where to look), otherwise I might not be your guy 😅

@seberg
Copy link
Member

seberg commented Mar 17, 2022

@honno if you like a bit of digging. I think another good start (and good enough for this), would be to make sure that sum(chunk) starts with -0.0, because I think that is always guaranteed to be added once more. That won't fix sum([-0.0, -0.0]) – unless you put initial=-0.0`, but it should be good enough for this:

The other thing would be to modify the IS_BINARY_REDUCE macro:

#define IS_BINARY_REDUCE ((args[0] == args[2])\

I do think both may make sense independently, although the second is just an implementation detail, and the first is a broader fix, since it probably fixes np.add.reduce as long as you pass initial=-.0 manually (which we should add tests for if we change it).

@asmeurer
Copy link
Member

I'm in agreement that we should fix the underlying numpy function. If that ends up being impossible right now, I'd rather just leave this as is and XFAIL the array-api-tests test.

seberg added a commit to seberg/numpy that referenced this pull request Mar 18, 2022
Technically, we should ensure that we do all summations starting with
-0 unless there is nothing to sum (in which case the result is clearly
0).
This is a start, since the initial value is still filled in as 0 by
the reduce machinery.  However, it fixes the odd case where an
inplace addition:

   x1 = np.array(-0.0)
   x2 = np.array(-0.0)
   x1 += x2

May go into the reduce code path (becaus strides are 0).  We could
avoid the reduce path there, but -0 here is strictly correct anyway
and also a necessary step towards fixing `sum([-0., -0., -0.])`
which still requires `initial=-0.0` to be passed manually right now.

There are new `xfail` marked tests also checking the path without
initial.  Presumably, we should be using -0.0 as initial value,
but 0.0 as default (if an array is empty) here.
This may be more reasonably possible after numpygh-20970.

Closes numpygh-21213, numpygh-21212
@honno
Copy link
Contributor Author

honno commented Mar 22, 2022

Closed as #21218 resolves this problem much better

@honno honno closed this Mar 22, 2022
melissawm pushed a commit to melissawm/numpy that referenced this pull request Apr 12, 2022
Technically, we should ensure that we do all summations starting with
-0 unless there is nothing to sum (in which case the result is clearly
0).
This is a start, since the initial value is still filled in as 0 by
the reduce machinery.  However, it fixes the odd case where an
inplace addition:

   x1 = np.array(-0.0)
   x2 = np.array(-0.0)
   x1 += x2

May go into the reduce code path (becaus strides are 0).  We could
avoid the reduce path there, but -0 here is strictly correct anyway
and also a necessary step towards fixing `sum([-0., -0., -0.])`
which still requires `initial=-0.0` to be passed manually right now.

There are new `xfail` marked tests also checking the path without
initial.  Presumably, we should be using -0.0 as initial value,
but 0.0 as default (if an array is empty) here.
This may be more reasonably possible after numpygh-20970.

Closes numpygh-21213, numpygh-21212
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: __iadd__() for numpy.array_api arrays does not follow negative zeros special case

3 participants