Repository: dask/dask

Issue:
Add support for endpoint in linspace's 
With NumPy, one can use `np.linspace`'s `endpoint` like below. Currently `da.linspace` does support the first syntax, but doesn't support the second. Would be good for it to support both options.

```python
In [1]: np.linspace(0, 1, 4)
Out[1]: array([0.        , 0.33333333, 0.66666667, 1.        ])

In [2]: np.linspace(0, 1, 3, endpoint=False)
Out[2]: array([0.        , 0.33333333, 0.66666667])
```


Discussion:

Resolving patch:
diff --git a/dask/array/creation.py b/dask/array/creation.py
--- a/dask/array/creation.py
+++ b/dask/array/creation.py
@@ -173,13 +173,12 @@ def full_like(a, fill_value, dtype=None, chunks=None):
     )
 
 
-def linspace(start, stop, num=50, chunks=None, dtype=None):
+def linspace(start, stop, num=50, endpoint=True, retstep=False, chunks=None,
+             dtype=None):
     """
     Return `num` evenly spaced values over the closed interval [`start`,
     `stop`].
 
-    TODO: implement the `endpoint`, `restep`, and `dtype` keyword args
-
     Parameters
     ----------
     start : scalar
@@ -188,14 +187,25 @@ def linspace(start, stop, num=50, chunks=None, dtype=None):
         The last value of the sequence.
     num : int, optional
         Number of samples to include in the returned dask array, including the
-        endpoints.
+        endpoints. Default is 50.
+    endpoint : bool, optional
+        If True, ``stop`` is the last sample. Otherwise, it is not included.
+        Default is True.
+    retstep : bool, optional
+        If True, return (samples, step), where step is the spacing between
+        samples. Default is False.
     chunks :  int
         The number of samples on each block. Note that the last block will have
         fewer samples if `num % blocksize != 0`
+    dtype : dtype, optional
+        The type of the output array. Default is given by ``numpy.dtype(float)``.
 
     Returns
     -------
     samples : dask array
+    step : float, optional
+        Only returned if ``retstep`` is True. Size of spacing between samples.
+
 
     See Also
     --------
@@ -210,23 +220,29 @@ def linspace(start, stop, num=50, chunks=None, dtype=None):
 
     range_ = stop - start
 
-    space = float(range_) / (num - 1)
+    div = (num - 1) if endpoint else num
+    step = float(range_) / div
 
     if dtype is None:
         dtype = np.linspace(0, 1, 1).dtype
 
-    name = 'linspace-' + tokenize((start, stop, num, chunks, dtype))
+    name = 'linspace-' + tokenize((start, stop, num, endpoint, chunks, dtype))
 
     dsk = {}
     blockstart = start
 
     for i, bs in enumerate(chunks[0]):
-        blockstop = blockstart + ((bs - 1) * space)
-        task = (partial(np.linspace, dtype=dtype), blockstart, blockstop, bs)
-        blockstart = blockstart + (space * bs)
+        bs_space = bs - 1 if endpoint else bs
+        blockstop = blockstart + (bs_space * step)
+        task = (partial(np.linspace, endpoint=endpoint, dtype=dtype),
+                blockstart, blockstop, bs)
+        blockstart = blockstart + (step * bs)
         dsk[(name, i)] = task
 
-    return Array(dsk, name, chunks, dtype=dtype)
+    if retstep:
+        return Array(dsk, name, chunks, dtype=dtype), step
+    else:
+        return Array(dsk, name, chunks, dtype=dtype)
 
 
 def arange(*args, **kwargs):


Test patch:
diff --git a/dask/array/tests/test_creation.py b/dask/array/tests/test_creation.py
--- a/dask/array/tests/test_creation.py
+++ b/dask/array/tests/test_creation.py
@@ -52,26 +52,32 @@ def test_arr_like(funcname, shape, dtype, chunks):
         assert (np_r == np.asarray(da_r)).all()
 
 
-def test_linspace():
-    darr = da.linspace(6, 49, chunks=5)
-    nparr = np.linspace(6, 49)
+@pytest.mark.parametrize("endpoint", [True, False])
+def test_linspace(endpoint):
+    darr = da.linspace(6, 49, endpoint=endpoint, chunks=5)
+    nparr = np.linspace(6, 49, endpoint=endpoint)
     assert_eq(darr, nparr)
 
-    darr = da.linspace(1.4, 4.9, chunks=5, num=13)
-    nparr = np.linspace(1.4, 4.9, num=13)
+    darr = da.linspace(1.4, 4.9, endpoint=endpoint, chunks=5, num=13)
+    nparr = np.linspace(1.4, 4.9, endpoint=endpoint, num=13)
     assert_eq(darr, nparr)
 
-    darr = da.linspace(6, 49, chunks=5, dtype=float)
-    nparr = np.linspace(6, 49, dtype=float)
+    darr = da.linspace(6, 49, endpoint=endpoint, chunks=5, dtype=float)
+    nparr = np.linspace(6, 49, endpoint=endpoint, dtype=float)
     assert_eq(darr, nparr)
 
-    darr = da.linspace(1.4, 4.9, chunks=5, num=13, dtype=int)
-    nparr = np.linspace(1.4, 4.9, num=13, dtype=int)
+    darr, dstep = da.linspace(6, 49, endpoint=endpoint, chunks=5, retstep=True)
+    nparr, npstep = np.linspace(6, 49, endpoint=endpoint, retstep=True)
+    assert np.allclose(dstep, npstep)
     assert_eq(darr, nparr)
-    assert (sorted(da.linspace(1.4, 4.9, chunks=5, num=13).dask) ==
-            sorted(da.linspace(1.4, 4.9, chunks=5, num=13).dask))
-    assert (sorted(da.linspace(6, 49, chunks=5, dtype=float).dask) ==
-            sorted(da.linspace(6, 49, chunks=5, dtype=float).dask))
+
+    darr = da.linspace(1.4, 4.9, endpoint=endpoint, chunks=5, num=13, dtype=int)
+    nparr = np.linspace(1.4, 4.9, num=13, endpoint=endpoint, dtype=int)
+    assert_eq(darr, nparr)
+    assert (sorted(da.linspace(1.4, 4.9, endpoint=endpoint, chunks=5, num=13).dask) ==
+            sorted(da.linspace(1.4, 4.9, endpoint=endpoint, chunks=5, num=13).dask))
+    assert (sorted(da.linspace(6, 49, endpoint=endpoint, chunks=5, dtype=float).dask) ==
+            sorted(da.linspace(6, 49, endpoint=endpoint, chunks=5, dtype=float).dask))
 
 
 def test_arange():