diff --git a/benchmark/python/ffi/benchmark_ffi.py b/benchmark/python/ffi/benchmark_ffi.py index 1983de594b28..ac7793706a22 100644 --- a/benchmark/python/ffi/benchmark_ffi.py +++ b/benchmark/python/ffi/benchmark_ffi.py @@ -81,6 +81,18 @@ def prepare_workloads(): OpArgMngr.add_workload("random.uniform", low=0, high=1, size=1) OpArgMngr.add_workload("where", pool['2x3'], pool['2x3'], pool['2x1']) OpArgMngr.add_workload("may_share_memory", pool['2x3'][:0], pool['2x3'][:1]) + OpArgMngr.add_workload("diag", pool['2x2'], k=1) + OpArgMngr.add_workload("diagonal", pool['2x2x2'], offset=-1, axis1=0, axis2=1) + OpArgMngr.add_workload("diag_indices_from", pool['2x2']) + OpArgMngr.add_workload("bincount", dnp.arange(3, dtype=int), pool['3'], minlength=4) + OpArgMngr.add_workload("percentile", pool['2x2x2'], 80, axis=0, out=pool['2x2'],\ + interpolation='midpoint') + OpArgMngr.add_workload("quantile", pool['2x2x2'], 0.8, axis=0, out=pool['2x2'],\ + interpolation='midpoint') + OpArgMngr.add_workload("all", pool['2x2x2'], axis=(0, 1),\ + out=dnp.array([False, False], dtype=bool), keepdims=False) + OpArgMngr.add_workload("any", pool['2x2x2'], axis=(0, 1),\ + out=dnp.array([False, False], dtype=bool), keepdims=False) OpArgMngr.add_workload("roll", pool["2x2"], 1, axis=0) OpArgMngr.add_workload("rot90", pool["2x2"], 2) diff --git a/python/mxnet/ndarray/numpy/_op.py b/python/mxnet/ndarray/numpy/_op.py index 32519d142c1e..fdfefdd6793a 100644 --- a/python/mxnet/ndarray/numpy/_op.py +++ b/python/mxnet/ndarray/numpy/_op.py @@ -46,7 +46,7 @@ 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'roll', 'rot90', 'einsum', 'true_divide', 'nonzero', 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', 'ediff1d', 'resize', 'polyval', 'nan_to_num', 'isnan', 'isinf', 'isposinf', 'isneginf', 'isfinite', - 'where', 'bincount', 'pad', 'cumsum'] + 'where', 'bincount', 'pad', 'cumsum', 'diag', 'diagonal'] @set_module('mxnet.ndarray.numpy') @@ -5058,6 +5058,7 @@ def ravel(x, order='C'): raise TypeError('type {} not supported'.format(str(type(x)))) +@set_module('mxnet.ndarray.numpy') def unravel_index(indices, shape, order='C'): # pylint: disable=redefined-outer-name """ Converts a flat index or array of flat indices into a tuple of coordinate arrays. @@ -5087,11 +5088,7 @@ def unravel_index(indices, shape, order='C'): # pylint: disable=redefined-outer- if order == 'C': if isinstance(indices, numeric_types): return _np.unravel_index(indices, shape) - ret = _npi.unravel_index_fallback(indices, shape=shape) - ret_list = [] - for item in ret: - ret_list += [item] - return tuple(ret_list) + return tuple(_npi.unravel_index_fallback(indices, shape=shape)) else: raise NotImplementedError('Do not support column-major (Fortran-style) order at this moment') @@ -5135,6 +5132,7 @@ def flatnonzero(a): return nonzero(ravel(a))[0] +@set_module('mxnet.ndarray.numpy') def diag_indices_from(arr): """ This returns a tuple of indices that can be used to access the main diagonal of an array @@ -5171,7 +5169,7 @@ def diag_indices_from(arr): [ 8, 9, 100, 11], [ 12, 13, 14, 100]]) """ - return tuple(_npi.diag_indices_from(arr)) + return tuple(_api_internal.diag_indices_from(arr)) @set_module('mxnet.ndarray.numpy') @@ -7824,3 +7822,97 @@ def cumsum(a, axis=None, dtype=None, out=None): [ 4, 9, 15]]) """ return _api_internal.cumsum(a, axis, dtype, out) + + +@set_module('mxnet.ndarray.numpy') +def diag(v, k=0): + """ + Extracts a diagonal or constructs a diagonal array. + - 1-D arrays: constructs a 2-D array with the input as its diagonal, all other elements are zero. + - 2-D arrays: extracts the k-th Diagonal + + Parameters + ---------- + array : ndarray + The array to apply diag method. + k : offset + extracts or constructs kth diagonal given input array + + Returns + ---------- + out : ndarray + The extracted diagonal or constructed diagonal array. + + Examples + -------- + >>> x = np.arange(9).reshape((3,3)) + >>> x + array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + >>> np.diag(x) + array([0, 4, 8]) + >>> np.diag(x, k=1) + array([1, 5]) + >>> np.diag(x, k=-1) + array([3, 7]) + + >>> np.diag(np.diag(x)) + array([[0, 0, 0], + [0, 4, 0], + [0, 0, 8]]) + """ + return _api_internal.diag(v, k) + + +@set_module('mxnet.ndarray.numpy') +def diagonal(a, offset=0, axis1=0, axis2=1): + """ + If a is 2-D, returns the diagonal of a with the given offset, i.e., the collection of elements of + the form a[i, i+offset]. If a has more than two dimensions, then the axes specified by axis1 and + axis2 are used to determine the 2-D sub-array whose diagonal is returned. The shape of the + resulting array can be determined by removing axis1 and axis2 and appending an index to the + right equal to the size of the resulting diagonals. + + Parameters + ---------- + a : ndarray + Input data from which diagonal are taken. + offset: int, Optional + Offset of the diagonal from the main diagonal + axis1: int, Optional + Axis to be used as the first axis of the 2-D sub-arrays + axis2: int, Optional + Axis to be used as the second axis of the 2-D sub-arrays + + Returns + ------- + out : ndarray + Output result + + Raises + ------- + ValueError: If the dimension of a is less than 2. + + Examples + -------- + >>> a = np.arange(4).reshape(2,2) + >>> a + array([[0, 1], + [2, 3]]) + >>> np.diagonal(a) + array([0, 3]) + >>> np.diagonal(a, 1) + array([1]) + + >>> a = np.arange(8).reshape(2,2,2) + >>>a + array([[[0, 1], + [2, 3]], + [[4, 5], + [6, 7]]]) + >>> np.diagonal(a, 0, 0, 1) + array([[0, 6], + [1, 7]]) + """ + return _api_internal.diagonal(a, offset, axis1, axis2) diff --git a/python/mxnet/numpy/multiarray.py b/python/mxnet/numpy/multiarray.py index 04476919dbd0..f2c7b5e38f01 100644 --- a/python/mxnet/numpy/multiarray.py +++ b/python/mxnet/numpy/multiarray.py @@ -69,7 +69,7 @@ 'greater', 'less', 'greater_equal', 'less_equal', 'roll', 'rot90', 'einsum', 'true_divide', 'nonzero', 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', 'ediff1d', 'resize', 'matmul', 'nan_to_num', 'isnan', 'isinf', 'isposinf', 'isneginf', 'isfinite', 'polyval', 'where', 'bincount', - 'pad', 'cumsum'] + 'pad', 'cumsum', 'diag', 'diagonal'] __all__ += fallback.__all__ @@ -9958,3 +9958,97 @@ def cumsum(a, axis=None, dtype=None, out=None): """ return _mx_nd_np.cumsum(a, axis=axis, dtype=dtype, out=out) # pylint: enable=redefined-outer-name + + +@set_module('mxnet.numpy') +def diag(v, k=0): + """ + Extracts a diagonal or constructs a diagonal array. + - 1-D arrays: constructs a 2-D array with the input as its diagonal, all other elements are zero. + - 2-D arrays: extracts the k-th Diagonal + + Parameters + ---------- + array : ndarray + The array to apply diag method. + k : offset + extracts or constructs kth diagonal given input array + + Returns + ---------- + out : ndarray + The extracted diagonal or constructed diagonal array. + + Examples + -------- + >>> x = np.arange(9).reshape((3,3)) + >>> x + array([[0, 1, 2], + [3, 4, 5], + [6, 7, 8]]) + >>> np.diag(x) + array([0, 4, 8]) + >>> np.diag(x, k=1) + array([1, 5]) + >>> np.diag(x, k=-1) + array([3, 7]) + + >>> np.diag(np.diag(x)) + array([[0, 0, 0], + [0, 4, 0], + [0, 0, 8]]) + """ + return _mx_nd_np.diag(v, k=k) + + +@set_module('mxnet.numpy') +def diagonal(a, offset=0, axis1=0, axis2=1): + """ + If a is 2-D, returns the diagonal of a with the given offset, i.e., the collection of elements of + the form a[i, i+offset]. If a has more than two dimensions, then the axes specified by axis1 and + axis2 are used to determine the 2-D sub-array whose diagonal is returned. The shape of the + resulting array can be determined by removing axis1 and axis2 and appending an index to the + right equal to the size of the resulting diagonals. + + Parameters + ---------- + a : ndarray + Input data from which diagonal are taken. + offset: int, Optional + Offset of the diagonal from the main diagonal + axis1: int, Optional + Axis to be used as the first axis of the 2-D sub-arrays + axis2: int, Optional + Axis to be used as the second axis of the 2-D sub-arrays + + Returns + ------- + out : ndarray + Output result + + Raises + ------- + ValueError: If the dimension of a is less than 2. + + Examples + -------- + >>> a = np.arange(4).reshape(2,2) + >>> a + array([[0, 1], + [2, 3]]) + >>> np.diagonal(a) + array([0, 3]) + >>> np.diagonal(a, 1) + array([1]) + + >>> a = np.arange(8).reshape(2,2,2) + >>>a + array([[[0, 1], + [2, 3]], + [[4, 5], + [6, 7]]]) + >>> np.diagonal(a, 0, 0, 1) + array([[0, 6], + [1, 7]]) + """ + return _mx_nd_np.diagonal(a, offset=offset, axis1=axis1, axis2=axis2) diff --git a/python/mxnet/symbol/numpy/_symbol.py b/python/mxnet/symbol/numpy/_symbol.py index 717049a6a8bf..612f2873560b 100644 --- a/python/mxnet/symbol/numpy/_symbol.py +++ b/python/mxnet/symbol/numpy/_symbol.py @@ -51,7 +51,7 @@ 'equal', 'not_equal', 'greater', 'less', 'greater_equal', 'less_equal', 'roll', 'rot90', 'einsum', 'true_divide', 'quantile', 'percentile', 'shares_memory', 'may_share_memory', 'diff', 'ediff1d', 'resize', 'polyval', 'nan_to_num', 'isnan', 'isinf', 'isposinf', 'isneginf', 'isfinite', - 'where', 'bincount', 'pad', 'cumsum'] + 'where', 'bincount', 'pad', 'cumsum', 'diag', 'diagonal'] @set_module('mxnet.symbol.numpy') @@ -6902,4 +6902,58 @@ def cumsum(a, axis=None, dtype=None, out=None): return _npi.cumsum(a, axis=axis, dtype=dtype, out=out) +@set_module('mxnet.symbol.numpy') +def diag(v, k=0): + """ + Extracts a diagonal or constructs a diagonal array. + - 1-D arrays: constructs a 2-D array with the input as its diagonal, all other elements are zero. + - 2-D arrays: extracts the k-th Diagonal + + Parameters + ---------- + array : _Symbol + The array to apply diag method. + k : offset + extracts or constructs kth diagonal given input array + + Returns + ---------- + out : _Symbol + The extracted diagonal or constructed diagonal array. + """ + return _npi.diag(v, k=k) + + +@set_module('mxnet.symbol.numpy') +def diagonal(a, offset=0, axis1=0, axis2=1): + """ + If a is 2-D, returns the diagonal of a with the given offset, i.e., the collection of elements of + the form a[i, i+offset]. If a has more than two dimensions, then the axes specified by axis1 and + axis2 are used to determine the 2-D sub-array whose diagonal is returned. The shape of the + resulting array can be determined by removing axis1 and axis2 and appending an index to the + right equal to the size of the resulting diagonals. + + Parameters + ---------- + a : _Symbol + Input data from which diagonal are taken. + offset: int, Optional + Offset of the diagonal from the main diagonal + axis1: int, Optional + Axis to be used as the first axis of the 2-D sub-arrays + axis2: int, Optional + Axis to be used as the second axis of the 2-D sub-arrays + + Returns + ------- + out : _Symbol + Output result + + Raises + ------- + ValueError: If the dimension of a is less than 2. + """ + return _npi.diagonal(a, offset=offset, axis1=axis1, axis2=axis2) + + _set_np_symbol_class(_Symbol) diff --git a/src/api/operator/numpy/np_matrix_op.cc b/src/api/operator/numpy/np_matrix_op.cc index 36d06c7fc4cc..ae8421ac4010 100644 --- a/src/api/operator/numpy/np_matrix_op.cc +++ b/src/api/operator/numpy/np_matrix_op.cc @@ -142,4 +142,53 @@ MXNET_REGISTER_API("_npi.rot90") *ret = ndoutputs[0]; }); +MXNET_REGISTER_API("_npi.diag") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_diag"); + nnvm::NodeAttrs attrs; + op::NumpyDiagParam param; + param.k = args[1].operator int(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + int num_inputs = 1; + int num_outputs = 0; + auto ndoutputs = Invoke(op, &attrs, num_inputs, inputs, &num_outputs, nullptr); + *ret = ndoutputs[0]; +}); + +MXNET_REGISTER_API("_npi.diagonal") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_diagonal"); + nnvm::NodeAttrs attrs; + op::NumpyDiagonalParam param; + param.offset = args[1].operator int(); + param.axis1 = args[2].operator int(); + param.axis2 = args[3].operator int(); + attrs.parsed = param; + attrs.op = op; + SetAttrDict(&attrs); + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + int num_inputs = 1; + int num_outputs = 0; + auto ndoutputs = Invoke(op, &attrs, num_inputs, inputs, &num_outputs, nullptr); + *ret = ndoutputs[0]; +}); + +MXNET_REGISTER_API("_npi.diag_indices_from") +.set_body([](runtime::MXNetArgs args, runtime::MXNetRetValue* ret) { + using namespace runtime; + const nnvm::Op* op = Op::Get("_npi_diag_indices_from"); + nnvm::NodeAttrs attrs; + attrs.op = op; + NDArray* inputs[] = {args[0].operator mxnet::NDArray*()}; + int num_inputs = 1; + int num_outputs = 0; + auto ndoutputs = Invoke(op, &attrs, num_inputs, inputs, &num_outputs, nullptr); + *ret = ndoutputs[0]; +}); + } // namespace mxnet diff --git a/src/operator/numpy/np_matrix_op-inl.h b/src/operator/numpy/np_matrix_op-inl.h index 0bbe263cfc76..2e48596cee9c 100644 --- a/src/operator/numpy/np_matrix_op-inl.h +++ b/src/operator/numpy/np_matrix_op-inl.h @@ -983,6 +983,11 @@ struct NumpyDiagParam : public dmlc::Parameter { "Use k>0 for diagonals above the main diagonal, " "and k<0 for diagonals below the main diagonal. "); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream k_s; + k_s << k; + (*dict)["k"] = k_s.str(); + } }; inline mxnet::TShape NumpyDiagShapeImpl(const mxnet::TShape &ishape, @@ -1006,7 +1011,7 @@ inline mxnet::TShape NumpyDiagShapeImpl(const mxnet::TShape &ishape, auto s = std::max(std::min(h, w), a); // s is the length of diagonal with k as the offset - int32_t n_dim = ishape.ndim() - 1; + int n_dim = ishape.ndim() - 1; mxnet::TShape oshape(n_dim, -1); oshape[n_dim - 1] = s; return oshape; @@ -1177,8 +1182,8 @@ void NumpyDiagOpBackward(const nnvm::NodeAttrs &attrs, struct NumpyDiagonalParam : public dmlc::Parameter { int offset; - int32_t axis1; - int32_t axis2; + int axis1; + int axis2; DMLC_DECLARE_PARAMETER(NumpyDiagonalParam) { DMLC_DECLARE_FIELD(offset) .set_default(0) @@ -1195,12 +1200,21 @@ struct NumpyDiagonalParam : public dmlc::Parameter { .describe("The second axis of the sub-arrays of interest. " "Ignored when the input is a 1-D array."); } + void SetAttrDict(std::unordered_map* dict) { + std::ostringstream offset_s, axis1_s, axis2_s; + offset_s << offset; + axis1_s << axis1; + axis2_s << axis2; + (*dict)["offset"] = offset_s.str(); + (*dict)["axis1"] = axis1_s.str(); + (*dict)["axis2"] = axis2_s.str(); + } }; inline mxnet::TShape NumpyDiagonalShapeImpl(const mxnet::TShape& ishape, const int k, - const int32_t axis1, const int32_t axis2) { - int32_t x1 = CheckAxis(axis1, ishape.ndim()); - int32_t x2 = CheckAxis(axis2, ishape.ndim()); + const int axis1, const int axis2) { + int x1 = CheckAxis(axis1, ishape.ndim()); + int x2 = CheckAxis(axis2, ishape.ndim()); CHECK_NE(x1, x2) << "axis1 and axis2 cannot refer to the same axis " << x1; @@ -1215,11 +1229,11 @@ inline mxnet::TShape NumpyDiagonalShapeImpl(const mxnet::TShape& ishape, const i if (s < 0) s = 0; if (x1 > x2) std::swap(x1, x2); - int32_t n_dim = ishape.ndim() - 1; + int n_dim = ishape.ndim() - 1; mxnet::TShape oshape(n_dim, -1); // remove axis1 and axis2 and append the new axis to the end - uint32_t idx = 0; + int idx = 0; for (int i = 0; i <= n_dim; ++i) { if (i != x1 && i != x2) { oshape[idx++] = ishape[i]; @@ -1292,22 +1306,22 @@ void NumpyDiagonalOpImpl(const TBlob& in_data, const std::vector& req) { using namespace mxnet_op; using namespace mshadow; - uint32_t x1 = CheckAxis(param.axis1, ishape.ndim()); - uint32_t x2 = CheckAxis(param.axis2, ishape.ndim()); - uint32_t idim = ishape.ndim(), odim = oshape.ndim(); - uint32_t minx = x1, maxx = x2; + int x1 = CheckAxis(param.axis1, ishape.ndim()); + int x2 = CheckAxis(param.axis2, ishape.ndim()); + int idim = ishape.ndim(), odim = oshape.ndim(); + int minx = x1, maxx = x2; if (minx > maxx) std::swap(minx, maxx); index_t oleading = 1, obody = 1, otrailing = 1; - for (uint32_t i = 0; i < minx; ++i) { + for (int i = 0; i < minx; ++i) { oleading *= ishape[i]; } - for (uint32_t i = minx + 1; i < maxx; ++i) { + for (int i = minx + 1; i < maxx; ++i) { obody *= ishape[i]; } - for (uint32_t i = maxx + 1; i < idim; ++i) { + for (int i = maxx + 1; i < idim; ++i) { otrailing *= ishape[i]; } diff --git a/src/operator/numpy/np_matrix_op.cc b/src/operator/numpy/np_matrix_op.cc index e9d269dd54d6..1c0a8a610a6e 100644 --- a/src/operator/numpy/np_matrix_op.cc +++ b/src/operator/numpy/np_matrix_op.cc @@ -1435,7 +1435,7 @@ NNVM_REGISTER_OP(_npi_dsplit) .add_argument("data", "NDArray-or-Symbol", "The input") .add_arguments(SplitParam::__FIELDS__()); -NNVM_REGISTER_OP(_np_diag) +NNVM_REGISTER_OP(_npi_diag) .set_attr_parser(ParamParser) .set_num_inputs(1) .set_num_outputs(1) @@ -1446,18 +1446,18 @@ NNVM_REGISTER_OP(_np_diag) .set_attr("FInferShape", NumpyDiagOpShape) .set_attr("FInferType", NumpyDiagOpType) .set_attr("FCompute", NumpyDiagOpForward) -.set_attr("FGradient", ElemwiseGradUseNone{"_backward_diag"}) +.set_attr("FGradient", ElemwiseGradUseNone{"_backward_npi_diag"}) .add_argument("data", "NDArray-or-Symbol", "Input ndarray") .add_arguments(NumpyDiagParam::__FIELDS__()); -NNVM_REGISTER_OP(_backward_np_diag) +NNVM_REGISTER_OP(_backward_npi_diag) .set_attr_parser(ParamParser) .set_num_inputs(1) .set_num_outputs(1) .set_attr("TIsBackward", true) .set_attr("FCompute", NumpyDiagOpBackward); -NNVM_REGISTER_OP(_np_diagonal) +NNVM_REGISTER_OP(_npi_diagonal) .set_attr_parser(ParamParser) .set_num_inputs(1) .set_num_outputs(1) @@ -1468,11 +1468,11 @@ NNVM_REGISTER_OP(_np_diagonal) .set_attr("FInferShape", NumpyDiagonalOpShape) .set_attr("FInferType", NumpyDiagonalOpType) .set_attr("FCompute", NumpyDiagonalOpForward) -.set_attr("FGradient", ElemwiseGradUseNone{"_backward_np_diagonal"}) +.set_attr("FGradient", ElemwiseGradUseNone{"_backward_npi_diagonal"}) .add_argument("data", "NDArray-or-Symbol", "Input ndarray") .add_arguments(NumpyDiagonalParam::__FIELDS__()); -NNVM_REGISTER_OP(_backward_np_diagonal) +NNVM_REGISTER_OP(_backward_npi_diagonal) .set_attr_parser(ParamParser) .set_num_inputs(1) .set_num_outputs(1) diff --git a/src/operator/numpy/np_matrix_op.cu b/src/operator/numpy/np_matrix_op.cu index c9e896bc5b57..c4b3290d58b7 100644 --- a/src/operator/numpy/np_matrix_op.cu +++ b/src/operator/numpy/np_matrix_op.cu @@ -127,16 +127,16 @@ NNVM_REGISTER_OP(_npi_dsplit) NNVM_REGISTER_OP(_npx_reshape) .set_attr("FCompute", UnaryOp::IdentityCompute); -NNVM_REGISTER_OP(_np_diag) +NNVM_REGISTER_OP(_npi_diag) .set_attr("FCompute", NumpyDiagOpForward); -NNVM_REGISTER_OP(_backward_np_diag) +NNVM_REGISTER_OP(_backward_npi_diag) .set_attr("FCompute", NumpyDiagOpBackward); -NNVM_REGISTER_OP(_np_diagonal) +NNVM_REGISTER_OP(_npi_diagonal) .set_attr("FCompute", NumpyDiagonalOpForward); -NNVM_REGISTER_OP(_backward_np_diagonal) +NNVM_REGISTER_OP(_backward_npi_diagonal) .set_attr("FCompute", NumpyDiagonalOpBackward); NNVM_REGISTER_OP(_np_diagflat)