Repository: cupy/cupy

Issue:
compatibility issue of `erfinv` and `erfcinv` 
For `erfinv`, the valid domain is [-1, 1], and at the boundary -1 and +1 the values are -Inf and +Inf, respectively. But outside the boundary, the values are NaN in SciPy: see [here](https://github.com/scipy/scipy/blob/59347ae8b86bcc92c339efe213128f64ab6df98c/scipy/special/cephes/ndtri.c#L146-L149) (the `ndtri` function is the underlying workhorse).

Reproducer:
```python
>>> from cupyx.scipy.special import erfinv
>>> import  cupy as cp
>>> 
>>> a  = (cp.arange(6) + 1).reshape(2,3)
>>> a
array([[1, 2, 3],
       [4, 5, 6]])
>>> erfinv(a)
array([[inf, inf, inf],
       [inf, inf, inf]])
>>>
>>> import scipy.special as scp
>>> scp.erfinv(cp.asnumpy(a))
array([[inf, nan, nan],
       [nan, nan, nan]])
```

Reproducer 2:
```bash
$ pytest -v tests/cupyx_tests/scipy_tests/special_tests/test_erf.py
========================================================================= test session starts =========================================================================
platform linux -- Python 3.7.6, pytest-5.3.5, py-1.8.1, pluggy-0.12.0 -- /home/leofang/miniconda3/envs/cupy_dev/bin/python
cachedir: .pytest_cache
rootdir: /home/leofang/cupy, inifile: setup.cfg
collected 10 items                                                                                                                                                    

tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestSpecial::test_erf PASSED                                                                           [ 10%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestSpecial::test_erfc PASSED                                                                          [ 20%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestSpecial::test_erfcinv FAILED                                                                       [ 30%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestSpecial::test_erfcx PASSED                                                                         [ 40%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestSpecial::test_erfinv FAILED                                                                        [ 50%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestFusionSpecial::test_erf PASSED                                                                     [ 60%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestFusionSpecial::test_erfc PASSED                                                                    [ 70%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestFusionSpecial::test_erfcinv FAILED                                                                 [ 80%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestFusionSpecial::test_erfcx PASSED                                                                   [ 90%]
tests/cupyx_tests/scipy_tests/special_tests/test_erf.py::TestFusionSpecial::test_erfinv FAILED                                                                  [100%]

=============================================================== 4 failed, 6 passed, 1 warning in 0.74s ================================================================
```

I am a bit surprised to learn this, as the CI doesn't seem to complain at all, so it is likely the behavior is changed in recent SciPy? (I'm using v1.4.1 btw.) 

The fix should be simple: just add another `else if` branch handling the out of boundary behavior to the ufunc here : https://github.com/cupy/cupy/blob/84343ce8a87d34928abef65d8930ba590189f43f/cupyx/scipy/special/erf.py#L37-L43

I have not dug into `erfcinv` but presumably the source of error is similar. 


Discussion:
Can you submit a PR? 
Thanks!

Resolving patch:
diff --git a/cupyx/scipy/special/erf.py b/cupyx/scipy/special/erf.py
--- a/cupyx/scipy/special/erf.py
+++ b/cupyx/scipy/special/erf.py
@@ -33,35 +33,27 @@
 
 erfinv = core.create_ufunc(
     'cupyx_scipy_erfinv', ('f->f', 'd->d'),
-    '''
-    if (in0 < -1) {
-        out0 = -1.0 / 0.0;
-    } else if (in0 > 1) {
-        out0 = 1.0 / 0.0;
-    } else {
-        out0 = erfinv(in0);
-    }
-    ''',
+    'out0 = erfinv(in0);',
     doc='''Inverse function of error function.
 
     .. seealso:: :meth:`scipy.special.erfinv`
 
+    .. note::
+        The behavior close to (and outside) the domain follows that of
+        SciPy v1.4.0+.
+
     ''')
 
 
 erfcinv = core.create_ufunc(
     'cupyx_scipy_erfcinv', ('f->f', 'd->d'),
-    '''
-    if (in0 < 0) {
-        out0 = 1.0 / 0.0;
-    } else if (in0 > 2) {
-        out0 = -1.0 / 0.0;
-    } else {
-        out0 = erfcinv(in0);
-    }
-    ''',
+    'out0 = erfcinv(in0);',
     doc='''Inverse function of complementary error function.
 
     .. seealso:: :meth:`scipy.special.erfcinv`
 
+    .. note::
+        The behavior close to (and outside) the domain follows that of
+        SciPy v1.4.0+.
+
     ''')


Test patch:
diff --git a/tests/cupyx_tests/scipy_tests/special_tests/test_erf.py b/tests/cupyx_tests/scipy_tests/special_tests/test_erf.py
--- a/tests/cupyx_tests/scipy_tests/special_tests/test_erf.py
+++ b/tests/cupyx_tests/scipy_tests/special_tests/test_erf.py
@@ -24,12 +24,14 @@ def test_erfc(self):
     def test_erfcx(self):
         self.check_unary('erfcx')
 
+    @testing.with_requires('scipy>=1.4.0')
     def test_erfinv(self):
         self.check_unary('erfinv')
         self.check_unary_random('erfinv', scale=2, offset=-1)
         self.check_unary_boundary('erfinv', boundary=-1)
         self.check_unary_boundary('erfinv', boundary=1)
 
+    @testing.with_requires('scipy>=1.4.0')
     def test_erfcinv(self):
         self.check_unary('erfcinv')
         self.check_unary_random('erfcinv', scale=2, offset=0)
@@ -66,6 +68,42 @@ def check_unary_boundary(self, name, xp, scp, dtype, boundary):
         a = xp.array(a, dtype=dtype)
         return getattr(scp.special, name)(a)
 
+    @testing.with_requires('scipy>=1.4.0')
+    @testing.for_dtypes(['f', 'd'])
+    def test_erfinv_behavior(self, dtype):
+        a = cupy.empty((1,), dtype=dtype)
+
+        a[:] = 1.0 + 1E-6
+        a = cupyx.scipy.special.erfinv(a)
+        assert cupy.isnan(a)
+        a[:] = -1.0 - 1E-6
+        a = cupyx.scipy.special.erfinv(a)
+        assert cupy.isnan(a)
+        a[:] = 1.0
+        a = cupyx.scipy.special.erfinv(a)
+        assert numpy.isposinf(cupy.asnumpy(a))
+        a[:] = -1.0
+        a = cupyx.scipy.special.erfinv(a)
+        assert numpy.isneginf(cupy.asnumpy(a))
+
+    @testing.with_requires('scipy>=1.4.0')
+    @testing.for_dtypes(['f', 'd'])
+    def test_erfcinv_behavior(self, dtype):
+        a = cupy.empty((1,), dtype=dtype)
+
+        a[:] = 2.0 + 1E-6
+        a = cupyx.scipy.special.erfcinv(a)
+        assert cupy.isnan(a)
+        a[:] = 0.0 - 1E-6
+        a = cupyx.scipy.special.erfcinv(a)
+        assert cupy.isnan(a)
+        a[:] = 0.0
+        a = cupyx.scipy.special.erfcinv(a)
+        assert numpy.isposinf(cupy.asnumpy(a))
+        a[:] = 2.0
+        a = cupyx.scipy.special.erfcinv(a)
+        assert numpy.isneginf(cupy.asnumpy(a))
+
 
 @testing.gpu
 @testing.with_requires('scipy')