From 795990b81b3fd40fed0dbea801dc33dfb6475a00 Mon Sep 17 00:00:00 2001 From: Jake Lee Date: Mon, 12 Aug 2019 20:52:43 -0700 Subject: [PATCH] numpy linspace (#15852) --- python/mxnet/ndarray/numpy/_op.py | 100 ++++++++++++++++++++++++- python/mxnet/numpy/multiarray.py | 88 +++++++++++++++++++++- python/mxnet/symbol/numpy/_symbol.py | 75 ++++++++++++++++++- src/operator/tensor/init_op.cc | 1 + tests/python/unittest/test_numpy_op.py | 70 +++++++++++++++++ 5 files changed, 331 insertions(+), 3 deletions(-) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index caa9ba1de67c..0cc2192cdcac 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -24,8 +24,9 @@ from ...util import set_module from ...context import current_context from . import _internal as _npi +from ..ndarray import NDArray -__all__ = ['zeros', 'ones', 'add', 'subtract', 'multiply', 'divide', 'mod', 'power', 'tensordot'] +__all__ = ['zeros', 'ones', 'add', 'subtract', 'multiply', 'divide', 'mod', 'power', 'tensordot', 'linspace'] @set_module('mxnet.ndarray.numpy') @@ -364,3 +365,100 @@ def tensordot(a, b, axes=2): raise ValueError('Axes length mismatch') return _npi.tensordot(a, b, a_axes_summed, b_axes_summed) + + +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, ctx=None): # pylint: disable=too-many-arguments + r""" + Return evenly spaced numbers over a specified interval. + + Returns num evenly spaced samples, calculated over the interval [start, stop]. + The endpoint of the interval can optionally be excluded. + + Parameters + ---------- + start : real number + The starting value of the sequence. + stop : real number + The end value of the sequence, unless endpoint is set to False. In + that case, the sequence consists of all but the last of num + 1 + evenly spaced samples, so that stop is excluded. Note that the step + size changes when endpoint is False. + num : int, optional + Number of samples to generate. Default is 50. Must be non-negative. + 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. + dtype : dtype, optional + The type of the output array. If dtype is not given, infer the data + type from the other input arguments. + axis : int, optional + The axis in the result to store the samples. Relevant only if start or + stop are array-like. By default (0), the samples will be along a new + axis inserted at the beginning. Use -1 to get an axis at the end. + + Returns + ------- + samples : ndarray + There are num equally spaced samples in the closed interval + `[start, stop]` or the half-open interval `[start, stop)` + (depending on whether endpoint is True or False). + step : float, optional + Only returned if retstep is True + Size of spacing between samples. + + + See Also + -------- + arange : Similar to `linspace`, but uses a step size (instead of the + number of samples). + + Examples + -------- + >>> np.linspace(2.0, 3.0, num=5) + array([2. , 2.25, 2.5 , 2.75, 3. ]) + >>> np.linspace(2.0, 3.0, num=5, endpoint=False) + array([2. , 2.2, 2.4, 2.6, 2.8]) + >>> np.linspace(2.0, 3.0, num=5, retstep=True) + (array([2. , 2.25, 2.5 , 2.75, 3. ]), 0.25) + + Graphical illustration: + + >>> import matplotlib.pyplot as plt + >>> N = 8 + >>> y = np.zeros(N) + >>> x1 = np.linspace(0, 10, N, endpoint=True) + >>> x2 = np.linspace(0, 10, N, endpoint=False) + >>> plt.plot(x1.asnumpy(), y.asnumpy(), 'o') + [] + >>> plt.plot(x2.asnumpy(), (y + 0.5).asnumpy(), 'o') + [] + >>> plt.ylim([-0.5, 1]) + (-0.5, 1) + >>> plt.show() + + Notes + ----- + + This function differs from the original `numpy.linspace + `_ in + the following aspects: + + - `start` and `stop` do not support list, numpy ndarray and mxnet ndarray + - axis could only be 0 + - There could be an additional `ctx` argument to specify the device, e.g. the i-th + GPU. + """ + if isinstance(start, (list, _np.ndarray, NDArray)) or \ + isinstance(stop, (list, _np.ndarray, NDArray)): + raise NotImplementedError('start and stop only support int') + if axis != 0: + raise NotImplementedError("the function only support axis 0") + if ctx is None: + ctx = current_context() + if retstep: + step = (stop - start) / (num - 1) + return _npi.linspace(start=start, stop=stop, num=num, endpoint=endpoint, ctx=ctx, dtype=dtype), step + else: + return _npi.linspace(start=start, stop=stop, num=num, endpoint=endpoint, ctx=ctx, dtype=dtype) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index f4b6e733c495..8a9028bf5125 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -44,7 +44,7 @@ from ..ndarray.numpy import _internal as _npi __all__ = ['ndarray', 'empty', 'array', 'zeros', 'ones', 'add', 'subtract', 'multiply', 'divide', - 'mod', 'power', 'tensordot'] + 'mod', 'power', 'tensordot', 'linspace'] # This function is copied from ndarray.py since pylint @@ -1606,3 +1606,89 @@ def tensordot(a, b, axes=2): [ 4928., 5306.]]) """ return _mx_nd_np.tensordot(a, b, axes) + + +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, ctx=None): # pylint: disable=too-many-arguments + r""" + Return evenly spaced numbers over a specified interval. + + Returns num evenly spaced samples, calculated over the interval [start, stop]. + The endpoint of the interval can optionally be excluded. + + Parameters + ---------- + start : real number + The starting value of the sequence. + stop : real number + The end value of the sequence, unless endpoint is set to False. In + that case, the sequence consists of all but the last of num + 1 + evenly spaced samples, so that stop is excluded. Note that the step + size changes when endpoint is False. + num : int, optional + Number of samples to generate. Default is 50. Must be non-negative. + 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. + dtype : dtype, optional + The type of the output array. If dtype is not given, infer the data + type from the other input arguments. + axis : int, optional + The axis in the result to store the samples. Relevant only if start or + stop are array-like. By default (0), the samples will be along a new + axis inserted at the beginning. Use -1 to get an axis at the end. + + Returns + ------- + samples : ndarray + There are num equally spaced samples in the closed interval + `[start, stop]` or the half-open interval `[start, stop)` + (depending on whether endpoint is True or False). + step : float, optional + Only returned if retstep is True + Size of spacing between samples. + + + See Also + -------- + arange : Similar to `linspace`, but uses a step size (instead of the + number of samples). + + Examples + -------- + >>> np.linspace(2.0, 3.0, num=5) + array([2. , 2.25, 2.5 , 2.75, 3. ]) + >>> np.linspace(2.0, 3.0, num=5, endpoint=False) + array([2. , 2.2, 2.4, 2.6, 2.8]) + >>> np.linspace(2.0, 3.0, num=5, retstep=True) + (array([2. , 2.25, 2.5 , 2.75, 3. ]), 0.25) + + Graphical illustration: + + >>> import matplotlib.pyplot as plt + >>> N = 8 + >>> y = np.zeros(N) + >>> x1 = np.linspace(0, 10, N, endpoint=True) + >>> x2 = np.linspace(0, 10, N, endpoint=False) + >>> plt.plot(x1.asnumpy(), y.asnumpy(), 'o') + [] + >>> plt.plot(x2.asnumpy(), (y + 0.5).asnumpy(), 'o') + [] + >>> plt.ylim([-0.5, 1]) + (-0.5, 1) + >>> plt.show() + + Notes + ----- + + This function differs from the original `numpy.linspace + `_ in + the following aspects: + + - `start` and `stop` do not support list, numpy ndarray and mxnet ndarray + - axis could only be 0 + - There could be an additional `ctx` argument to specify the device, e.g. the i-th + GPU. + """ + return _mx_nd_np.linspace(start, stop, num, endpoint, retstep, dtype, axis, ctx) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 65db429e8ba7..d0137932b8ec 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -28,7 +28,7 @@ from .._internal import _set_np_symbol_class from . import _internal as _npi -__all__ = ['zeros', 'ones', 'add', 'subtract', 'multiply', 'divide', 'mod', 'power', 'tensordot'] +__all__ = ['zeros', 'ones', 'add', 'subtract', 'multiply', 'divide', 'mod', 'power', 'tensordot', 'linspace'] def _num_outputs(sym): @@ -1065,4 +1065,77 @@ def tensordot(a, b, axes=2): return _npi.tensordot(a, b, a_axes_summed, b_axes_summed) +def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0, ctx=None): # pylint: disable=too-many-arguments + r""" + Return evenly spaced numbers over a specified interval. + + Returns num evenly spaced samples, calculated over the interval [start, stop]. + The endpoint of the interval can optionally be excluded. + + Parameters + ---------- + start : real number + The starting value of the sequence. + stop : real number + The end value of the sequence, unless endpoint is set to False. In + that case, the sequence consists of all but the last of num + 1 + evenly spaced samples, so that stop is excluded. Note that the step + size changes when endpoint is False. + num : int, optional + Number of samples to generate. Default is 50. Must be non-negative. + 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. + dtype : dtype, optional + The type of the output array. If dtype is not given, infer the data + type from the other input arguments. + axis : int, optional + The axis in the result to store the samples. Relevant only if start or + stop are array-like. By default (0), the samples will be along a new + axis inserted at the beginning. Use -1 to get an axis at the end. + + Returns + ------- + samples : _Symbol + There are num equally spaced samples in the closed interval + `[start, stop]` or the half-open interval `[start, stop)` + (depending on whether endpoint is True or False). + step : float, optional + Only returned if retstep is True + Size of spacing between samples. + + + See Also + -------- + arange : Similar to `linspace`, but uses a step size (instead of the + number of samples). + + Notes + ----- + + This function differs from the original `numpy.linspace + `_ in + the following aspects: + + - `start` and `stop` do not support list, numpy ndarray and mxnet ndarray + - axis could only be 0 + - There could be an additional `ctx` argument to specify the device, e.g. the i-th + GPU. + """ + if isinstance(start, (list, _np.ndarray)) or \ + isinstance(stop, (list, _np.ndarray)): + raise NotImplementedError('start and stop only support int') + if axis != 0: + raise NotImplementedError("the function only support axis 0") + if ctx is None: + ctx = current_context() + if retstep: + step = (stop - start) / (num - 1) + return (_npi.linspace(start=start, stop=stop, num=num, endpoint=endpoint, ctx=ctx, dtype=dtype), step) + else: + return _npi.linspace(start=start, stop=stop, num=num, endpoint=endpoint, ctx=ctx, dtype=dtype) + + _set_np_symbol_class(_Symbol) diff --git a/src/operator/tensor/init_op.cc b/src/operator/tensor/init_op.cc index 239cff798e59..172665fbbf12 100644 --- a/src/operator/tensor/init_op.cc +++ b/src/operator/tensor/init_op.cc @@ -138,6 +138,7 @@ Examples:: .add_arguments(RangeLikeParam::__FIELDS__()); NNVM_REGISTER_OP(_linspace) +.add_alias("_npi_linspace") .describe("Return evenly spaced numbers over a specified interval. Similar to Numpy") .set_num_inputs(0) .set_num_outputs(1) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index b0388d7c996c..12d9f779fd50 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -275,6 +275,76 @@ def is_int(dtype): assert_almost_equal(mx_out.asnumpy(), np_out, rtol=1e-3, atol=1e-5) +@with_seed() +@use_np +def test_np_linspace(): + configs = [ + (0.0, 1.0, 10), + (-2, 4, 30), + (5.234324, 8.98324, 324), + (2, 10, 100) + ] + exception_configs = [ + (0, 10, -1), + (0, 1, 2.5) + ] + dtypes = ['int32', 'float16', 'float32', 'float64', None] + for config in configs: + for dtype in dtypes: + for endpoint in [False, True]: + for retstep in [False, True]: + if isinstance(config, tuple): + mx_ret = np.linspace(*config, endpoint=endpoint, retstep=retstep, dtype=dtype) + np_ret = _np.linspace(*config, endpoint=endpoint, retstep=retstep, dtype=dtype) + else: + mx_ret = np.linspace(config, endpoint=endpoint, retstep=retstep, dtype=dtype) + np_ret = _np.linspace(config, endpoint=endpoint, retstep=retstep, dtype=dtype) + if retstep: + assert_almost_equal(mx_ret[0].asnumpy(), np_ret[0], atol=1e-3, rtol=1e-5) + same(mx_ret[1], np_ret[1]) + else: + assert_almost_equal(mx_ret.asnumpy(), np_ret, atol=1e-3, rtol=1e-5) + # check for exception input + for config in exception_configs: + assertRaises(MXNetError, np.linspace, *config) + # check linspace equivalent to arange + for test_index in range(1000): + assert_almost_equal(mx.np.linspace(0, test_index, test_index + 1).asnumpy(), _np.arange(test_index + 1)) + @use_np + class TestLinspace(HybridBlock): + def __init__(self, start, stop, num=50, endpoint=None, retstep=False, dtype=None, axis=0): + super(TestLinspace, self).__init__() + self._start = start + self._stop = stop + self._num = num + self._endpoint = endpoint + self._retstep = retstep + self._dtype = dtype + + def hybrid_forward(self, F, x): + if self._retstep: + raise ValueError("linspace didn't support retstep = True inside HybridBlock") + else: + return x + F.np.linspace(self._start, self._stop, self._num, \ + self._endpoint, self._retstep, self._dtype) + + for dtype in dtypes: + x = np.zeros(shape=(), dtype=dtype) + for config in configs: + for hybridize in [False, True]: + for endpoint in [False, True]: + if isinstance(config, tuple): + net = TestLinspace(*config, endpoint=endpoint, dtype=dtype) + np_out = _np.linspace(*config, endpoint=endpoint, dtype=dtype) + else: + net = TestLinspace(config, endpoint=endpoint, dtype=dtype) + np_out = _np.linspace(config, endpoint=endpoint, dtype=dtype) + if hybridize: + net.hybridize() + mx_out = net(x) + assert_almost_equal(mx_out.asnumpy(), np_out, atol=1e-3, rtol=1e-5) + + @with_seed() @use_np def test_npx_slice():