diff --git a/code/numpy/compare.c b/code/numpy/compare.c index 5845a9dd..6406cc44 100644 --- a/code/numpy/compare.c +++ b/code/numpy/compare.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020-2021 Zoltán Vörös + * Copyright (c) 2020-2025 Zoltán Vörös * 2020 Jeff Epler for Adafruit Industries */ @@ -23,6 +23,136 @@ #include "carray/carray_tools.h" #include "compare.h" +#ifdef ULAB_NUMPY_HAS_BINCOUNT +mp_obj_t compare_bincount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_rom_obj = MP_ROM_NONE} } , + { MP_QSTR_weights, MP_ARG_OBJ | MP_ARG_KW_ONLY, { .u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_minlength, MP_ARG_OBJ | MP_ARG_KW_ONLY, { .u_rom_obj = MP_ROM_NONE } }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("input must be an ndarray")); + } + ndarray_obj_t *input = MP_OBJ_TO_PTR(args[0].u_obj); + + #if ULAB_MAX_DIMS > 1 + // no need to check anything, if the maximum number of dimensions is 1 + if(input->ndim != 1) { + mp_raise_ValueError(MP_ERROR_TEXT("object too deep for desired array")); + } + #endif + if((input->dtype != NDARRAY_UINT8) && (input->dtype != NDARRAY_UINT16)) { + mp_raise_TypeError(MP_ERROR_TEXT("cannot cast array data from dtype")); + } + + // first find the maximum of the array, and figure out how long the result should be + size_t length = 0; + int32_t stride = input->strides[ULAB_MAX_DIMS - 1]; + if(input->dtype == NDARRAY_UINT8) { + uint8_t *iarray = (uint8_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + if(*iarray > length) { + length = *iarray; + } + iarray += stride; + } + } else if(input->dtype == NDARRAY_UINT16) { + stride /= 2; + uint16_t *iarray = (uint16_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + if(*iarray > length) { + length = *iarray; + } + iarray += stride; + } + } + length += 1; + + if(args[2].u_obj != mp_const_none) { + int32_t minlength = mp_obj_get_int(args[2].u_obj); + if(minlength < 0) { + mp_raise_ValueError(MP_ERROR_TEXT("minlength must not be negative")); + } + if((size_t)minlength > length) { + length = minlength; + } + } else { + if(input->len == 0) { + length = 0; + } + } + + ndarray_obj_t *result = NULL; + ndarray_obj_t *weights = NULL; + + if(args[1].u_obj == mp_const_none) { + result = ndarray_new_linear_array(length, NDARRAY_UINT16); + } else { + if(!mp_obj_is_type(args[1].u_obj, &ulab_ndarray_type)) { + mp_raise_TypeError(MP_ERROR_TEXT("input must be an ndarray")); + } + weights = MP_OBJ_TO_PTR(args[1].u_obj); + if(weights->len < input->len) { + mp_raise_ValueError(MP_ERROR_TEXT("the weights and list don't have the same length")); + } + #if ULAB_SUPPORTS_COMPLEX + if(weights->dtype == NDARRAY_COMPLEX) { + mp_raise_TypeError(MP_ERROR_TEXT("cannot cast weigths to float")); + } + #endif /* ULAB_SUPPORTS_COMPLEX */ + + result = ndarray_new_linear_array(length, NDARRAY_FLOAT); + } + + // now we can do the binning + if(result->dtype == NDARRAY_UINT16) { + uint16_t *rarray = (uint16_t *)result->array; + if(input->dtype == NDARRAY_UINT8) { + uint8_t *iarray = (uint8_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + rarray[*iarray] += 1; + iarray += stride; + } + } else if(input->dtype == NDARRAY_UINT16) { + uint16_t *iarray = (uint16_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + rarray[*iarray] += 1; + iarray += stride; + } + } + } else { + mp_float_t *rarray = (mp_float_t *)result->array; + + mp_float_t (*get_weights)(void *) = ndarray_get_float_function(weights->dtype); + uint8_t *warray = (uint8_t *)weights->array; + + if(input->dtype == NDARRAY_UINT8) { + uint8_t *iarray = (uint8_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + rarray[*iarray] += get_weights(warray); + iarray += stride; + warray += weights->strides[ULAB_MAX_DIMS - 1]; + } + } else if(input->dtype == NDARRAY_UINT16) { + uint16_t *iarray = (uint16_t *)input->array; + for(size_t i = 0; i < input->len; i++) { + rarray[*iarray] += get_weights(warray); + iarray += stride; + warray += weights->strides[ULAB_MAX_DIMS - 1]; + } + } + } + + return MP_OBJ_FROM_PTR(result); +} + +MP_DEFINE_CONST_FUN_OBJ_KW(compare_bincount_obj, 1, compare_bincount); +#endif /* ULAB_NUMPY_HAS_BINCOUNT */ + static mp_obj_t compare_function(mp_obj_t x1, mp_obj_t x2, uint8_t op) { ndarray_obj_t *lhs = ndarray_from_mp_obj(x1, 0); ndarray_obj_t *rhs = ndarray_from_mp_obj(x2, 0); diff --git a/code/numpy/compare.h b/code/numpy/compare.h index de3d7e65..4169a648 100644 --- a/code/numpy/compare.h +++ b/code/numpy/compare.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020-2021 Zoltán Vörös + * Copyright (c) 2020-2025 Zoltán Vörös */ #ifndef _COMPARE_ @@ -23,6 +23,7 @@ enum COMPARE_FUNCTION_TYPE { COMPARE_CLIP, }; +MP_DECLARE_CONST_FUN_OBJ_KW(compare_bincount_obj); MP_DECLARE_CONST_FUN_OBJ_3(compare_clip_obj); MP_DECLARE_CONST_FUN_OBJ_2(compare_equal_obj); MP_DECLARE_CONST_FUN_OBJ_2(compare_isfinite_obj); diff --git a/code/numpy/numpy.c b/code/numpy/numpy.c index eafd7728..765f5d14 100644 --- a/code/numpy/numpy.c +++ b/code/numpy/numpy.c @@ -169,6 +169,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_ZEROS { MP_ROM_QSTR(MP_QSTR_zeros), MP_ROM_PTR(&create_zeros_obj) }, #endif + #if ULAB_NUMPY_HAS_BINCOUNT + { MP_ROM_QSTR(MP_QSTR_bincount), MP_ROM_PTR(&compare_bincount_obj) }, + #endif #if ULAB_NUMPY_HAS_CLIP { MP_ROM_QSTR(MP_QSTR_clip), MP_ROM_PTR(&compare_clip_obj) }, #endif diff --git a/code/ulab.c b/code/ulab.c index c1f32c0a..8a0b50ca 100644 --- a/code/ulab.c +++ b/code/ulab.c @@ -33,7 +33,7 @@ #include "user/user.h" #include "utils/utils.h" -#define ULAB_VERSION 6.9.0 +#define ULAB_VERSION 6.10.0 #define xstr(s) str(s) #define str(s) #s diff --git a/code/ulab.h b/code/ulab.h index 597cdef5..ca234433 100644 --- a/code/ulab.h +++ b/code/ulab.h @@ -374,6 +374,10 @@ #endif // functions that compare arrays +#ifndef ULAB_NUMPY_HAS_BINCOUNT +#define ULAB_NUMPY_HAS_BINCOUNT (1) +#endif + #ifndef ULAB_NUMPY_HAS_CLIP #define ULAB_NUMPY_HAS_CLIP (1) #endif @@ -413,7 +417,7 @@ // the integrate module; functions of the integrate module still have // to be defined separately #ifndef ULAB_SCIPY_HAS_INTEGRATE_MODULE -#define ULAB_SCIPY_HAS_INTEGRATE_MODULE (1) +#define ULAB_SCIPY_HAS_INTEGRATE_MODULE (1) #endif #ifndef ULAB_INTEGRATE_HAS_TANHSINH diff --git a/docs/numpy-functions.ipynb b/docs/numpy-functions.ipynb index d13278cf..0c617bb1 100644 --- a/docs/numpy-functions.ipynb +++ b/docs/numpy-functions.ipynb @@ -238,6 +238,7 @@ "1. [numpy.argmin](#argmin)\n", "1. [numpy.argsort](#argsort)\n", "1. [numpy.asarray*](#asarray)\n", + "1. [numpy.bincount](#bincount)\n", "1. [numpy.bitwise_and](#bitwise_and)\n", "1. [numpy.bitwise_or](#bitwise_and)\n", "1. [numpy.bitwise_xor](#bitwise_and)\n", @@ -612,6 +613,68 @@ "print('a == c: {}'.format(a is c))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## bincount\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.bincount.htm\n", + "\n", + "This method counts number of occurrences of each value in array of non-negative integers. A method accepts a single positional, and two keyword arguments, `weights`, and `minlength`. The positional arguments is assumed to be a one-dimensional array of unsigned integers that are either 8 or 16 bits wide. For other types, a `TypeError` is raised.\n", + "\n", + "The `weights` keyword argument can be supplied to apply weigths to the counts. In this case, the return value is a `float` array, otherwise, it is a `uint16`. The `weights` are assumed to be a one-dimensional `ndarray`, otherwise, a `TypeError` is raised. \n", + "\n", + "`minlength` can be supplied to give a lower bound to the length of the `ndarray` returned. In this case, the unused slots are filled with zeros." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\n", + "bincount(a): array([1, 1, 1, 1, 1, 1, 1, 1], dtype=uint16)\n", + "\n", + "a: array([0, 0, 1, 1, 1, 2, 2, 2, 2], dtype=uint8)\n", + "bincount(a): array([2, 3, 4], dtype=uint16)\n", + "\n", + "a: array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\n", + "w: array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8], dtype=float64)\n", + "bincount(a, weights=w): array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8], dtype=float64)\n", + "\n", + "a: array([0, 1, 2, 3], dtype=uint8)\n", + "bincount(a, minlength=8): array([1, 1, 1, 1, 0, 0, 0, 0], dtype=uint16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(8), dtype=np.uint8)\n", + "\n", + "print(f'a: {a}\\nbincount(a): {np.bincount(a)}')\n", + "\n", + "a = np.array([0, 0, 1, 1, 1, 2, 2, 2, 2], dtype=np.uint8)\n", + "print(f'\\na: {a}\\nbincount(a): {np.bincount(a)}')\n", + "\n", + "a = np.array(range(8), dtype=np.uint8)\n", + "w = np.array([0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.8])\n", + "\n", + "print(f'\\na: {a}\\nw: {w}\\nbincount(a, weights=w): {np.bincount(a, weights=w)}')\n", + "\n", + "a = np.array(range(4), dtype=np.uint8)\n", + "print(f'\\na: {a}\\nbincount(a, minlength=8): {np.bincount(a, minlength=8)}')\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -2944,7 +3007,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.8.5 ('base')", + "display_name": "base", "language": "python", "name": "python3" }, @@ -3006,11 +3069,6 @@ "_Feature" ], "window_display": false - }, - "vscode": { - "interpreter": { - "hash": "9e4ec6f642f986afcc9e252c165e44859a62defc5c697cae6f82c2943465ec10" - } } }, "nbformat": 4, diff --git a/docs/ulab-change-log.md b/docs/ulab-change-log.md index 48f0adea..6b5995b8 100644 --- a/docs/ulab-change-log.md +++ b/docs/ulab-change-log.md @@ -1,3 +1,9 @@ +Wed, 3 Sep 2025 + +version 6.10.0 + + add bincount + Fri, 06 Jun 2025 version 6.8.0 diff --git a/tests/2d/numpy/bincount.py b/tests/2d/numpy/bincount.py new file mode 100644 index 00000000..681e3d18 --- /dev/null +++ b/tests/2d/numpy/bincount.py @@ -0,0 +1,82 @@ +try: + from ulab import numpy as np +except: + import numpy as np + +for dtype in (np.uint8, np.uint16): + a = np.array([0, 1, 1, 3, 3, 3], dtype=dtype) + print(np.bincount(a)) + +for dtype in (np.uint8, np.uint16): + a = np.array([0, 2, 2, 4], dtype=dtype) + print(np.bincount(a, minlength=3)) + +for dtype in (np.uint8, np.uint16): + a = np.array([0, 2, 2, 4], dtype=dtype) + print(np.bincount(a, minlength=8)) + +for dtype in (np.uint8, np.uint16): + a = np.array([], dtype=dtype) + print(np.bincount(a)) + print(np.bincount(a, minlength=8)) + +for dtype in (np.uint8, np.uint16): + a = np.array([0, 1, 1, 3], dtype=dtype) + w = np.array([0.5, 1.0, 2.5, 0.25]) + print(np.where(abs(np.bincount(a, weights=w) - np.array([0.5, 2.0, 0.0, 0.25])) < 0.001, 1, 0)) + + w = np.array([1, 2, 3, 4], dtype=np.uint8) + print(np.bincount(a, weights=w)) + +for dtype in (np.uint8, np.uint16): + a = np.array([1, 1], dtype=dtype) + w = np.array([0.5, 1.5]) + print(np.bincount(a, weights=w, minlength=4)) + +for dtype in (np.uint8, np.uint16): + a = np.array([2, 2, 2, 3], dtype=dtype) + for wtype in (np.uint8, np.uint16, np.int8, np.int16, np.float): + w = np.array([1, 2, 3, 4], dtype=wtype) + print(np.bincount(a, weights=w)) + +for dtype in (np.int8, np.int16, np.float): + a = np.array([2, 2, 2, 3], dtype=dtype) + try: + np.bincount(a) + except Exception as e: + print(e) + +for dtype in (np.uint8, np.int8, np.uint16, np.int16, np.float): + a = np.array(range(4), dtype=dtype).reshape((2, 2)) + try: + np.bincount(a) + except Exception as e: + print(e) + +for dtype in (np.uint8, np.uint16): + a = np.array([1, 2, 3], dtype=dtype) + w = np.array([1, 2]) + try: + np.bincount(a, weights=w) + except Exception as e: + print(e) + +for dtype in (np.uint8, np.uint16): + a = np.array([1, 2, 3], dtype=dtype) + try: + np.bincount(a, minlength=-1) + except Exception as e: + print(e) + +for dtype in (np.uint8, np.uint16): + a = np.array([1, 2, 3], dtype=dtype) + w = np.array([1j, 2j, 3j], dtype=np.complex) + try: + np.bincount(a, weights=w) + except Exception as e: + print(e) + + +a = np.array([0, 1000], dtype=np.uint16) +y = np.bincount(a) +print(y[0], y[1000], len(y)) \ No newline at end of file diff --git a/tests/2d/numpy/bincount.py.exp b/tests/2d/numpy/bincount.py.exp new file mode 100644 index 00000000..e7871c62 --- /dev/null +++ b/tests/2d/numpy/bincount.py.exp @@ -0,0 +1,41 @@ +array([1, 2, 0, 3], dtype=uint16) +array([1, 2, 0, 3], dtype=uint16) +array([1, 0, 2, 0, 1], dtype=uint16) +array([1, 0, 2, 0, 1], dtype=uint16) +array([1, 0, 2, 0, 1, 0, 0, 0], dtype=uint16) +array([1, 0, 2, 0, 1, 0, 0, 0], dtype=uint16) +array([], dtype=uint16) +array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint16) +array([], dtype=uint16) +array([0, 0, 0, 0, 0, 0, 0, 0], dtype=uint16) +array([1, 0, 1, 1], dtype=uint8) +array([1.0, 5.0, 0.0, 4.0], dtype=float64) +array([1, 0, 1, 1], dtype=uint8) +array([1.0, 5.0, 0.0, 4.0], dtype=float64) +array([0.0, 2.0, 0.0, 0.0], dtype=float64) +array([0.0, 2.0, 0.0, 0.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +array([0.0, 0.0, 6.0, 4.0], dtype=float64) +cannot cast array data from dtype +cannot cast array data from dtype +cannot cast array data from dtype +object too deep for desired array +object too deep for desired array +object too deep for desired array +object too deep for desired array +object too deep for desired array +the weights and list don't have the same length +the weights and list don't have the same length +minlength must not be negative +minlength must not be negative +cannot cast weigths to float +cannot cast weigths to float +1 1 1001