Repository: numpy/numpy
Pull request: BUG: Fix step returned by linspace when num=1 and endpoint=False
Changed the the behavior of linspace to return a proper step size for arguments num=1 and endpoint=False, where previously NaN was returned.

closes gh-14927

<!-- Please be sure you are following the instructions in the dev guidelines
http://www.numpy.org/devdocs/dev/development_workflow.html
-->

<!-- We'd appreciate it if your commit message is properly formatted
http://www.numpy.org/devdocs/dev/development_workflow.html#writing-the-commit-message
-->


Problem statement:
`linspace(0, 2, 1, endpoint=False, retstep=True)` returns `nan` as a step
Calling
```python
import numpy as np
np.linspace(0, 2, 1, endpoint=False, retstep=True)
```
returns `(array([0.]), nan)`, whereas I think `(array([0.]), 2.)` would be a more consistent result.

Obviously, this is a slightly degenerated case, but it was the cause of a difficult to debug problem in my code base, which would have been avoided if linspace returned the suggest result.

The suggest result is a direct consequence of the calculations in the linspace function, where the following definitions are made
```python
delta = stop - start
div = (num - 1) if endpoint else num
step = delta / div
```
Note that the case `num == 1` has a hardcoded alternative path, which enforces `step = NaN`.

Hints from issue discussion:
I think you are right, do you want to look into making a PR?
I just open a pull request on this. There is only one line changed in the code (+ some a test).

Commit message: Extended tests for linspace with retstep=True

Added more extensive tests

Patch to review:
diff --git a/numpy/core/function_base.py b/numpy/core/function_base.py
index 42604ec3fa66..538ac8b842a6 100644
--- a/numpy/core/function_base.py
+++ b/numpy/core/function_base.py
@@ -139,7 +139,7 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None,
     # from overriding what class is produced, and thus prevents, e.g. use of Quantities,
     # see gh-7142. Hence, we multiply in place only for standard scalar types.
     _mult_inplace = _nx.isscalar(delta)
-    if num > 1:
+    if div > 0:
         step = delta / div
         if _nx.any(step == 0):
             # Special handling for denormal numbers, gh-5437
@@ -154,7 +154,8 @@ def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None,
             else:
                 y = y * step
     else:
-        # 0 and 1 item long sequences have an undefined step
+        # sequences with 0 items or 1 item with endpoint=True (i.e. div <= 0)
+        # have an undefined step
         step = NaN
         # Multiply with delta to allow possible override of output class.
         y = y * delta
diff --git a/numpy/core/tests/test_function_base.py b/numpy/core/tests/test_function_base.py
index 84b60b19cfde..cdc20cf96f75 100644
--- a/numpy/core/tests/test_function_base.py
+++ b/numpy/core/tests/test_function_base.py
@@ -351,14 +351,20 @@ def test_equivalent_to_arange(self):
                          arange(j+1, dtype=int))
 
     def test_retstep(self):
-        y = linspace(0, 1, 2, retstep=True)
-        assert_(isinstance(y, tuple) and len(y) == 2)
-        for num in (0, 1):
-            for ept in (False, True):
+        for num in [0, 1, 2]:
+            for ept in [False, True]:
                 y = linspace(0, 1, num, endpoint=ept, retstep=True)
-                assert_(isinstance(y, tuple) and len(y) == 2 and
-                        len(y[0]) == num and isnan(y[1]),
-                        'num={0}, endpoint={1}'.format(num, ept))
+                assert(isinstance(y, tuple) and len(y) == 2)
+                if num == 2:
+                    y0_expect = [0.0, 1.0] if ept else [0.0, 0.5]
+                    assert_array_equal(y[0], y0_expect)
+                    assert_equal(y[1], y0_expect[1])
+                elif num == 1 and not ept:
+                    assert_array_equal(y[0], [0.0])
+                    assert_equal(y[1], 1.0)
+                else:
+                    assert_array_equal(y[0], [0.0][:num])
+                    assert(isnan(y[1]))
 
     def test_object(self):
         start = array(1, dtype='O')


Code review comments:
diff_hunk: @@ -351,14 +351,20 @@ def test_equivalent_to_arange(self):
                          arange(j+1, dtype=int))
 
     def test_retstep(self):
-        y = linspace(0, 1, 2, retstep=True)
-        assert_(isinstance(y, tuple) and len(y) == 2)
-        for num in (0, 1):
-            for ept in (False, True):
+        for num in [0, 1, 2]:
+            for ept in [False, True]:
                 y = linspace(0, 1, num, endpoint=ept, retstep=True)
-                assert_(isinstance(y, tuple) and len(y) == 2 and
-                        len(y[0]) == num and isnan(y[1]),
-                        'num={0}, endpoint={1}'.format(num, ept))
+                assert(isinstance(y, tuple) and len(y) == 2)
path: numpy/core/tests/test_function_base.py
text: @user1:
```suggestion
                assert isinstance(y, tuple) and len(y) == 2
```

diff_hunk: @@ -351,14 +351,20 @@ def test_equivalent_to_arange(self):
                          arange(j+1, dtype=int))
 
     def test_retstep(self):
-        y = linspace(0, 1, 2, retstep=True)
-        assert_(isinstance(y, tuple) and len(y) == 2)
-        for num in (0, 1):
-            for ept in (False, True):
+        for num in [0, 1, 2]:
+            for ept in [False, True]:
                 y = linspace(0, 1, num, endpoint=ept, retstep=True)
-                assert_(isinstance(y, tuple) and len(y) == 2 and
-                        len(y[0]) == num and isnan(y[1]),
-                        'num={0}, endpoint={1}'.format(num, ept))
+                assert(isinstance(y, tuple) and len(y) == 2)
+                if num == 2:
+                    y0_expect = [0.0, 1.0] if ept else [0.0, 0.5]
+                    assert_array_equal(y[0], y0_expect)
+                    assert_equal(y[1], y0_expect[1])
+                elif num == 1 and not ept:
+                    assert_array_equal(y[0], [0.0])
+                    assert_equal(y[1], 1.0)
+                else:
+                    assert_array_equal(y[0], [0.0][:num])
+                    assert(isnan(y[1]))
path: numpy/core/tests/test_function_base.py
text: @user1:
```suggestion
                    assert isnan(y[1])
```