Skip to content

Commit

Permalink
implement nonzero (#540)
Browse files Browse the repository at this point in the history
* implement nonzero for Boolean arrays

* remove axtls from build script

* extend nonzero to ndarrays of arbitrary dtype, and iterable, fix float tests

* temporarily disable circuitpython tests

* add nonzero documentation

* Added test script for np.nonzero()

Co-authored-by: Tejal Ashwini Barnwal <64950661+tejalbarnwal@users.noreply.github.com>
  • Loading branch information
v923z and tejalbarnwal committed Aug 3, 2022
1 parent a2c5ece commit dfed7a8
Show file tree
Hide file tree
Showing 20 changed files with 356 additions and 61 deletions.
2 changes: 1 addition & 1 deletion build-cp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ make -C circuitpython/mpy-cross -j$NPROC
sed -e '/MICROPY_PY_UHASHLIB/s/1/0/' < circuitpython/ports/unix/mpconfigport.h > circuitpython/ports/unix/mpconfigport_ulab.h
make -k -C circuitpython/ports/unix -j$NPROC DEBUG=1 MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 MICROPY_SSL_AXTLS=0 MICROPY_PY_USSL=0 CFLAGS_EXTRA="-DMP_CONFIGFILE=\"<mpconfigport_ulab.h>\" -Wno-tautological-constant-out-of-range-compare -Wno-unknown-pragmas -DULAB_MAX_DIMS=$dims" BUILD=build-$dims PROG=micropython-$dims

bash test-common.sh "${dims}" "circuitpython/ports/unix/micropython-$dims"
# bash test-common.sh "${dims}" "circuitpython/ports/unix/micropython-$dims"

# Docs don't depend on the dimensionality, so only do it once
if [ "$dims" -eq 2 ]; then
Expand Down
10 changes: 7 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ NPROC=`python3 -c 'import multiprocessing; print(multiprocessing.cpu_count())'`
PLATFORM=`python3 -c 'import sys; print(sys.platform)'`
set -e
HERE="$(dirname -- "$(readlinkf_posix -- "${0}")" )"
[ -e micropython/py/py.mk ] || git clone --no-recurse-submodules https://github.com/micropython/micropython
[ -e micropython/lib/axtls/README ] || (cd micropython && git submodule update --init lib/axtls )
dims=${1-2}
if [ ! -d "micropython" ] ; then
git clone https://github.com/micropython/micropython
else
git -C micropython pull
fi
make -C micropython/mpy-cross -j${NPROC}
make -C micropython/ports/unix -j${NPROC} axtls
make -C micropython/ports/unix submodules
make -C micropython/ports/unix -j${NPROC} USER_C_MODULES="${HERE}" DEBUG=1 STRIP=: MICROPY_PY_FFI=0 MICROPY_PY_BTREE=0 CFLAGS_EXTRA=-DULAB_MAX_DIMS=$dims CFLAGS_EXTRA+=-DULAB_HASH=$GIT_HASH BUILD=build-$dims PROG=micropython-$dims


bash test-common.sh "${dims}" "micropython/ports/unix/micropython-$dims"

# Build with single-precision float.
Expand Down
130 changes: 130 additions & 0 deletions code/numpy/compare.c
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,136 @@ mp_obj_t compare_minimum(mp_obj_t x1, mp_obj_t x2) {
MP_DEFINE_CONST_FUN_OBJ_2(compare_minimum_obj, compare_minimum);
#endif

#if ULAB_NUMPY_HAS_NONZERO

mp_obj_t compare_nonzero(mp_obj_t x) {
ndarray_obj_t *ndarray_x = ndarray_from_mp_obj(x, 0);
// since ndarray_new_linear_array calls m_new0, the content of zero is a single zero
ndarray_obj_t *zero = ndarray_new_linear_array(1, NDARRAY_UINT8);

uint8_t ndim = 0;
size_t *shape = m_new(size_t, ULAB_MAX_DIMS);
int32_t *x_strides = m_new(int32_t, ULAB_MAX_DIMS);
int32_t *zero_strides = m_new(int32_t, ULAB_MAX_DIMS);
// we don't actually have to inspect the outcome of ndarray_can_broadcast,
// because the right hand side is a linear array with a single element
ndarray_can_broadcast(ndarray_x, zero, &ndim, shape, x_strides, zero_strides);

// equal_obj is a Boolean ndarray
mp_obj_t equal_obj = ndarray_binary_equality(ndarray_x, zero, ndim, shape, x_strides, zero_strides, MP_BINARY_OP_NOT_EQUAL);
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(equal_obj);

// these are no longer needed, get rid of them
m_del(size_t, shape, ULAB_MAX_DIMS);
m_del(int32_t, x_strides, ULAB_MAX_DIMS);
m_del(int32_t, zero_strides, ULAB_MAX_DIMS);

uint8_t *array = (uint8_t *)ndarray->array;
uint8_t *origin = (uint8_t *)ndarray->array;

// First, count the number of Trues:
uint16_t count = 0;
size_t indices[ULAB_MAX_DIMS];

#if ULAB_MAX_DIMS > 3
indices[3] = 0;
do {
#endif
#if ULAB_MAX_DIMS > 2
indices[2] = 0;
do {
#endif
#if ULAB_MAX_DIMS > 1
indices[1] = 0;
do {
#endif
indices[0] = 0;
do {
if(*array != 0) {
count++;
}
array += ndarray->strides[ULAB_MAX_DIMS - 1];
indices[0]++;
} while(indices[0] < ndarray->shape[ULAB_MAX_DIMS - 1]);
#if ULAB_MAX_DIMS > 1
array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1];
array += ndarray->strides[ULAB_MAX_DIMS - 2];
indices[1]++;
} while(indices[1] < ndarray->shape[ULAB_MAX_DIMS - 2]);
#endif
#if ULAB_MAX_DIMS > 2
array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2];
array += ndarray->strides[ULAB_MAX_DIMS - 3];
indices[2]++;
} while(indices[2] < ndarray->shape[ULAB_MAX_DIMS - 3]);
#endif
#if ULAB_MAX_DIMS > 3
array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3];
array += ndarray->strides[ULAB_MAX_DIMS - 4];
indices[3]++;
} while(indices[3] < ndarray->shape[ULAB_MAX_DIMS - 4]);
#endif

