Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 131 additions & 1 deletion code/numpy/compare.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

Expand All @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion code/numpy/compare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions code/numpy/numpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion code/ulab.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 5 additions & 1 deletion code/ulab.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
70 changes: 64 additions & 6 deletions docs/numpy-functions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": {},
Expand Down Expand Up @@ -2944,7 +3007,7 @@
],
"metadata": {
"kernelspec": {
"display_name": "Python 3.8.5 ('base')",
"display_name": "base",
"language": "python",
"name": "python3"
},
Expand Down Expand Up @@ -3006,11 +3069,6 @@
"_Feature"
],
"window_display": false
},
"vscode": {
"interpreter": {
"hash": "9e4ec6f642f986afcc9e252c165e44859a62defc5c697cae6f82c2943465ec10"
}
}
},
"nbformat": 4,
Expand Down
6 changes: 6 additions & 0 deletions docs/ulab-change-log.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Wed, 3 Sep 2025

version 6.10.0

add bincount

Fri, 06 Jun 2025

version 6.8.0
Expand Down
82 changes: 82 additions & 0 deletions tests/2d/numpy/bincount.py
Original file line number Diff line number Diff line change
@@ -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))
Loading
Loading