Repository: numpy/numpy

Issue:
Core: Convolve and Correlate mode check in core/numeric.py
<!-- 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
y = np.convolve(np.ones(1,), np.ones((1,))/1, mode='random_mode')
```

### Error message:
    637 def _mode_from_name(mode):                                  <--- numpy/core/numeric.py
    638     if isinstance(mode, basestring):
    639         return _mode_from_name_dict[mode.lower()[0]]            
    640     return mode
    
    KeyError: 'r'
<!-- 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)' -->
1.18.5 3.7.6 (default, Jan  8 2020, 19:59:22) 
[GCC 7.3.0]

### Discussion:
Current modes for numpy.convolve, and numpy.correlate are 'valid', 'same' or 'full'. If there is any other string provided it raises a KeyError with the first letter.
**In fact,**
```python
import numpy as np
y = np.convolve(np.ones(1,), np.ones((1,))/1, mode='very_random_mode')
```
**Does NOT raise an error. As it is simply mistaken for 'valid'** (the very first character is checked)
Think there should be a check here:
https://github.com/numpy/numpy/blob/2eed8784d5946e420500bf87f9af305d87aaa055/numpy/core/numeric.py#L670 which makes sure the mode is in fact, corresponding to (modified?) keys of https://github.com/numpy/numpy/blob/2eed8784d5946e420500bf87f9af305d87aaa055/numpy/core/numeric.py#L665-L667


Discussion:
If the issue is valid, I'll create a PR once I run the tests locally. 👍🏼 
This seems like a sensible change to me - we already did something similar for other string arguments elsewhere in numpy (eg `mode='clip'`). We should find what we did in that PR and do the same (ie, did we immediately error, or emit a deprecation warning?)
> This seems like a sensible change to me - we already did something similar for other string arguments elsewhere in numpy (eg `mode='clip'`). We should find what we did in that PR and do the same (ie, did we immediately error, or emit a deprecation warning?)

Took me some time, but do you mean https://github.com/numpy/numpy/pull/16056?
It emits a deprecation warning if inexact modes are present. Ref:
https://github.com/numpy/numpy/blob/9e37e585cc86ff6dc821d0566baacc137cb16343/numpy/core/src/multiarray/conversion_utils.c#L625-L632
We can do a similar thing here: https://github.com/numpy/numpy/blob/2eed8784d5946e420500bf87f9af305d87aaa055/numpy/core/numeric.py#L665-L673

Thanks for finding that - yes, that is the one I mean, nice job.

Resolving patch:
diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py
--- a/numpy/core/numeric.py
+++ b/numpy/core/numeric.py
@@ -662,17 +662,6 @@ def flatnonzero(a):
     return np.nonzero(np.ravel(a))[0]
 
 
-_mode_from_name_dict = {'v': 0,
-                        's': 1,
-                        'f': 2}
-
-
-def _mode_from_name(mode):
-    if isinstance(mode, str):
-        return _mode_from_name_dict[mode.lower()[0]]
-    return mode
-
-
 def _correlate_dispatcher(a, v, mode=None):
     return (a, v)
 
@@ -748,7 +737,6 @@ def correlate(a, v, mode='valid'):
     array([ 0.0+0.j ,  3.0+1.j ,  1.5+1.5j,  1.0+0.j ,  0.5+0.5j])
 
     """
-    mode = _mode_from_name(mode)
     return multiarray.correlate2(a, v, mode)
 
 
@@ -852,7 +840,6 @@ def convolve(a, v, mode='full'):
         raise ValueError('a cannot be empty')
     if len(v) == 0:
         raise ValueError('v cannot be empty')
-    mode = _mode_from_name(mode)
     return multiarray.correlate(a, v[::-1], mode)
 
 


Test patch:
diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py
--- a/numpy/core/tests/test_numeric.py
+++ b/numpy/core/tests/test_numeric.py
@@ -296,6 +296,7 @@ def test_var(self):
         B[0] = 1j
         assert_almost_equal(np.var(B), 0.25)
 
+
 class TestIsscalar:
     def test_isscalar(self):
         assert_(np.isscalar(3.1))
@@ -2362,8 +2363,8 @@ def test_clip_property(self, data, shape):
                 base_shape=shape,
                 # Commenting out the min_dims line allows zero-dimensional arrays,
                 # and zero-dimensional arrays containing NaN make the test fail.
-                min_dims=1  
-                            
+                min_dims=1
+  
             )
         )
         amin = data.draw(
@@ -2896,10 +2897,10 @@ def _setup(self, dt):
         self.x = np.array([1, 2, 3, 4, 5], dtype=dt)
         self.xs = np.arange(1, 20)[::3]
         self.y = np.array([-1, -2, -3], dtype=dt)
-        self.z1 = np.array([ -3.,  -8., -14., -20., -26., -14.,  -5.], dtype=dt)
+        self.z1 = np.array([-3., -8., -14., -20., -26., -14., -5.], dtype=dt)
         self.z1_4 = np.array([-2., -5., -8., -11., -14., -5.], dtype=dt)
-        self.z1r = np.array([-15., -22., -22., -16., -10.,  -4.,  -1.], dtype=dt)
-        self.z2 = np.array([-5., -14., -26., -20., -14., -8.,  -3.], dtype=dt)
+        self.z1r = np.array([-15., -22., -22., -16., -10., -4., -1.], dtype=dt)
+        self.z2 = np.array([-5., -14., -26., -20., -14., -8., -3.], dtype=dt)
         self.z2r = np.array([-1., -4., -10., -16., -22., -22., -15.], dtype=dt)
         self.zs = np.array([-3., -14., -30., -48., -66., -84.,
                            -102., -54., -19.], dtype=dt)
@@ -2947,6 +2948,22 @@ def test_zero_size(self):
         with pytest.raises(ValueError):
             np.correlate(np.ones(1000), np.array([]), mode='full')
 
+    def test_mode(self):
+        d = np.ones(100)
+        k = np.ones(3)
+        default_mode = np.correlate(d, k, mode='valid')
+        with assert_warns(DeprecationWarning):
+            valid_mode = np.correlate(d, k, mode='v')
+        assert_array_equal(valid_mode, default_mode)
+        # integer mode
+        with assert_raises(ValueError):
+            np.correlate(d, k, mode=-1)
+        assert_array_equal(np.correlate(d, k, mode=0), valid_mode)
+        # illegal arguments
+        with assert_raises(TypeError):
+            np.correlate(d, k, mode=None)
+
+
 class TestConvolve:
     def test_object(self):
         d = [1.] * 100
@@ -2960,6 +2977,21 @@ def test_no_overwrite(self):
         assert_array_equal(d, np.ones(100))
         assert_array_equal(k, np.ones(3))
 
+    def test_mode(self):
+        d = np.ones(100)
+        k = np.ones(3)
+        default_mode = np.convolve(d, k, mode='full')
+        with assert_warns(DeprecationWarning):
+            full_mode = np.convolve(d, k, mode='f')
+        assert_array_equal(full_mode, default_mode)
+        # integer mode
+        with assert_raises(ValueError):
+            np.convolve(d, k, mode=-1)
+        assert_array_equal(np.convolve(d, k, mode=2), full_mode)
+        # illegal arguments
+        with assert_raises(TypeError):
+            np.convolve(d, k, mode=None)
+
 
 class TestArgwhere: