diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 0456a180b658..91dfba7c7281 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -29,7 +29,8 @@ __all__ = ['shape', 'zeros', 'zeros_like', 'ones', 'ones_like', 'full', 'full_like', 'empty_like', 'invert', 'delete', - 'add', 'broadcast_to', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'power', 'bitwise_not', + 'add', 'broadcast_to', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'fmod', + 'power', 'bitwise_not', 'arctan2', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'sqrt', 'cbrt', 'abs', 'insert', 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'matmul', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'histogram', @@ -1151,6 +1152,33 @@ def mod(x1, x2, out=None, **kwargs): return _ufunc_helper(x1, x2, _npi.mod, _np.mod, _npi.mod_scalar, _npi.rmod_scalar, out) +@set_module('mxnet.ndarray.numpy') +@wrap_np_binary_func +def fmod(x1, x2, out=None, **kwargs): + """ + Return element-wise remainder of division. + + Parameters + ---------- + x1 : ndarray or scalar + Dividend array. + + x2 : ndarray or scalar + Divisor array. + + out : ndarray + A location into which the result is stored. If provided, it must have a shape + that the inputs broadcast to. If not provided or None, a freshly-allocated array + is returned. + + Returns + ------- + out : ndarray or scalar + This is a scalar if both x1 and x2 are scalars. + """ + return _ufunc_helper(x1, x2, _npi.fmod, _np.fmod, _npi.fmod_scalar, out) + + @set_module('mxnet.ndarray.numpy') def delete(arr, obj, axis=None): """ diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 09520fd9e522..490d9a131e32 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -51,7 +51,8 @@ __all__ = ['ndarray', 'empty', 'empty_like', 'array', 'shape', 'zeros', 'zeros_like', 'ones', 'ones_like', 'full', 'full_like', 'broadcast_to', - 'add', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'power', 'bitwise_not', 'delete', + 'add', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'fmod', + 'power', 'bitwise_not', 'delete', 'arctan2', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'invert', 'sqrt', 'cbrt', 'abs', 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'log1p', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'histogram', @@ -3014,6 +3015,38 @@ def mod(x1, x2, out=None, **kwargs): return _mx_nd_np.mod(x1, x2, out=out) +@set_module('mxnet.numpy') +@wrap_np_binary_func +def fmod(x1, x2, out=None, **kwargs): + """ + Return element-wise remainder of division. + + Parameters + ---------- + x1 : ndarray or scalar + Dividend array. + + x2 : ndarray or scalar + Divisor array. + + out : ndarray + A location into which the result is stored. If provided, it must have a shape + that the inputs broadcast to. If not provided or None, a freshly-allocated array + is returned. + + Returns + ------- + out : ndarray or scalar + This is a scalar if both x1 and x2 are scalars. + + Examples + -------- + >>> np.fmod(np.arange(7), 5) + array([0., 1., 2., 3., 4., 0., 1.]) + """ + return _mx_nd_np.fmod(x1, x2, out=out) + + @set_module('mxnet.numpy') @wrap_np_binary_func def matmul(a, b, out=None, **kwargs): diff --git a/python/mxnet/numpy_dispatch_protocol.py b/python/mxnet/numpy_dispatch_protocol.py index 7325f692eabb..78ed2330dd78 100644 --- a/python/mxnet/numpy_dispatch_protocol.py +++ b/python/mxnet/numpy_dispatch_protocol.py @@ -243,6 +243,7 @@ def _register_array_function(): 'negative', 'power', 'mod', + 'fmod', 'matmul', 'absolute', 'rint', diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 87e421ac8ee8..71ced3844f90 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -36,7 +36,8 @@ from builtins import slice as py_slice __all__ = ['zeros', 'zeros_like', 'ones', 'ones_like', 'full', 'full_like', 'empty_like', 'bitwise_not', 'invert', - 'delete', 'add', 'broadcast_to', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'power', 'arctan2', + 'delete', 'add', 'broadcast_to', 'subtract', 'multiply', 'divide', 'mod', 'remainder', 'fmod', + 'power', 'arctan2', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'log10', 'sqrt', 'cbrt', 'abs', 'absolute', 'exp', 'expm1', 'arcsin', 'arccos', 'arctan', 'sign', 'log', 'degrees', 'log2', 'log1p', 'matmul', 'rint', 'radians', 'reciprocal', 'square', 'negative', 'fix', 'ceil', 'floor', 'histogram', 'insert', @@ -1576,6 +1577,12 @@ def mod(x1, x2, out=None, **kwargs): return _ufunc_helper(x1, x2, _npi.mod, _np.mod, _npi.mod_scalar, _npi.rmod_scalar, out) +@set_module('mxnet.symbol.numpy') +@wrap_np_binary_func +def fmod(x1, x2, out=None, **kwargs): + return _ufunc_helper(x1, x2, _npi.fmod, _np.fmod, _npi.fmod_scalar, out) + + @set_module('mxnet.symbol.numpy') @wrap_np_binary_func def remainder(x1, x2, out=None, **kwargs): diff --git a/src/operator/mshadow_op.h b/src/operator/mshadow_op.h index 7b7baa973585..b50256eecf9d 100644 --- a/src/operator/mshadow_op.h +++ b/src/operator/mshadow_op.h @@ -795,6 +795,17 @@ struct mod : public mxnet_op::tunable { } }; +struct fmod : public mxnet_op::tunable { + template + MSHADOW_XINLINE static DType Map(DType a, DType b) { + if (b == DType(0)) { + return DType(0); + } else { + return DType(::fmod(static_cast(a), static_cast(b))); + } + } +}; + template<> MSHADOW_XINLINE mshadow::half::half2_t mod::Map (mshadow::half::half2_t a, diff --git a/src/operator/numpy/np_elemwise_broadcast_op_extended.cc b/src/operator/numpy/np_elemwise_broadcast_op_extended.cc index 9bc550e94b94..308e254c2b4d 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op_extended.cc +++ b/src/operator/numpy/np_elemwise_broadcast_op_extended.cc @@ -427,5 +427,33 @@ MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_fmin_scalar) .set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) .set_attr("FCompute", BinaryScalarOp::Backward); +MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(_npi_fmod) +.set_attr("FCompute", BinaryBroadcastCompute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_npi_fmod"}); + +NNVM_REGISTER_OP(_backward_npi_fmod) +.set_num_inputs(3) +.set_num_outputs(2) +.set_attr("TIsBackward", true) +.set_attr("FInplaceOption", + [](const NodeAttrs& attrs){ + return std::vector >{{0, 1}}; + }) +.set_attr("FResourceRequest", + [](const NodeAttrs& attrs) { + return std::vector{ResourceRequest::kTempSpace}; + }) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + +MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(_npi_fmod_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute) +.set_attr("FGradient", ElemwiseGradUseIn{"_backward_npi_fmod_scalar"}); + +MXNET_OPERATOR_REGISTER_BINARY(_backward_npi_fmod_scalar) +.add_argument("scalar", "float", "scalar value") +.set_attr_parser([](NodeAttrs *attrs) { attrs->parsed = std::stod(attrs->dict["scalar"]); }) +.set_attr("FCompute", BinaryScalarOp::Backward); + } // namespace op } // namespace mxnet diff --git a/src/operator/numpy/np_elemwise_broadcast_op_extended.cu b/src/operator/numpy/np_elemwise_broadcast_op_extended.cu index c62a663a4f17..3bb6b2d0aee6 100644 --- a/src/operator/numpy/np_elemwise_broadcast_op_extended.cu +++ b/src/operator/numpy/np_elemwise_broadcast_op_extended.cu @@ -142,5 +142,18 @@ NNVM_REGISTER_OP(_npi_fmin_scalar) NNVM_REGISTER_OP(_backward_npi_fmin_scalar) .set_attr("FCompute", BinaryScalarOp::Backward); +NNVM_REGISTER_OP(_npi_fmod) +.set_attr("FCompute", BinaryBroadcastCompute); + +NNVM_REGISTER_OP(_backward_npi_fmod) +.set_attr("FCompute", BinaryBroadcastBackwardUseIn); + +NNVM_REGISTER_OP(_npi_fmod_scalar) +.set_attr("FCompute", BinaryScalarOp::Compute); + +NNVM_REGISTER_OP(_backward_npi_fmod_scalar) +.set_attr("FCompute", BinaryScalarOp::Backward); + } // namespace op } // namespace mxnet diff --git a/src/operator/operator_tune.cc b/src/operator/operator_tune.cc index 7e39671af4b6..85a14765680f 100644 --- a/src/operator/operator_tune.cc +++ b/src/operator/operator_tune.cc @@ -345,6 +345,7 @@ IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::div_rgrad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::div_rgrad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::rdiv_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::mod); // NOLINT() +IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::fmod); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::mod_grad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_BWD(mxnet::op::mshadow_op::mod_rgrad); // NOLINT() IMPLEMENT_BINARY_WORKLOAD_FWD(mxnet::op::mshadow_op::rmod); // NOLINT() diff --git a/tests/python/unittest/test_numpy_interoperability.py b/tests/python/unittest/test_numpy_interoperability.py index ee7f396ab9dc..1ca0706d5713 100644 --- a/tests/python/unittest/test_numpy_interoperability.py +++ b/tests/python/unittest/test_numpy_interoperability.py @@ -1392,6 +1392,13 @@ def _add_workload_mod(array_pool): OpArgMngr.add_workload('mod', array_pool['4x1'], array_pool['1x1x0']) +def _add_workload_fmod(array_pool): + OpArgMngr.add_workload('fmod', array_pool['4x1'], array_pool['1x2']) + OpArgMngr.add_workload('fmod', array_pool['4x1'], 2) + OpArgMngr.add_workload('fmod', 2, array_pool['4x1']) + OpArgMngr.add_workload('fmod', array_pool['4x1'], array_pool['1x1x0']) + + def _add_workload_remainder(): # test remainder basic OpArgMngr.add_workload('remainder', np.array([0, 1, 2, 4, 2], dtype=np.float16), @@ -2806,6 +2813,7 @@ def _prepare_workloads(): _add_workload_multiply(array_pool) _add_workload_power(array_pool) _add_workload_mod(array_pool) + _add_workload_fmod(array_pool) _add_workload_remainder() _add_workload_maximum(array_pool) _add_workload_fmax(array_pool) diff --git a/tests/python/unittest/test_numpy_op.py b/tests/python/unittest/test_numpy_op.py index 6cf8ad97a0c2..c974e016cb76 100644 --- a/tests/python/unittest/test_numpy_op.py +++ b/tests/python/unittest/test_numpy_op.py @@ -2254,6 +2254,12 @@ def hybrid_forward(self, F, a, b, *args, **kwargs): [lambda y, x1, x2: -_np.floor(x1 / x2), lambda y, x1, x2: _np.zeros(y.shape)], [[_np.float16, _np.float32, _np.float64], [_np.int32]]), + 'fmod': (1.0, 10.0, + [lambda y, x1, x2: _np.ones(y.shape), + lambda y, x1, x2: _np.zeros(y.shape)], + [lambda y, x1, x2: -_np.floor(x1 / x2), + lambda y, x1, x2: _np.zeros(y.shape)], + [[_np.float16, _np.float32, _np.float64], [_np.int32]]), 'remainder': (1.0, 10.0, [lambda y, x1, x2: _np.ones(y.shape), lambda y, x1, x2: _np.zeros(y.shape)],