mp_obj_t *items = m_new(mp_obj_t, ndarray->ndim);
uint16_t *arrays[ULAB_MAX_DIMS];

for(uint8_t i = 0; i < ndarray->ndim; i++) {
ndarray_obj_t *item_array = ndarray_new_linear_array(count, NDARRAY_UINT16);
uint16_t *iarray = (uint16_t *)item_array->array;
arrays[ULAB_MAX_DIMS - 1 - i] = iarray;
items[ndarray->ndim - 1 - i] = MP_OBJ_FROM_PTR(item_array);
}
array = origin;
count = 0;

#if ULAB_MAX_DIMS > 3
indices[3] = 0;
do {
#endif
#if ULAB_MAX_DIMS > 2
indices[2] = 0;
do {
#endif
#if ULAB_MAX_DIMS > 1
indices[1] = 0;
do {
#endif
indices[0] = 0;
do {
if(*array != 0) {
for(uint8_t d = 0; d < ndarray->ndim; d++) {
arrays[ULAB_MAX_DIMS - 1 - d][count] = indices[d];
}
count++;
}
array += ndarray->strides[ULAB_MAX_DIMS - 1];
indices[0]++;
} while(indices[0] < ndarray->shape[ULAB_MAX_DIMS - 1]);
#if ULAB_MAX_DIMS > 1
array -= ndarray->strides[ULAB_MAX_DIMS - 1] * ndarray->shape[ULAB_MAX_DIMS-1];
array += ndarray->strides[ULAB_MAX_DIMS - 2];
indices[1]++;
} while(indices[1] < ndarray->shape[ULAB_MAX_DIMS - 2]);
#endif
#if ULAB_MAX_DIMS > 2
array -= ndarray->strides[ULAB_MAX_DIMS - 2] * ndarray->shape[ULAB_MAX_DIMS-2];
array += ndarray->strides[ULAB_MAX_DIMS - 3];
indices[2]++;
} while(indices[2] < ndarray->shape[ULAB_MAX_DIMS - 3]);
#endif
#if ULAB_MAX_DIMS > 3
array -= ndarray->strides[ULAB_MAX_DIMS - 3] * ndarray->shape[ULAB_MAX_DIMS-3];
array += ndarray->strides[ULAB_MAX_DIMS - 4];
indices[3]++;
} while(indices[3] < ndarray->shape[ULAB_MAX_DIMS - 4]);
#endif

return mp_obj_new_tuple(ndarray->ndim, items);
}

MP_DEFINE_CONST_FUN_OBJ_1(compare_nonzero_obj, compare_nonzero);
#endif /* ULAB_NUMPY_HAS_NONZERO */

#if ULAB_NUMPY_HAS_WHERE

