Repository: numpy/numpy

Issue:
AVX test failures for 32 bit manylinux1 wheels
See test [here](https://dev.azure.com/numpy/numpy/_build/results?buildId=12323&view=logs&j=d1321064-f6c3-56b7-0172-6d994d01c836&t=3e664a8f-27c8-559d-01e9-e1651f65562b). The problem is in `TestAVXUfuncs.test_avx_based_ufunc`. Note that the manylinux2010 wheels are fine. One possible solution not providing manylinux1 wheels for NumPy 1.20.


TestAVXUfuncs.test_avx_based_ufunc
<!-- Please describe the issue in detail here, and fill in the fields below -->

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np
<< your code here >>
```

<!-- Remove these sections for a feature request -->

### Error message:

<!-- If you are reporting a segfault please include a GDB traceback, which you
can generate by following
https://github.com/numpy/numpy/blob/master/doc/source/dev/development_environment.rst#debugging -->

<!-- Full error message, if any (starting from line Traceback: ...) -->

### Numpy/Python version information:

<!-- Output from 'import sys, numpy; print(numpy.__version__, sys.version)' -->


AVX test failures for 32 bit manylinux1 wheels
See test [here](https://dev.azure.com/numpy/numpy/_build/results?buildId=12323&view=logs&j=d1321064-f6c3-56b7-0172-6d994d01c836&t=3e664a8f-27c8-559d-01e9-e1651f65562b). The problem is in `TestAVXUfuncs.test_avx_based_ufunc`. Note that the manylinux2010 wheels are fine. One possible solution not providing manylinux1 wheels for NumPy 1.20.


TestAVXUfuncs.test_avx_based_ufunc
<!-- Please describe the issue in detail here, and fill in the fields below -->

### Reproducing code example:

<!-- A short code example that reproduces the problem/missing feature. It should be
self-contained, i.e., possible to run as-is via 'python myproblem.py' -->

```python
import numpy as np
<< your code here >>
```

<!-- Remove these sections for a feature request -->

### Error message:

<!-- If you are reporting a segfault please include a GDB traceback, which you
can generate by following
https://github.com/numpy/numpy/blob/master/doc/source/dev/development_environment.rst#debugging -->

<!-- Full error message, if any (starting from line Traceback: ...) -->

### Numpy/Python version information:

<!-- Output from 'import sys, numpy; print(numpy.__version__, sys.version)' -->




Discussion:
this is the flaky 'reciprocal' ufunc on 32-bits we have seen in other places. like [this issue](https://github.com/MacPython/numpy-wheels/pull/73#issuecomment-596182413) about msse2 flags, [this one](https://github.com/MacPython/numpy-wheels/pull/99#issuecomment-669721208) that seems to be a [problem with older gcc](https://github.com/MacPython/numpy-wheels/pull/99#issuecomment-678765336)
I think dropping manylinux1 wheels for linux-32-bit is an option. Should we run it past the mailing list?
> Should we run it past the mailing list?

Yes, it was one of the options I was considering for 1.20. The other was dropping Python 3.6, at least from the wheels. I think moving to newer compilers is something we need to do when we can.
I'll make a post.
@mattip,
> I think dropping manylinux1 wheels for linux-32-bit is an option

No wait, please assign me to this issue, I will fix it.
> No wait, please assign me to this issue, I will fix it.

Done. Note that I (we?) were contemplating dropping manylinux1 even without the 32 bit problems.
@charris, this issue will be resolved by #16247, I will reduce the amount of work so it can only cover (sqrt, absolute, square, reciprocal) to speed up the progress since NEON and SSE2 require a lot of emulations.
this is the flaky 'reciprocal' ufunc on 32-bits we have seen in other places. like [this issue](https://github.com/MacPython/numpy-wheels/pull/73#issuecomment-596182413) about msse2 flags, [this one](https://github.com/MacPython/numpy-wheels/pull/99#issuecomment-669721208) that seems to be a [problem with older gcc](https://github.com/MacPython/numpy-wheels/pull/99#issuecomment-678765336)
I think dropping manylinux1 wheels for linux-32-bit is an option. Should we run it past the mailing list?
> Should we run it past the mailing list?

Yes, it was one of the options I was considering for 1.20. The other was dropping Python 3.6, at least from the wheels. I think moving to newer compilers is something we need to do when we can.
I'll make a post.
@mattip,
> I think dropping manylinux1 wheels for linux-32-bit is an option

No wait, please assign me to this issue, I will fix it.
> No wait, please assign me to this issue, I will fix it.

Done. Note that I (we?) were contemplating dropping manylinux1 even without the 32 bit problems.
@charris, this issue will be resolved by #16247, I will reduce the amount of work so it can only cover (sqrt, absolute, square, reciprocal) to speed up the progress since NEON and SSE2 require a lot of emulations.

Resolving patch:
diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py
--- a/numpy/core/code_generators/generate_umath.py
+++ b/numpy/core/code_generators/generate_umath.py
@@ -352,14 +352,14 @@ def english_upper(s):
     Ufunc(1, 1, None,
           docstrings.get('numpy.core.umath.square'),
           None,
-          TD(ints+inexact, simd=[('avx2', ints), ('fma', 'fd'), ('avx512f', 'FDfd')]),
+          TD(ints+inexact, simd=[('avx2', ints), ('avx512f', 'FD')], dispatch=[('loops_unary_fp', 'fd')]),
           TD(O, f='Py_square'),
           ),
 'reciprocal':
     Ufunc(1, 1, None,
           docstrings.get('numpy.core.umath.reciprocal'),
           None,
-          TD(ints+inexact, simd=[('avx2', ints), ('fma', 'fd'), ('avx512f','fd')]),
+          TD(ints+inexact, simd=[('avx2', ints)], dispatch=[('loops_unary_fp', 'fd')]),
           TD(O, f='Py_reciprocal'),
           ),
 # This is no longer used as numpy.ones_like, however it is
@@ -389,7 +389,7 @@ def english_upper(s):
     Ufunc(1, 1, None,
           docstrings.get('numpy.core.umath.absolute'),
           'PyUFunc_AbsoluteTypeResolver',
-          TD(bints+flts+timedeltaonly, simd=[('fma', 'fd'), ('avx512f', 'fd')]),
+          TD(bints+flts+timedeltaonly, dispatch=[('loops_unary_fp', 'fd')]),
           TD(cmplx, simd=[('avx512f', cmplxvec)], out=('f', 'd', 'g')),
           TD(O, f='PyNumber_Absolute'),
           ),
@@ -767,7 +767,7 @@ def english_upper(s):
           docstrings.get('numpy.core.umath.sqrt'),
           None,
           TD('e', f='sqrt', astype={'e':'f'}),
-          TD(inexactvec, simd=[('fma', 'fd'), ('avx512f', 'fd')]),
+          TD(inexactvec, dispatch=[('loops_unary_fp', 'fd')]),
           TD('fdg' + cmplx, f='sqrt'),
           TD(P, f='sqrt'),
           ),
diff --git a/numpy/core/setup.py b/numpy/core/setup.py
--- a/numpy/core/setup.py
+++ b/numpy/core/setup.py
@@ -904,6 +904,7 @@ def generate_umath_c(ext, build_dir):
             join('src', 'umath', 'simd.inc.src'),
             join('src', 'umath', 'loops.h.src'),
             join('src', 'umath', 'loops.c.src'),
+            join('src', 'umath', 'loops_unary_fp.dispatch.c.src'),
             join('src', 'umath', 'matmul.h.src'),
             join('src', 'umath', 'matmul.c.src'),
             join('src', 'umath', 'clip.h.src'),


Test patch:
diff --git a/numpy/core/tests/test_simd.py b/numpy/core/tests/test_simd.py
--- a/numpy/core/tests/test_simd.py
+++ b/numpy/core/tests/test_simd.py
@@ -1,6 +1,6 @@
 # NOTE: Please avoid the use of numpy.testing since NPYV intrinsics
 # may be involved in their functionality.
-import pytest
+import pytest, math
 from numpy.core._simd import targets
 
 class _Test_Utility:
@@ -80,6 +80,18 @@ def _to_unsigned(self, vector):
                 cvt_intrin = "reinterpret_u{0}_{1}"
             return getattr(self.npyv, cvt_intrin.format(sfx[1:], sfx))(vector)
 
+    def _pinfinity(self):
+        v = self.npyv.setall_u32(0x7f800000)
+        return self.npyv.reinterpret_f32_u32(v)[0]
+
+    def _ninfinity(self):
+        v = self.npyv.setall_u32(0xff800000)
+        return self.npyv.reinterpret_f32_u32(v)[0]
+
+    def _nan(self):
+        v = self.npyv.setall_u32(0x7fc00000)
+        return self.npyv.reinterpret_f32_u32(v)[0]
+
 class _SIMD_INT(_Test_Utility):
     """
     To test all integer vector types at once
@@ -150,6 +162,65 @@ def test_arithmetic_fused(self):
         data_nfms = self.mul(data_fma, self.setall(-1))
         assert nfms == data_nfms
 
+    def test_abs(self):
+        pinf, ninf, nan = self._pinfinity(), self._ninfinity(), self._nan()
+        data = self._data()
+        vdata = self.load(self._data())
+
+        abs_cases = ((-0, 0), (ninf, pinf), (pinf, pinf), (nan, nan))
+        for case, desired in abs_cases:
+            data_abs = [desired]*self.nlanes
+            vabs = self.abs(self.setall(case))
+            assert vabs == pytest.approx(data_abs, nan_ok=True)
+
+        vabs = self.abs(self.mul(vdata, self.setall(-1)))
+        assert vabs == data
+
+    def test_sqrt(self):
+        pinf, ninf, nan = self._pinfinity(), self._ninfinity(), self._nan()
+        data = self._data()
+        vdata = self.load(self._data())
+
+        sqrt_cases = ((-0.0, -0.0), (0.0, 0.0), (-1.0, nan), (ninf, nan), (pinf, pinf))
+        for case, desired in sqrt_cases:
+            data_sqrt = [desired]*self.nlanes
+            sqrt  = self.sqrt(self.setall(case))
+            assert sqrt == pytest.approx(data_sqrt, nan_ok=True)
+
+        data_sqrt = self.load([math.sqrt(x) for x in data]) # load to truncate precision
+        sqrt = self.sqrt(vdata)
+        assert sqrt == data_sqrt
+
+    def test_square(self):
+        pinf, ninf, nan = self._pinfinity(), self._ninfinity(), self._nan()
+        data = self._data()
+        vdata = self.load(self._data())
+        # square
+        square_cases = ((nan, nan), (pinf, pinf), (ninf, pinf))
+        for case, desired in square_cases:
+            data_square = [desired]*self.nlanes
+            square  = self.square(self.setall(case))
+            assert square == pytest.approx(data_square, nan_ok=True)
+
+        data_square = [x*x for x in data]
+        square = self.square(vdata)
+        assert square == data_square
+
+    def test_reciprocal(self):
+        pinf, ninf, nan = self._pinfinity(), self._ninfinity(), self._nan()
+        data = self._data()
+        vdata = self.load(self._data())
+
+        recip_cases = ((nan, nan), (pinf, 0.0), (ninf, -0.0), (0.0, pinf), (-0.0, ninf))
+        for case, desired in recip_cases:
+            data_recip = [desired]*self.nlanes
+            recip = self.recip(self.setall(case))
+            assert recip == pytest.approx(data_recip, nan_ok=True)
+
+        data_recip = self.load([1/x for x in data]) # load to truncate precision
+        recip = self.recip(vdata)
+        assert recip == data_recip
+
 class _SIMD_ALL(_Test_Utility):
     """
     To test all vector types at once