diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index b95e0529f202..d6e76fe77b49 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -43,7 +43,7 @@ 'tril', 'identity', 'take', 'ldexp', 'vdot', 'inner', 'outer', 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'hsplit', 'rot90', 'einsum', 'true_divide', 'nonzero', 'quantile', 'percentile', 'shares_memory', 'may_share_memory', - 'diff', 'resize', 'nan_to_num', 'where', 'bincount'] + 'diff', 'resize', 'nan_to_num', 'isnan', 'isinf', 'where', 'bincount'] @set_module('mxnet.ndarray.numpy') @@ -6543,6 +6543,106 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None, **kwargs): raise TypeError('type {} not supported'.format(str(type(x)))) +@set_module('mxnet.ndarray.numpy') +@wrap_np_unary_func +def isnan(x, out=None, **kwargs): + """ + Test element-wise for NaN and return result as a boolean array. + + Parameters + ---------- + x : ndarray + Input array. + out : ndarray or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : ndarray or bool + True where x is NaN, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + + Examples + -------- + >>> np.isnan(np.nan) + True + >>> np.isnan(np.inf) + False + >>> np.isnan(np.array([np.log(-1.),1.,np.log(0)])) + array([ True, False, False]) + """ + return _unary_func_helper(x, _npi.isnan, _np.isnan, out=out, **kwargs) + + +@set_module('mxnet.ndarray.numpy') +@wrap_np_unary_func +def isinf(x, out=None, **kwargs): + """ + Test element-wise for positive or negative infinity. + + Parameters + ---------- + x : ndarray + Input array. + out : ndarray or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : ndarray or bool + True where x is positive or negative infinity, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + + Examples + -------- + >>> np.isinf(np.inf) + True + >>> np.isinf(np.nan) + False + >>> np.isinf(np.array([np.inf, -np.inf, 1.0, np.nan])) + array([ True, True, False, False]) + >>> x = np.array([-np.inf, 0., np.inf]) + >>> y = np.array([True, True, True], dtype=np.bool_) + >>> np.isinf(x, y) + array([ True, False, True]) + >>> y + array([ True, False, True]) + """ + return _unary_func_helper(x, _npi.isinf, _np.isinf, out=out, **kwargs) + + @set_module('mxnet.ndarray.numpy') def where(condition, x=None, y=None): """where(condition, [x, y]) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 2a58415bfce8..967127434b77 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -64,8 +64,8 @@ 'bitwise_and', 'bitwise_xor', 'bitwise_or', 'rad2deg', 'deg2rad', 'unique', 'lcm', 'tril', 'identity', 'take', 'ldexp', 'vdot', 'inner', 'outer', 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'hsplit', 'rot90', 'einsum', 'true_divide', 'nonzero', - 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', 'resize', 'nan_to_num', 'where', - 'bincount'] + 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', 'resize', + 'nan_to_num', 'isnan', 'isinf', 'where', 'bincount'] __all__ += fallback.__all__ @@ -8625,6 +8625,106 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None, **kwargs): return _mx_nd_np.nan_to_num(x, copy=copy, nan=nan, posinf=posinf, neginf=neginf) +@set_module('mxnet.numpy') +@wrap_np_unary_func +def isnan(x, out=None, **kwargs): + """ + Test element-wise for NaN and return result as a boolean array. + + Parameters + ---------- + x : ndarray + Input array. + out : ndarray or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : ndarray or bool + True where x is NaN, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + + Examples + -------- + >>> np.isnan(np.nan) + True + >>> np.isnan(np.inf) + False + >>> np.isnan(np.array([np.log(-1.),1.,np.log(0)])) + array([ True, False, False]) + """ + return _mx_nd_np.isnan(x, out=out, **kwargs) + + +@set_module('mxnet.numpy') +@wrap_np_unary_func +def isinf(x, out=None, **kwargs): + """ + Test element-wise for positive or negative infinity. + + Parameters + ---------- + x : ndarray + Input array. + out : ndarray or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : ndarray or bool + True where x is positive or negative infinity, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + + Examples + -------- + >>> np.isinf(np.inf) + True + >>> np.isinf(np.nan) + False + >>> np.isinf(np.array([np.inf, -np.inf, 1.0, np.nan])) + array([ True, True, False, False]) + >>> x = np.array([-np.inf, 0., np.inf]) + >>> y = np.array([True, True, True], dtype=np.bool_) + >>> np.isinf(x, y) + array([ True, False, True]) + >>> y + array([ True, False, True]) + """ + return _mx_nd_np.isinf(x, out=out, **kwargs) + + @set_module('mxnet.numpy') def where(condition, x=None, y=None): """where(condition, [x, y]) diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py index 740a88c0d399..46d459cf5a89 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -170,6 +170,8 @@ def _run_with_array_ufunc_proto(*args, **kwargs): 'bincount', 'empty_like', 'nan_to_num', + 'isnan', + 'isinf', ] diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 29e22d3ba97b..86dedde85744 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -51,7 +51,7 @@ 'tril', 'identity', 'take', 'ldexp', 'vdot', 'inner', 'outer', 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'hsplit', 'rot90', 'einsum', 'true_divide', 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', - 'resize', 'nan_to_num', 'where', 'bincount'] + 'resize', 'nan_to_num', 'isnan', 'isinf', 'where', 'bincount'] @set_module('mxnet.symbol.numpy') @@ -5883,6 +5883,82 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None, **kwargs): raise TypeError('type {} not supported'.format(str(type(x)))) +@set_module('mxnet.symbol.numpy') +@wrap_np_unary_func +def isnan(x, out=None, **kwargs): + """ + Test element-wise for NaN and return result as a boolean array. + + Parameters + ---------- + x : _Symbol + Input array. + out : _Symbol or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : _Symbol or bool + True where x is NaN, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + """ + return _unary_func_helper(x, _npi.isnan, _np.isnan, out=out, **kwargs) + + +@set_module('mxnet.symbol.numpy') +@wrap_np_unary_func +def isinf(x, out=None, **kwargs): + """ + Test element-wise for positive or negative infinity. + + Parameters + ---------- + x : _Symbol + Input array. + out : ndarray or None, optional + A location into which the result is stored. + If provided, it must have the same shape and dtype as input ndarray. + If not provided or `None`, a freshly-allocated array is returned. + + Returns + ------- + y : _Symbol or bool + True where x is positive or negative infinity, false otherwise. + This is a scalar if x is a scalar. + + Notes + ----- + NumPy uses the IEEE Standard for Binary Floating-Point for Arithmetic (IEEE 754). + This means that Not a Number is not equivalent to infinity. + + This function differs from the original `numpy.isnan + `_ in + the following aspects: + - Does not support complex number for now + - Input type does not support Python native iterables(list, tuple, ...). + - ``out`` param: cannot perform auto broadcasting. ``out`` ndarray's shape must be the same as the expected output. + - ``out`` param: cannot perform auto type cast. ``out`` ndarray's dtype must be the same as the expected output. + - ``out`` param does not support scalar input case. + """ + return _unary_func_helper(x, _npi.isinf, _np.isinf, out=out, **kwargs) + + @set_module('mxnet.symbol.numpy') def where(condition, x, y): """ diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cc b/src/operator/numpy/np_elemwise_unary_op_basic.cc index 5e15d7ad4e67..de292d5e51ff 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cc +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cc @@ -27,6 +27,15 @@ namespace mxnet { namespace op { +inline bool NumpyInferBoolType(const nnvm::NodeAttrs& attrs, + std::vector *in_attrs, + std::vector *out_attrs) { + CHECK_EQ(in_attrs->size(), 1U); + CHECK_EQ(out_attrs->size(), 1U); + TYPE_ASSIGN_CHECK(*out_attrs, 0, mshadow::kBool); + return out_attrs->at(0) != -1 && in_attrs->at(0) != -1; +} + MXNET_OPERATOR_REGISTER_UNARY(_npx_relu) .describe(R"code(Computes rectified linear activation. .. math:: @@ -462,5 +471,41 @@ NNVM_REGISTER_OP(_npi_backward_nan_to_num) .set_attr("TIsBackward", true) .set_attr("FCompute", NumpyNanToNumOpBackward); +NNVM_REGISTER_OP(_npi_isnan) +.describe("" ADD_FILELINE) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferType", NumpyInferBoolType) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 0}}; + }) +.set_attr("FCompute", NumpyIsNanOpForward) +.set_attr("FGradient", MakeZeroGradNodes) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + return std::vector{"x"}; + }) +.add_argument("x", "NDArray-or-Symbol", "The input array."); + +NNVM_REGISTER_OP(_npi_isinf) +.describe("" ADD_FILELINE) +.set_num_inputs(1) +.set_num_outputs(1) +.set_attr("FInferShape", ElemwiseShape<1, 1>) +.set_attr("FInferType", NumpyInferBoolType) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 0}}; + }) +.set_attr("FCompute", NumpyIsInfOpForward) +.set_attr("FGradient", MakeZeroGradNodes) +.set_attr("FListInputNames", + [](const NodeAttrs& attrs) { + return std::vector{"x"}; + }) +.add_argument("x", "NDArray-or-Symbol", "The input array."); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cu b/src/operator/numpy/np_elemwise_unary_op_basic.cu index 517ef9c2b52a..e1147c8e3edb 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cu +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cu @@ -119,5 +119,11 @@ NNVM_REGISTER_OP(_npi_nan_to_num) NNVM_REGISTER_OP(_npi_backward_nan_to_num) .set_attr("FCompute", NumpyNanToNumOpBackward); +NNVM_REGISTER_OP(_npi_isnan) +.set_attr("FCompute", NumpyIsNanOpForward); + +NNVM_REGISTER_OP(_npi_isinf) +.set_attr("FCompute", NumpyIsInfOpForward); + } // namespace op } // namespace mxnet diff --git a/src/operator/tensor/elemwise_unary_op.h b/src/operator/tensor/elemwise_unary_op.h index 4486b0dcd712..f638036e9c48 100644 --- a/src/operator/tensor/elemwise_unary_op.h +++ b/src/operator/tensor/elemwise_unary_op.h @@ -806,6 +806,102 @@ void NumpyNanToNumOpBackward(const nnvm::NodeAttrs& attrs, }); } +template +struct isnan_forward_float { + template + MSHADOW_XINLINE static void Map(int i, + bool* out_data, + const DType* in_data) { + KERNEL_ASSIGN(out_data[i], req, mshadow_op::IsNan(in_data[i])); + } +}; + +template +struct isnan_forward_int { + template + MSHADOW_XINLINE static void Map(int i, + bool* out_data, + const DType* in_data) { + KERNEL_ASSIGN(out_data[i], req, false); + } +}; + +template +void NumpyIsNanOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mxnet; + CHECK_EQ(inputs.size(), 1U); + CHECK_EQ(outputs.size(), 1U); + CHECK_EQ(req.size(), 1U); + mshadow::Stream *s = ctx.get_stream(); + const TBlob& in_data = inputs[0]; + const TBlob& out_data = outputs[0]; + using namespace mxnet_op; + + MSHADOW_TYPE_SWITCH(in_data.type_flag_, DType, { + MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, { + if (!common::is_float(in_data.type_flag_)) { + Kernel, xpu>::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr()); + } else { + Kernel, xpu>::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr()); + } + }); + }); +} + +template +struct isinf_forward_float { + template + MSHADOW_XINLINE static void Map(int i, + bool* out_data, + const DType* in_data) { + KERNEL_ASSIGN(out_data[i], req, mshadow_op::IsInf(in_data[i])); + } +}; + +template +struct isinf_forward_int { + template + MSHADOW_XINLINE static void Map(int i, + bool* out_data, + const DType* in_data) { + KERNEL_ASSIGN(out_data[i], req, false); + } +}; + +template +void NumpyIsInfOpForward(const nnvm::NodeAttrs& attrs, + const OpContext& ctx, + const std::vector& inputs, + const std::vector& req, + const std::vector& outputs) { + using namespace mxnet; + CHECK_EQ(inputs.size(), 1U); + CHECK_EQ(outputs.size(), 1U); + CHECK_EQ(req.size(), 1U); + mshadow::Stream *s = ctx.get_stream(); + const TBlob& in_data = inputs[0]; + const TBlob& out_data = outputs[0]; + using namespace mxnet_op; + + MSHADOW_TYPE_SWITCH(in_data.type_flag_, DType, { + MXNET_ASSIGN_REQ_SWITCH(req[0], req_type, { + if (!common::is_float(in_data.type_flag_)) { + Kernel, xpu>::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr()); + } else { + Kernel, xpu>::Launch( + s, out_data.Size(), out_data.dptr(), in_data.dptr()); + } + }); + }); +} + /*! \brief Unary compute */ #define MXNET_OPERATOR_REGISTER_UNARY(__name$) \ NNVM_REGISTER_OP(__name$) \ diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py index 310d65bdd5cb..ac061b860a6e 100644 --- a/tests/python/unittest/test_numpy_interoperability.py +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -1739,6 +1739,24 @@ def _add_workload_nan_to_num(): OpArgMngr.add_workload('nan_to_num', array3, True) +def _add_workload_isnan(): + array1 = np.array([[-_np.nan, 0, 456, _np.inf], [-1, -_np.inf, 0, _np.nan]]) + array2 = np.array([_np.inf/_np.inf, _np.inf, _np.nan, -574, 0, 23425, _np.nan,-5]) + array3 = np.array(_np.nan) + OpArgMngr.add_workload('isnan', array1,) + OpArgMngr.add_workload('isnan', array2) + OpArgMngr.add_workload('isnan', array3) + + +def _add_workload_isinf(): + array1 = np.array([[-433, float('inf'), 456, _np.inf], [-1, -_np.inf, 0, 1]]) + array2 = np.array([_np.inf/_np.inf, _np.inf, -_np.inf, -574, 0, 23425, _np.inf,-5]) + array3 = np.array(_np.inf) + OpArgMngr.add_workload('isinf', array1) + OpArgMngr.add_workload('isinf', array2) + OpArgMngr.add_workload('isinf', array3) + + def _add_workload_linalg_cond(): A = np.array([[1., 0, 1], [0, -2., 0], [0, 0, 3.]]) OpArgMngr.add_workload('linalg.cond', A, np.inf) @@ -1918,6 +1936,8 @@ def _prepare_workloads(): _add_workload_full_like(array_pool) _add_workload_empty_like() _add_workload_nan_to_num() + _add_workload_isnan() + _add_workload_isinf() _add_workload_heaviside() _add_workload_spacing() diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index ee18ddec8045..175071dfcd63 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -7059,6 +7059,72 @@ def hybrid_forward(self, F, a): assert_almost_equal(mx_out.asnumpy(), np_out, rtol=1e-3, atol=1e-5, use_broadcast=False) +@with_seed() +@use_np +def test_np_isnan_isinf(): + def check_unary_func(func): + class TestUnary(HybridBlock): + def __init__(self, func): + super(TestUnary, self).__init__() + self._func = func + + def hybrid_forward(self, F, a): + return getattr(F.np, self._func)(a) + + src_list = [ + _np.nan, + _np.inf, + -_np.inf, + _np.array(0)/0, + _np.inf/_np.inf, + 1, + [_np.nan], + [_np.inf], + [-_np.inf], + [_np.array(0)/0], + [1], + [1,2,3,4,-1,-2,-3,-4,0], + [_np.nan, _np.inf, -_np.inf], + [_np.nan, _np.inf, -_np.inf, -574, 0, 23425, 24234,-5], + [_np.nan, -1, 0, 1], + [[-433, 0, 456, _np.inf], [-1, -_np.inf, 0, 1]] + ] + + np_func = getattr(_np, func) + mx_func = TestUnary(func) + dtype_list = ['float16', 'float32', 'float64'] + hybridize_list = [True, False] + atol, rtol = 1e-5, 1e-3 + + for [hybridize, dtype, src] in itertools.product(hybridize_list, dtype_list, src_list): + mx_data = mx.np.array(src, dtype=dtype) + np_data = mx_data.asnumpy() + + if hybridize: + mx_func.hybridize() + with mx.autograd.record(): + mx_out= mx_func(mx_data) + + assert mx_out.dtype == np.bool_ + + np_out = np_func(np_data) + assert_almost_equal(mx_out.asnumpy(), np_out, rtol, atol) + mx_out_imperative = getattr(mx.np, func)(mx_data) + assert_almost_equal(mx_out_imperative .asnumpy(), np_out, rtol, atol) + + assertRaises(NotImplementedError, getattr(np, func), mx_data, where=False) + assertRaises(NotImplementedError, getattr(np, func), mx_data, subok=False) + assertRaises(NotImplementedError, getattr(np, func), mx_data, dtype=_np.int8) + assertRaises(TypeError, getattr(np, func), mx_data, dtype="abcdefg") + assertRaises(NotImplementedError, getattr(np, func), mx_data, casting='safe') + assertRaises(TypeError, getattr(np, func), mx_data, casting='mxnet') + assertRaises(NotImplementedError, getattr(np, func), mx_data, order='C') + assertRaises(NotImplementedError, getattr(np, func), mx_data, order='mxnet') + + check_unary_func("isnan") + check_unary_func("isinf") + + @with_seed() @use_np def test_np_where():