mp_obj_t compare_where(mp_obj_t _condition, mp_obj_t _x, mp_obj_t _y) {
Expand Down
1 change: 1 addition & 0 deletions code/numpy/compare.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ MP_DECLARE_CONST_FUN_OBJ_2(compare_isfinite_obj);
MP_DECLARE_CONST_FUN_OBJ_2(compare_isinf_obj);
MP_DECLARE_CONST_FUN_OBJ_2(compare_minimum_obj);
MP_DECLARE_CONST_FUN_OBJ_2(compare_maximum_obj);
MP_DECLARE_CONST_FUN_OBJ_1(compare_nonzero_obj);
MP_DECLARE_CONST_FUN_OBJ_2(compare_not_equal_obj);
MP_DECLARE_CONST_FUN_OBJ_3(compare_where_obj);

Expand Down
4 changes: 4 additions & 0 deletions code/numpy/numpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = {
#if ULAB_NUMPY_HAS_MINIMUM
{ MP_ROM_QSTR(MP_QSTR_minimum), MP_ROM_PTR(&compare_minimum_obj) },
#endif
#if ULAB_NUMPY_HAS_NONZERO
{ MP_ROM_QSTR(MP_QSTR_nonzero), MP_ROM_PTR(&compare_nonzero_obj) },
#endif

#if ULAB_NUMPY_HAS_WHERE
{ MP_ROM_QSTR(MP_QSTR_where), MP_ROM_PTR(&compare_where_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 5.0.9
#define ULAB_VERSION 5.1.0
#define xstr(s) str(s)
#define str(s) #s

Expand Down
4 changes: 4 additions & 0 deletions code/ulab.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@
#define ULAB_NUMPY_HAS_MINIMUM (1)
#endif

#ifndef ULAB_NUMPY_HAS_NONZERO
#define ULAB_NUMPY_HAS_NONZERO (1)
#endif

#ifndef ULAB_NUMPY_HAS_NOTEQUAL
#define ULAB_NUMPY_HAS_NOTEQUAL (1)
#endif
Expand Down
2 changes: 1 addition & 1 deletion docs/manual/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
author = 'Zoltán Vörös'

# The full version, including alpha/beta/rc tags
release = '5.0.2'
release = '5.1.0'


# -- General configuration ---------------------------------------------------
Expand Down
82 changes: 62 additions & 20 deletions docs/manual/source/numpy-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,22 @@ the firmware was compiled with complex support.
25. `numpy.median <#median>`__
26. `numpy.min <#min>`__
27. `numpy.minimum <#minimum>`__
28. `numpy.not_equal <#equal>`__
29. `numpy.polyfit <#polyfit>`__
30. `numpy.polyval <#polyval>`__
31. `numpy.real\* <#real>`__
32. `numpy.roll <#roll>`__
33. `numpy.save <#save>`__
34. `numpy.savetxt <#savetxt>`__
35. `numpy.size <#size>`__
36. `numpy.sort <#sort>`__
37. `numpy.sort_complex\* <#sort_complex>`__
38. `numpy.std <#std>`__
39. `numpy.sum <#sum>`__
40. `numpy.trace <#trace>`__
41. `numpy.trapz <#trapz>`__
42. `numpy.where <#where>`__
28. `numpy.nozero <#nonzero>`__
29. `numpy.not_equal <#equal>`__
30. `numpy.polyfit <#polyfit>`__
31. `numpy.polyval <#polyval>`__
32. `numpy.real\* <#real>`__
33. `numpy.roll <#roll>`__
34. `numpy.save <#save>`__
35. `numpy.savetxt <#savetxt>`__
36. `numpy.size <#size>`__
37. `numpy.sort <#sort>`__
38. `numpy.sort_complex\* <#sort_complex>`__
39. `numpy.std <#std>`__
40. `numpy.sum <#sum>`__
41. `numpy.trace <#trace>`__
42. `numpy.trapz <#trapz>`__
43. `numpy.where <#where>`__

all
---
Expand Down Expand Up @@ -1289,6 +1290,46 @@ implemented.
nonzero
-------

``numpy``:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.nonzero.html

``nonzero`` returns the indices of the elements of an array that are not
zero. If the number of dimensions of the array is larger than one, a
tuple of arrays is returned, one for each dimension, containing the
indices of the non-zero elements in that dimension.

.. code::
# code to be run in micropython
from ulab import numpy as np
a = np.array(range(9)) - 5
print('a:\n', a)
print(np.nonzero(a))
a = a.reshape((3,3))
print('\na:\n', a)
print(np.nonzero(a))
.. parsed-literal::
a:
array([-5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0], dtype=float64)
(array([0, 1, 2, 3, 4, 6, 7, 8], dtype=uint16),)
a:
array([[-5.0, -4.0, -3.0],
[-2.0, -1.0, 0.0],
[1.0, 2.0, 3.0]], dtype=float64)
(array([0, 0, 0, 1, 1, 2, 2, 2], dtype=uint16), array([0, 1, 2, 0, 1, 0, 1, 2], dtype=uint16))
not_equal
---------

Expand All @@ -1300,11 +1341,12 @@ polyfit
``numpy``:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html

polyfit takes two, or three arguments. The last one is the degree of the
polynomial that will be fitted, the last but one is an array or iterable
with the ``y`` (dependent) values, and the first one, an array or
iterable with the ``x`` (independent) values, can be dropped. If that is
the case, ``x`` will be generated in the function as ``range(len(y))``.
``polyfit`` takes two, or three arguments. The last one is the degree of
the polynomial that will be fitted, the last but one is an array or
iterable with the ``y`` (dependent) values, and the first one, an array
or iterable with the ``x`` (independent) values, can be dropped. If that
is the case, ``x`` will be generated in the function as
``range(len(y))``.

If the lengths of ``x``, and ``y`` are not the same, the function raises
a ``ValueError``.
Expand Down

0 comments on commit dfed7a8

Please sign in to comment.