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 ebe0d3318c7b..3abf77f7c634 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -171,6 +171,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/mshadow_op.h b/src/operator/mshadow_op.h index fb5dc789cf3e..fa424ad6d0fc 100644 --- a/src/operator/mshadow_op.h +++ b/src/operator/mshadow_op.h @@ -683,6 +683,22 @@ struct fix : public mxnet_op::tunable { } }; +/*! \brief used to determine whether a number is Not A Number*/ +struct isnan : public mxnet_op::tunable { + template + MSHADOW_XINLINE static bool Map(DType a) { + return IsNan(a); + } +}; + +/*! \brief used to determine whether a number is infinite*/ +struct isinf : public mxnet_op::tunable { + template + MSHADOW_XINLINE static bool Map(DType a) { + return IsInf(a); + } +}; + /*! \brief used for generate gradient of MAE loss*/ MXNET_BINARY_MATH_OP_NC(minus_sign, a - b > DType(0) ? DType(1) : -DType(1)); diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cc b/src/operator/numpy/np_elemwise_unary_op_basic.cc index 5e15d7ad4e67..d13cb86030a7 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cc +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cc @@ -287,6 +287,14 @@ MXNET_OPERATOR_REGISTER_NUMPY_UNARY(_npi_expm1, "x", mshadow_op::expm1) MXNET_OPERATOR_REGISTER_NUMPY_UNARY_LOGIC(_npi_logical_not, "x", mshadow_op::np_logical_not) .set_attr("FGradient", MakeZeroGradNodes); +// isnan +MXNET_OPERATOR_REGISTER_NUMPY_UNARY_LOGIC(_npi_isnan, "x", mshadow_op::isnan) +.set_attr("FGradient", MakeZeroGradNodes); + +// isinf +MXNET_OPERATOR_REGISTER_NUMPY_UNARY_LOGIC(_npi_isinf, "x", mshadow_op::isinf) +.set_attr("FGradient", MakeZeroGradNodes); + // sin MXNET_OPERATOR_REGISTER_NUMPY_UNARY(_npi_sin, "x", mshadow_op::sin) .describe(R"code(Trigonometric sine, element-wise. diff --git a/src/operator/numpy/np_elemwise_unary_op_basic.cu b/src/operator/numpy/np_elemwise_unary_op_basic.cu index 517ef9c2b52a..9724f2d71b83 100644 --- a/src/operator/numpy/np_elemwise_unary_op_basic.cu +++ b/src/operator/numpy/np_elemwise_unary_op_basic.cu @@ -82,6 +82,12 @@ MXNET_OPERATOR_REGISTER_NUMPY_UNARY_GPU(_npi_expm1, mshadow_op::expm1); NNVM_REGISTER_OP(_npi_logical_not) .set_attr("FCompute", UnaryOp::ComputeLogic); +NNVM_REGISTER_OP(_npi_isnan) +.set_attr("FCompute", UnaryOp::ComputeLogic); + +NNVM_REGISTER_OP(_npi_isinf) +.set_attr("FCompute", UnaryOp::ComputeLogic); + MXNET_OPERATOR_REGISTER_NUMPY_UNARY_GPU(_npi_sin, mshadow_op::sin); MXNET_OPERATOR_REGISTER_NUMPY_UNARY_GPU(_npi_cos, mshadow_op::cos); diff --git a/src/operator/operator_tune.cc b/src/operator/operator_tune.cc index 745c3d5fe86d..c0e9a63af892 100644 --- a/src/operator/operator_tune.cc +++ b/src/operator/operator_tune.cc @@ -312,6 +312,8 @@ IMPLEMENT_UNARY_WORKLOAD_BWD(mxnet::op::mshadow_op::radians_grad); // NOLINT() IMPLEMENT_UNARY_WORKLOAD_FWD(mxnet::op::mshadow_op::nt); // NOLINT() IMPLEMENT_UNARY_WORKLOAD_FWD_WITH_BOOL(mxnet::op::mshadow_op::np_logical_not); // NOLINT() IMPLEMENT_UNARY_WORKLOAD_FWD_WITH_BOOL(mxnet::op::mshadow_op::bitwise_not); // NOLINT() +IMPLEMENT_UNARY_WORKLOAD_FWD_WITH_BOOL(mxnet::op::mshadow_op::isnan); // NOLINT() +IMPLEMENT_UNARY_WORKLOAD_FWD_WITH_BOOL(mxnet::op::mshadow_op::isinf); // NOLINT() IMPLEMENT_UNARY_WORKLOAD_BWD(mxnet::op::mshadow_op::nt); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::clip); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::clip); // NOLINT() diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py index a625cc848f6a..ad108522a58c 100644 --- a/tests/python/unittest/test_numpy_interoperability.py +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -1753,6 +1753,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) @@ -1933,6 +1951,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 726a6179c516..13817cbabe93 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -7099,6 +7099,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():