From 06bb8348a0de5f320886d87297151dcbbf8dd8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20V=C3=B6r=C3=B6s?= Date: Thu, 14 Jan 2021 19:40:18 +0100 Subject: [PATCH] updated docs, removed circuitpython stuff, and fixed diff code --- code/micropython.mk | 9 +- code/ndarray.c | 5 +- code/ndarray.h | 3 +- code/ndarray_operators.c | 11 + code/ndarray_operators.h | 10 + .../approximate.c => approx/approx.c} | 4 +- .../approximate.h => approx/approx.h} | 8 +- code/numpy/compare/compare.c | 2 +- code/numpy/compare/compare.h | 4 +- code/numpy/fft/fft.c | 17 +- code/numpy/fft/fft.h | 2 +- code/numpy/fft/fft_tools.c | 2 +- code/numpy/fft/fft_tools.h | 2 +- code/numpy/filter/filter.c | 2 +- code/numpy/filter/filter.h | 2 +- code/numpy/linalg/linalg.c | 2 +- code/numpy/linalg/linalg.h | 2 +- code/numpy/linalg/linalg_tools.c | 4 +- code/numpy/linalg/linalg_tools.h | 4 +- code/numpy/numerical/numerical.c | 2 +- code/numpy/numerical/numerical.h | 77 +- code/numpy/numpy.c | 170 +- code/numpy/numpy.h | 2 +- code/numpy/poly/poly.c | 2 +- code/numpy/poly/poly.h | 2 +- code/numpy/vector/{vectorise.c => vector.c} | 104 +- code/numpy/vector/{vectorise.h => vector.h} | 12 +- code/scipy/optimize/optimize.c | 26 +- code/scipy/optimize/optimize.h | 2 +- code/scipy/scipy.c | 6 +- code/scipy/scipy.h | 2 +- code/scipy/signal/signal.c | 2 +- code/scipy/signal/signal.h | 2 +- code/scipy/special/special.c | 4 +- code/scipy/special/special.h | 2 +- code/ulab.c | 47 +- code/ulab.h | 37 +- code/ulab_create.c | 2 +- code/ulab_create.h | 2 +- code/ulab_tools.c | 54 +- code/ulab_tools.h | 2 +- code/user/user.c | 2 +- code/user/user.h | 5 +- docs/manual/Makefile | 10 +- .../autoapi/templates/python/module.rst | 91 - docs/manual/extract_pyi.py | 223 -- docs/manual/source/conf.py | 50 +- docs/manual/source/index.rst | 21 +- .../source/{ulab-fft.rst => numpy-fft.rst} | 97 +- docs/manual/source/numpy-functions.rst | 1052 +++++ .../{ulab-linalg.rst => numpy-linalg.rst} | 309 +- ...ulab-vectorise.rst => numpy-universal.rst} | 255 +- docs/manual/source/scipy-optimize.rst | 173 + docs/manual/source/scipy-signal.rst | 135 + docs/manual/source/scipy-special.rst | 44 + docs/manual/source/ulab-approx.rst | 251 -- docs/manual/source/ulab-compare.rst | 149 - docs/manual/source/ulab-filter.rst | 99 - docs/manual/source/ulab-intro.rst | 287 +- docs/manual/source/ulab-ndarray.rst | 376 +- docs/manual/source/ulab-numerical.rst | 704 ---- docs/manual/source/ulab-poly.rst | 122 - docs/manual/source/ulab-programming.rst | 49 +- docs/manual/source/ulab/approx/index.rst | 70 - docs/manual/source/ulab/compare/index.rst | 51 - docs/manual/source/ulab/fft/index.rst | 40 - docs/manual/source/ulab/filter/index.rst | 48 - docs/manual/source/ulab/index.rst | 439 --- docs/manual/source/ulab/linalg/index.rst | 67 - docs/manual/source/ulab/numerical/index.rst | 89 - docs/manual/source/ulab/poly/index.rst | 25 - docs/manual/source/ulab/user/index.rst | 11 - docs/manual/source/ulab/vector/index.rst | 167 - docs/{ulab-fft.ipynb => numpy-fft.ipynb} | 124 +- docs/numpy-functions.ipynb | 1600 ++++++++ .../{ulab-linalg.ipynb => numpy-linalg.ipynb} | 435 +-- ...-vectorise.ipynb => numpy-universal.ipynb} | 317 +- docs/scipy-optimize.ipynb | 515 +++ .../{ulab-filter.ipynb => scipy-signal.ipynb} | 184 +- docs/scipy-special.ipynb | 344 ++ docs/ulab-approx.ipynb | 237 +- docs/ulab-change-log.md | 6 + docs/ulab-compare.ipynb | 183 +- docs/ulab-convert.ipynb | 112 +- docs/ulab-intro.ipynb | 169 +- docs/ulab-ndarray.ipynb | 3412 +++++++++++++++++ docs/ulab-numerical.ipynb | 2 +- docs/ulab-poly.ipynb | 2 +- docs/ulab-programming.ipynb | 19 +- 89 files changed, 8921 insertions(+), 4907 deletions(-) rename code/numpy/{approximate/approximate.c => approx/approx.c} (99%) rename code/numpy/{approximate/approximate.h => approx/approx.h} (86%) rename code/numpy/vector/{vectorise.c => vector.c} (84%) rename code/numpy/vector/{vectorise.h => vector.h} (96%) delete mode 100644 docs/manual/autoapi/templates/python/module.rst delete mode 100644 docs/manual/extract_pyi.py rename docs/manual/source/{ulab-fft.rst => numpy-fft.rst} (64%) create mode 100644 docs/manual/source/numpy-functions.rst rename docs/manual/source/{ulab-linalg.rst => numpy-linalg.rst} (81%) rename docs/manual/source/{ulab-vectorise.rst => numpy-universal.rst} (73%) create mode 100644 docs/manual/source/scipy-optimize.rst create mode 100644 docs/manual/source/scipy-signal.rst create mode 100644 docs/manual/source/scipy-special.rst delete mode 100644 docs/manual/source/ulab-approx.rst delete mode 100644 docs/manual/source/ulab-compare.rst delete mode 100644 docs/manual/source/ulab-filter.rst delete mode 100644 docs/manual/source/ulab-numerical.rst delete mode 100644 docs/manual/source/ulab-poly.rst delete mode 100644 docs/manual/source/ulab/approx/index.rst delete mode 100644 docs/manual/source/ulab/compare/index.rst delete mode 100644 docs/manual/source/ulab/fft/index.rst delete mode 100644 docs/manual/source/ulab/filter/index.rst delete mode 100644 docs/manual/source/ulab/index.rst delete mode 100644 docs/manual/source/ulab/linalg/index.rst delete mode 100644 docs/manual/source/ulab/numerical/index.rst delete mode 100644 docs/manual/source/ulab/poly/index.rst delete mode 100644 docs/manual/source/ulab/user/index.rst delete mode 100644 docs/manual/source/ulab/vector/index.rst rename docs/{ulab-fft.ipynb => numpy-fft.ipynb} (81%) create mode 100644 docs/numpy-functions.ipynb rename docs/{ulab-linalg.ipynb => numpy-linalg.ipynb} (88%) rename docs/{ulab-vectorise.ipynb => numpy-universal.ipynb} (82%) create mode 100644 docs/scipy-optimize.ipynb rename docs/{ulab-filter.ipynb => scipy-signal.ipynb} (74%) create mode 100644 docs/scipy-special.ipynb create mode 100644 docs/ulab-ndarray.ipynb diff --git a/code/micropython.mk b/code/micropython.mk index 91efae2..f93bcf2 100644 --- a/code/micropython.mk +++ b/code/micropython.mk @@ -2,15 +2,13 @@ USERMODULES_DIR := $(USERMOD_DIR) # Add all C files to SRC_USERMOD. -SRC_USERMOD += $(USERMODULES_DIR)/numpy/numpy.c -SRC_USERMOD += $(USERMODULES_DIR)/scipy/scipy.c SRC_USERMOD += $(USERMODULES_DIR)/scipy/optimize/optimize.c SRC_USERMOD += $(USERMODULES_DIR)/scipy/signal/signal.c SRC_USERMOD += $(USERMODULES_DIR)/scipy/special/special.c SRC_USERMOD += $(USERMODULES_DIR)/ndarray_operators.c SRC_USERMOD += $(USERMODULES_DIR)/ulab_tools.c SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c -SRC_USERMOD += $(USERMODULES_DIR)/numpy/approximate/approximate.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/approx/approx.c SRC_USERMOD += $(USERMODULES_DIR)/numpy/compare/compare.c SRC_USERMOD += $(USERMODULES_DIR)/ulab_create.c SRC_USERMOD += $(USERMODULES_DIR)/numpy/fft/fft.c @@ -20,8 +18,11 @@ SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg.c SRC_USERMOD += $(USERMODULES_DIR)/numpy/linalg/linalg_tools.c SRC_USERMOD += $(USERMODULES_DIR)/numpy/numerical/numerical.c SRC_USERMOD += $(USERMODULES_DIR)/numpy/poly/poly.c -SRC_USERMOD += $(USERMODULES_DIR)/numpy/vector/vectorise.c +SRC_USERMOD += $(USERMODULES_DIR)/numpy/vector/vector.c SRC_USERMOD += $(USERMODULES_DIR)/user/user.c + +SRC_USERMOD += $(USERMODULES_DIR)/numpy/numpy.c +SRC_USERMOD += $(USERMODULES_DIR)/scipy/scipy.c SRC_USERMOD += $(USERMODULES_DIR)/ulab.c CFLAGS_USERMOD += -I$(USERMODULES_DIR) diff --git a/code/ndarray.c b/code/ndarray.c index b77b9dc..650db54 100644 --- a/code/ndarray.c +++ b/code/ndarray.c @@ -6,7 +6,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries * 2020 Taku Fukada */ @@ -1503,7 +1504,7 @@ mp_obj_t ndarray_tobytes(mp_obj_t self_in) { if(!ndarray_is_dense(self)) { mp_raise_ValueError(translate("tobytes can be invoked for dense arrays only")); } - return mp_obj_new_bytearray_by_ref(self->len, self->array); + return mp_obj_new_bytearray_by_ref(self->itemsize * self->len, self->array); } MP_DEFINE_CONST_FUN_OBJ_1(ndarray_tobytes_obj, ndarray_tobytes); diff --git a/code/ndarray.h b/code/ndarray.h index b8d642b..8dbedbc 100644 --- a/code/ndarray.h +++ b/code/ndarray.h @@ -6,7 +6,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries */ #ifndef _NDARRAY_ diff --git a/code/ndarray_operators.c b/code/ndarray_operators.c index 7a08fde..465140b 100644 --- a/code/ndarray_operators.c +++ b/code/ndarray_operators.c @@ -1,3 +1,14 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + + #include #include "py/runtime.h" diff --git a/code/ndarray_operators.h b/code/ndarray_operators.h index a0f7315..7849e03 100644 --- a/code/ndarray_operators.h +++ b/code/ndarray_operators.h @@ -1,3 +1,13 @@ +/* + * This file is part of the micropython-ulab project, + * + * https://github.com/v923z/micropython-ulab + * + * The MIT License (MIT) + * + * Copyright (c) 2020-2021 Zoltán Vörös +*/ + #include "ndarray.h" mp_obj_t ndarray_binary_equality(ndarray_obj_t *, ndarray_obj_t *, uint8_t , size_t *, int32_t *, int32_t *, mp_binary_op_t ); diff --git a/code/numpy/approximate/approximate.c b/code/numpy/approx/approx.c similarity index 99% rename from code/numpy/approximate/approximate.c rename to code/numpy/approx/approx.c index c0a1233..171e0f4 100644 --- a/code/numpy/approximate/approximate.c +++ b/code/numpy/approx/approx.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * 2020 Diego Elio Pettenò * 2020 Taku Fukada */ @@ -19,7 +19,7 @@ #include "../../ulab.h" #include "../../ulab_tools.h" -#include "approximate.h" +#include "approx.h" //| """Numerical approximation methods""" //| diff --git a/code/numpy/approximate/approximate.h b/code/numpy/approx/approx.h similarity index 86% rename from code/numpy/approximate/approximate.h rename to code/numpy/approx/approx.h index ed10e6a..7708bb7 100644 --- a/code/numpy/approximate/approximate.h +++ b/code/numpy/approx/approx.h @@ -6,11 +6,11 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ -#ifndef _APPROXIMATE_ -#define _APPROXIMATE_ +#ifndef _APPROX_ +#define _APPROX_ #include "../../ulab.h" #include "../../ndarray.h" @@ -26,4 +26,4 @@ MP_DECLARE_CONST_FUN_OBJ_KW(approx_interp_obj); MP_DECLARE_CONST_FUN_OBJ_KW(approx_trapz_obj); -#endif /* _APPROXIMATE_ */ +#endif /* _APPROX_ */ diff --git a/code/numpy/compare/compare.c b/code/numpy/compare/compare.c index 9bf2485..ebb88cc 100644 --- a/code/numpy/compare/compare.c +++ b/code/numpy/compare/compare.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * 2020 Jeff Epler for Adafruit Industries */ diff --git a/code/numpy/compare/compare.h b/code/numpy/compare/compare.h index ecd22bf..ad89e3f 100644 --- a/code/numpy/compare/compare.h +++ b/code/numpy/compare/compare.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ #ifndef _COMPARE_ @@ -29,8 +29,6 @@ MP_DECLARE_CONST_FUN_OBJ_2(compare_minimum_obj); MP_DECLARE_CONST_FUN_OBJ_2(compare_maximum_obj); MP_DECLARE_CONST_FUN_OBJ_3(compare_clip_obj); -extern mp_obj_module_t ulab_compare_module; - #if ULAB_MAX_DIMS == 1 #define COMPARE_LOOP(results, array, type_out, type_left, type_right, larray, lstrides, rarray, rstrides, OPERATOR)\ size_t l = 0;\ diff --git a/code/numpy/fft/fft.c b/code/numpy/fft/fft.c index 4c33900..7dfe651 100644 --- a/code/numpy/fft/fft.c +++ b/code/numpy/fft/fft.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös * 2020 Scott Shawcroft for Adafruit Industries * 2020 Taku Fukada */ @@ -68,26 +68,11 @@ static mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) { MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj, 1, 2, fft_ifft); -#if ULAB_NUMPY_COMPATIBILITY STATIC const mp_rom_map_elem_t ulab_fft_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_fft) }, { MP_OBJ_NEW_QSTR(MP_QSTR_fft), (mp_obj_t)&fft_fft_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_ifft), (mp_obj_t)&fft_ifft_obj }, }; -#else -static mp_obj_t fft_spectrogram(size_t n_args, const mp_obj_t *args) { - return fft_fft_ifft_spectrogram(n_args, args[0], mp_const_none, FFT_SPECTROGRAM); -} - -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_spectrogram_obj, 1, 2, fft_spectrogram); - -STATIC const mp_rom_map_elem_t ulab_fft_globals_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_fft) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_fft), (mp_obj_t)&fft_fft_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_ifft), (mp_obj_t)&fft_ifft_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_spectrogram), (mp_obj_t)&fft_spectrogram_obj }, -}; -#endif STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_fft_globals, ulab_fft_globals_table); diff --git a/code/numpy/fft/fft.h b/code/numpy/fft/fft.h index a9735f3..66acafe 100644 --- a/code/numpy/fft/fft.h +++ b/code/numpy/fft/fft.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _FFT_ diff --git a/code/numpy/fft/fft_tools.c b/code/numpy/fft/fft_tools.c index 6a97918..e527f22 100644 --- a/code/numpy/fft/fft_tools.c +++ b/code/numpy/fft/fft_tools.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #include diff --git a/code/numpy/fft/fft_tools.h b/code/numpy/fft/fft_tools.h index c05af0b..d3b856d 100644 --- a/code/numpy/fft/fft_tools.h +++ b/code/numpy/fft/fft_tools.h @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _FFT_TOOLS_ diff --git a/code/numpy/filter/filter.c b/code/numpy/filter/filter.c index 2ef22c7..280efd0 100644 --- a/code/numpy/filter/filter.c +++ b/code/numpy/filter/filter.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ diff --git a/code/numpy/filter/filter.h b/code/numpy/filter/filter.h index 6a473c8..9b1ad18 100644 --- a/code/numpy/filter/filter.h +++ b/code/numpy/filter/filter.h @@ -7,7 +7,7 @@ * The MIT License (MIT) * * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös */ #ifndef _FILTER_ diff --git a/code/numpy/linalg/linalg.c b/code/numpy/linalg/linalg.c index 8d7d5a6..51b3996 100644 --- a/code/numpy/linalg/linalg.c +++ b/code/numpy/linalg/linalg.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös * 2020 Scott Shawcroft for Adafruit Industries * 2020 Roberto Colistete Jr. * 2020 Taku Fukada diff --git a/code/numpy/linalg/linalg.h b/code/numpy/linalg/linalg.h index 4d0877e..6c9e0f6 100644 --- a/code/numpy/linalg/linalg.h +++ b/code/numpy/linalg/linalg.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _LINALG_ diff --git a/code/numpy/linalg/linalg_tools.c b/code/numpy/linalg/linalg_tools.c index 255a004..5e03a50 100644 --- a/code/numpy/linalg/linalg_tools.c +++ b/code/numpy/linalg/linalg_tools.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2010 Zoltán Vörös */ #include @@ -86,7 +86,7 @@ bool linalg_invert_matrix(mp_float_t *data, size_t N) { */ size_t linalg_jacobi_rotations(mp_float_t *array, mp_float_t *eigvectors, size_t S) { - // eigvectors should be a 0-array; start out with the unit matrix + // eigvectors should be a 0-array; start out with the unit matrix for(size_t m=0; m < S; m++) { eigvectors[m * (S+1)] = 1.0; } diff --git a/code/numpy/linalg/linalg_tools.h b/code/numpy/linalg/linalg_tools.h index ebf8845..942da00 100644 --- a/code/numpy/linalg/linalg_tools.h +++ b/code/numpy/linalg/linalg_tools.h @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _TOOLS_TOOLS_ @@ -13,7 +13,7 @@ #ifndef LINALG_EPSILON #if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT -#define LINALG_EPSILON MICROPY_FLOAT_CONST(1.2e-7) +#define LINALG_EPSILON MICROPY_FLOAT_CONST(1.2e-7) #elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE #define LINALG_EPSILON MICROPY_FLOAT_CONST(2.3e-16) #endif diff --git a/code/numpy/numerical/numerical.c b/code/numpy/numerical/numerical.c index 54e999a..dcafde8 100644 --- a/code/numpy/numerical/numerical.c +++ b/code/numpy/numerical/numerical.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös * 2020 Scott Shawcroft for Adafruit Industries * 2020 Taku Fukada */ diff --git a/code/numpy/numerical/numerical.h b/code/numpy/numerical/numerical.h index c5a2ae2..53e2bd1 100644 --- a/code/numpy/numerical/numerical.h +++ b/code/numpy/numerical/numerical.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _NUMERICAL_ @@ -15,14 +15,9 @@ #include "../../ulab.h" #include "../../ndarray.h" -#if !ULAB_NUMPY_COMPATIBILITY -extern mp_obj_module_t ulab_numerical_module; -#endif - // TODO: implement cumsum //mp_obj_t numerical_cumsum(size_t , const mp_obj_t *, mp_map_t *); - #define RUN_ARGMIN1(ndarray, type, array, results, rarray, index, op)\ ({\ uint16_t best_index = 0;\ @@ -98,16 +93,16 @@ extern mp_obj_module_t ulab_numerical_module; #define RUN_DIFF1(ndarray, type, array, results, rarray, index, stencil, N)\ ({\ - for(size_t i=0; i < (ndarray)->shape[(index)] - (N); i++) {\ + for(size_t i=0; i < (results)->shape[ULAB_MAX_DIMS - 1]; i++) {\ type sum = 0;\ uint8_t *source = (array);\ for(uint8_t d=0; d < (N)+1; d++) {\ sum -= (stencil)[d] * *((type *)source);\ source += (ndarray)->strides[(index)];\ }\ - (array) += (strides)[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 1];\ *(type *)(rarray) = sum;\ - (rarray) += (ndarray)->itemsize;\ + (rarray) += (results)->itemsize;\ }\ }) @@ -258,10 +253,12 @@ extern mp_obj_module_t ulab_numerical_module; size_t l = 0;\ do {\ RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ - (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS - 1];\ - (array) += (strides)[ULAB_MAX_DIMS - 2];\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ l++;\ - } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ + } while(l < (results)->shape[ULAB_MAX_DIMS - 2]);\ } while(0) #define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ @@ -296,8 +293,8 @@ extern mp_obj_module_t ulab_numerical_module; (array) += (strides)[ULAB_MAX_DIMS - 1];\ l++;\ } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ - (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ k++;\ } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ } while(0) @@ -312,8 +309,8 @@ extern mp_obj_module_t ulab_numerical_module; (array) += (strides)[ULAB_MAX_DIMS - 1];\ l++;\ } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ - (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ k++;\ } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ } while(0) @@ -328,8 +325,8 @@ extern mp_obj_module_t ulab_numerical_module; (array) += (strides)[ULAB_MAX_DIMS - 1];\ l++;\ } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ - (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ k++;\ } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ } while(0) @@ -344,8 +341,8 @@ extern mp_obj_module_t ulab_numerical_module; (array) += (strides)[ULAB_MAX_DIMS - 1];\ l++;\ } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ - (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ + (array) += (strides)[ULAB_MAX_DIMS - 2];\ k++;\ } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ } while(0) @@ -356,14 +353,18 @@ extern mp_obj_module_t ulab_numerical_module; size_t l = 0;\ do {\ RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ - (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS - 1];\ - (array) += (strides)[ULAB_MAX_DIMS - 2];\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ l++;\ - } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ - (array) += (strides)[ULAB_MAX_DIMS - 2];\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (ndarray)->[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS-2];\ + (array) += (ndarray)->[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ k++;\ - } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ } while(0) #define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ @@ -498,18 +499,24 @@ extern mp_obj_module_t ulab_numerical_module; size_t l = 0;\ do {\ RUN_DIFF1((ndarray), type, (array), (results), (rarray), (index), (stencil), (N));\ - (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS - 1];\ - (array) += (strides)[ULAB_MAX_DIMS - 2];\ + (array) -= (ndarray)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (array) += (ndarray)->strides[ULAB_MAX_DIMS - 2];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 1] * (results)->shape[ULAB_MAX_DIMS - 1];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 2];\ l++;\ - } while(l < (shape)[ULAB_MAX_DIMS - 1]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 1] * (shape)[ULAB_MAX_DIMS-1];\ - (array) += (strides)[ULAB_MAX_DIMS - 2];\ + } while(l < (shape)[ULAB_MAX_DIMS - 2]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ + (array) += (strides)[ULAB_MAX_DIMS - 3];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 2] * (results)->shape[ULAB_MAX_DIMS - 2];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 3];\ k++;\ - } while(k < (shape)[ULAB_MAX_DIMS - 2]);\ - (array) -= (strides)[ULAB_MAX_DIMS - 2] * (shape)[ULAB_MAX_DIMS-2];\ - (array) += (strides)[ULAB_MAX_DIMS - 3];\ + } while(k < (shape)[ULAB_MAX_DIMS - 3]);\ + (array) -= (strides)[ULAB_MAX_DIMS - 3] * (shape)[ULAB_MAX_DIMS-3];\ + (array) += (strides)[ULAB_MAX_DIMS - 4];\ + (rarray) -= (results)->strides[ULAB_MAX_DIMS - 3] * (results)->shape[ULAB_MAX_DIMS - 3];\ + (rarray) += (results)->strides[ULAB_MAX_DIMS - 4];\ j++;\ - } while(j < (shape)[ULAB_MAX_DIMS - 3]);\ + } while(j < (shape)[ULAB_MAX_DIMS - 4]);\ } while(0) #define HEAPSORT(ndarray, type, array, shape, strides, index, increment, N) do {\ diff --git a/code/numpy/numpy.c b/code/numpy/numpy.c index f1b23ab..521b228 100644 --- a/code/numpy/numpy.c +++ b/code/numpy/numpy.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ @@ -17,15 +17,15 @@ #include "py/runtime.h" #include "numpy.h" -#include "ulab_create.h" -#include "approximate/approximate.h" +#include "../ulab_create.h" +#include "approx/approx.h" #include "compare/compare.h" #include "fft/fft.h" #include "filter/filter.h" #include "linalg/linalg.h" #include "numerical/numerical.h" #include "poly/poly.h" -#include "vector/vectorise.h" +#include "vector/vector.h" // math constants #if ULAB_NUMPY_HAS_E @@ -64,7 +64,7 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_PI { MP_ROM_QSTR(MP_QSTR_pi), MP_ROM_PTR(&ulab_const_float_pi_obj) }, #endif - // class constants, always included + // class constants, always included { MP_ROM_QSTR(MP_QSTR_bool), MP_ROM_INT(NDARRAY_BOOL) }, { MP_ROM_QSTR(MP_QSTR_uint8), MP_ROM_INT(NDARRAY_UINT8) }, { MP_ROM_QSTR(MP_QSTR_int8), MP_ROM_INT(NDARRAY_INT8) }, @@ -72,10 +72,10 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_int16), MP_ROM_INT(NDARRAY_INT16) }, { MP_ROM_QSTR(MP_QSTR_float), MP_ROM_INT(NDARRAY_FLOAT) }, // modules of numpy - #if ULAB_NUMPY_HAS_FFT_MODULE + #if ULAB_NUMPY_HAS_FFT_MODULE { MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) }, #endif - #if ULAB_NUMPY_HAS_LINALG_MODULE + #if ULAB_NUMPY_HAS_LINALG_MODULE { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_linalg_module) }, #endif #if ULAB_HAS_PRINTOPTIONS @@ -100,12 +100,12 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #endif #endif /* ULAB_MAX_DIMS */ // functions of the approx sub-module - #if ULAB_NUMPY_HAS_INTERP - { MP_OBJ_NEW_QSTR(MP_QSTR_interp), (mp_obj_t)&approx_interp_obj }, - #endif - #if ULAB_NUMPY_HAS_TRAPZ - { MP_OBJ_NEW_QSTR(MP_QSTR_trapz), (mp_obj_t)&approx_trapz_obj }, - #endif + #if ULAB_NUMPY_HAS_INTERP + { MP_OBJ_NEW_QSTR(MP_QSTR_interp), (mp_obj_t)&approx_interp_obj }, + #endif + #if ULAB_NUMPY_HAS_TRAPZ + { MP_OBJ_NEW_QSTR(MP_QSTR_trapz), (mp_obj_t)&approx_trapz_obj }, + #endif // functions of the create sub-module #if ULAB_NUMPY_HAS_FULL { MP_ROM_QSTR(MP_QSTR_full), (mp_obj_t)&create_full_obj }, @@ -123,75 +123,75 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_zeros), (mp_obj_t)&create_zeros_obj }, #endif // functions of the compare sub-module - #if ULAB_NUMPY_HAS_CLIP - { MP_OBJ_NEW_QSTR(MP_QSTR_clip), (mp_obj_t)&compare_clip_obj }, - #endif - #if ULAB_NUMPY_HAS_EQUAL - { MP_OBJ_NEW_QSTR(MP_QSTR_equal), (mp_obj_t)&compare_equal_obj }, - #endif - #if ULAB_NUMPY_HAS_NOTEQUAL - { MP_OBJ_NEW_QSTR(MP_QSTR_not_equal), (mp_obj_t)&compare_not_equal_obj }, - #endif - #if ULAB_NUMPY_HAS_MAXIMUM - { MP_OBJ_NEW_QSTR(MP_QSTR_maximum), (mp_obj_t)&compare_maximum_obj }, - #endif - #if ULAB_NUMPY_HAS_MINIMUM - { MP_OBJ_NEW_QSTR(MP_QSTR_minimum), (mp_obj_t)&compare_minimum_obj }, - #endif - // functions of the filter sub-module - #if ULAB_NUMPY_HAS_CONVOLVE - { MP_OBJ_NEW_QSTR(MP_QSTR_convolve), (mp_obj_t)&filter_convolve_obj }, - #endif - // functions of the numerical sub-module - #if ULAB_NUMPY_HAS_ARGMINMAX - { MP_OBJ_NEW_QSTR(MP_QSTR_argmax), (mp_obj_t)&numerical_argmax_obj }, - { MP_OBJ_NEW_QSTR(MP_QSTR_argmin), (mp_obj_t)&numerical_argmin_obj }, - #endif - #if ULAB_NUMPY_HAS_ARGSORT - { MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj }, - #endif - #if ULAB_NUMPY_HAS_CROSS - { MP_OBJ_NEW_QSTR(MP_QSTR_cross), (mp_obj_t)&numerical_cross_obj }, - #endif - #if ULAB_NUMPY_HAS_DIFF - { MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj }, - #endif - #if ULAB_NUMPY_HAS_FLIP - { MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj }, - #endif - #if ULAB_NUMPY_HAS_MINMAX - { MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj }, - #endif - #if ULAB_NUMPY_HAS_MEAN - { MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&numerical_mean_obj }, - #endif - #if ULAB_NUMPY_HAS_MEDIAN - { MP_OBJ_NEW_QSTR(MP_QSTR_median), (mp_obj_t)&numerical_median_obj }, - #endif - #if ULAB_NUMPY_HAS_MINMAX - { MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&numerical_min_obj }, - #endif - #if ULAB_NUMPY_HAS_ROLL - { MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj }, - #endif - #if ULAB_NUMPY_HAS_SORT - { MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj }, - #endif - #if ULAB_NUMPY_HAS_STD - { MP_OBJ_NEW_QSTR(MP_QSTR_std), (mp_obj_t)&numerical_std_obj }, - #endif - #if ULAB_NUMPY_HAS_SUM - { MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&numerical_sum_obj }, - #endif - // functions of the poly sub-module - #if ULAB_NUMPY_HAS_POLYFIT - { MP_OBJ_NEW_QSTR(MP_QSTR_polyfit), (mp_obj_t)&poly_polyfit_obj }, - #endif - #if ULAB_NUMPY_HAS_POLYVAL - { MP_OBJ_NEW_QSTR(MP_QSTR_polyval), (mp_obj_t)&poly_polyval_obj }, - #endif - // functions of the vector sub-module - #if ULAB_NUMPY_HAS_ACOS + #if ULAB_NUMPY_HAS_CLIP + { MP_OBJ_NEW_QSTR(MP_QSTR_clip), (mp_obj_t)&compare_clip_obj }, + #endif + #if ULAB_NUMPY_HAS_EQUAL + { MP_OBJ_NEW_QSTR(MP_QSTR_equal), (mp_obj_t)&compare_equal_obj }, + #endif + #if ULAB_NUMPY_HAS_NOTEQUAL + { MP_OBJ_NEW_QSTR(MP_QSTR_not_equal), (mp_obj_t)&compare_not_equal_obj }, + #endif + #if ULAB_NUMPY_HAS_MAXIMUM + { MP_OBJ_NEW_QSTR(MP_QSTR_maximum), (mp_obj_t)&compare_maximum_obj }, + #endif + #if ULAB_NUMPY_HAS_MINIMUM + { MP_OBJ_NEW_QSTR(MP_QSTR_minimum), (mp_obj_t)&compare_minimum_obj }, + #endif + // functions of the filter sub-module + #if ULAB_NUMPY_HAS_CONVOLVE + { MP_OBJ_NEW_QSTR(MP_QSTR_convolve), (mp_obj_t)&filter_convolve_obj }, + #endif + // functions of the numerical sub-module + #if ULAB_NUMPY_HAS_ARGMINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_argmax), (mp_obj_t)&numerical_argmax_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_argmin), (mp_obj_t)&numerical_argmin_obj }, + #endif + #if ULAB_NUMPY_HAS_ARGSORT + { MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj }, + #endif + #if ULAB_NUMPY_HAS_CROSS + { MP_OBJ_NEW_QSTR(MP_QSTR_cross), (mp_obj_t)&numerical_cross_obj }, + #endif + #if ULAB_NUMPY_HAS_DIFF + { MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj }, + #endif + #if ULAB_NUMPY_HAS_FLIP + { MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj }, + #endif + #if ULAB_NUMPY_HAS_MEAN + { MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&numerical_mean_obj }, + #endif + #if ULAB_NUMPY_HAS_MEDIAN + { MP_OBJ_NEW_QSTR(MP_QSTR_median), (mp_obj_t)&numerical_median_obj }, + #endif + #if ULAB_NUMPY_HAS_MINMAX + { MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&numerical_min_obj }, + #endif + #if ULAB_NUMPY_HAS_ROLL + { MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj }, + #endif + #if ULAB_NUMPY_HAS_SORT + { MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj }, + #endif + #if ULAB_NUMPY_HAS_STD + { MP_OBJ_NEW_QSTR(MP_QSTR_std), (mp_obj_t)&numerical_std_obj }, + #endif + #if ULAB_NUMPY_HAS_SUM + { MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&numerical_sum_obj }, + #endif + // functions of the poly sub-module + #if ULAB_NUMPY_HAS_POLYFIT + { MP_OBJ_NEW_QSTR(MP_QSTR_polyfit), (mp_obj_t)&poly_polyfit_obj }, + #endif + #if ULAB_NUMPY_HAS_POLYVAL + { MP_OBJ_NEW_QSTR(MP_QSTR_polyval), (mp_obj_t)&poly_polyval_obj }, + #endif + // functions of the vector sub-module + #if ULAB_NUMPY_HAS_ACOS { MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj }, #endif #if ULAB_NUMPY_HAS_ACOSH @@ -263,9 +263,9 @@ static const mp_rom_map_elem_t ulab_numpy_globals_table[] = { #if ULAB_NUMPY_HAS_TANH { MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj }, #endif - #if ULAB_NUMPY_HAS_VECTORIZE - { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj }, - #endif + #if ULAB_NUMPY_HAS_VECTORIZE + { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj }, + #endif }; diff --git a/code/numpy/numpy.h b/code/numpy/numpy.h index aa447bd..3e98fb1 100644 --- a/code/numpy/numpy.h +++ b/code/numpy/numpy.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * */ diff --git a/code/numpy/poly/poly.c b/code/numpy/poly/poly.c index 63abafa..6c1ed81 100644 --- a/code/numpy/poly/poly.c +++ b/code/numpy/poly/poly.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös * 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries * 2020 Taku Fukada diff --git a/code/numpy/poly/poly.h b/code/numpy/poly/poly.h index 3d82254..cf66ab1 100644 --- a/code/numpy/poly/poly.h +++ b/code/numpy/poly/poly.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef _POLY_ diff --git a/code/numpy/vector/vectorise.c b/code/numpy/vector/vector.c similarity index 84% rename from code/numpy/vector/vectorise.c rename to code/numpy/vector/vector.c index cfa6904..a92edcc 100644 --- a/code/numpy/vector/vectorise.c +++ b/code/numpy/vector/vector.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös * 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries * 2020 Taku Fukada @@ -22,7 +22,7 @@ #include "../../ulab.h" #include "../../ulab_tools.h" -#include "vectorise.h" +#include "vector.h" //| """Element-by-element functions //| @@ -641,103 +641,3 @@ static mp_obj_t vectorise_vectorize(size_t n_args, const mp_obj_t *pos_args, mp_ MP_DEFINE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj, 1, vectorise_vectorize); #endif - -#if !ULAB_NUMPY_COMPATIBILITY -STATIC const mp_rom_map_elem_t ulab_vectorise_globals_table[] = { - { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_vector) }, - #if ULAB_VECTORISE_HAS_ACOS - { MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj }, - #endif - #if ULAB_VECTORISE_HAS_ACOSH - { MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vectorise_acosh_obj }, - #endif - #if ULAB_VECTORISE_HAS_ARCTAN2 - { MP_OBJ_NEW_QSTR(MP_QSTR_arctan2), (mp_obj_t)&vectorise_arctan2_obj }, - #endif - #if ULAB_VECTORISE_HAS_AROUND - { MP_OBJ_NEW_QSTR(MP_QSTR_around), (mp_obj_t)&vectorise_around_obj }, - #endif - #if ULAB_VECTORISE_HAS_ASIN - { MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vectorise_asin_obj }, - #endif - #if ULAB_VECTORISE_HAS_ASINH - { MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vectorise_asinh_obj }, - #endif - #if ULAB_VECTORISE_HAS_ATAN - { MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vectorise_atan_obj }, - #endif - #if ULAB_VECTORISE_HAS_ATANH - { MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vectorise_atanh_obj }, - #endif - #if ULAB_VECTORISE_HAS_CEIL - { MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vectorise_ceil_obj }, - #endif - #if ULAB_VECTORISE_HAS_COS - { MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vectorise_cos_obj }, - #endif - #if ULAB_VECTORISE_HAS_COSH - { MP_OBJ_NEW_QSTR(MP_QSTR_cosh), (mp_obj_t)&vectorise_cosh_obj }, - #endif - #if ULAB_VECTORISE_HAS_DEGREES - { MP_OBJ_NEW_QSTR(MP_QSTR_degrees), (mp_obj_t)&vectorise_degrees_obj }, - #endif - #if ULAB_VECTORISE_HAS_ERF - { MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vectorise_erf_obj }, - #endif - #if ULAB_VECTORISE_HAS_ERFC - { MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vectorise_erfc_obj }, - #endif - #if ULAB_VECTORISE_HAS_EXP - { MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vectorise_exp_obj }, - #endif - #if ULAB_VECTORISE_HAS_EXPM1 - { MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vectorise_expm1_obj }, - #endif - #if ULAB_VECTORISE_HAS_FLOOR - { MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vectorise_floor_obj }, - #endif - #if ULAB_VECTORISE_HAS_GAMMA - { MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vectorise_gamma_obj }, - #endif - #if ULAB_VECTORISE_HAS_LGAMMA - { MP_OBJ_NEW_QSTR(MP_QSTR_lgamma), (mp_obj_t)&vectorise_lgamma_obj }, - #endif - #if ULAB_VECTORISE_HAS_LOG - { MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vectorise_log_obj }, - #endif - #if ULAB_VECTORISE_HAS_LOG10 - { MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vectorise_log10_obj }, - #endif - #if ULAB_VECTORISE_HAS_LOG2 - { MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vectorise_log2_obj }, - #endif - #if ULAB_VECTORISE_HAS_RADIANS - { MP_OBJ_NEW_QSTR(MP_QSTR_radians), (mp_obj_t)&vectorise_radians_obj }, - #endif - #if ULAB_VECTORISE_HAS_SIN - { MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vectorise_sin_obj }, - #endif - #if ULAB_VECTORISE_HAS_SINH - { MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vectorise_sinh_obj }, - #endif - #if ULAB_VECTORISE_HAS_SQRT - { MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vectorise_sqrt_obj }, - #endif - #if ULAB_VECTORISE_HAS_TAN - { MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vectorise_tan_obj }, - #endif - #if ULAB_VECTORISE_HAS_TANH - { MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj }, - #endif - #if ULAB_VECTORISE_HAS_VECTORIZE - { MP_OBJ_NEW_QSTR(MP_QSTR_vectorize), (mp_obj_t)&vectorise_vectorize_obj }, - #endif -}; - -STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_vectorise_globals, ulab_vectorise_globals_table); - -mp_obj_module_t ulab_vectorise_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t*)&mp_module_ulab_vectorise_globals, -}; -#endif /* ULAB_NUMPY_COMPATIBILITY */ diff --git a/code/numpy/vector/vectorise.h b/code/numpy/vector/vector.h similarity index 96% rename from code/numpy/vector/vectorise.h rename to code/numpy/vector/vector.h index fd7b59d..6b00a22 100644 --- a/code/numpy/vector/vectorise.h +++ b/code/numpy/vector/vector.h @@ -6,11 +6,11 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ -#ifndef _VECTORISE_ -#define _VECTORISE_ +#ifndef _VECTOR_ +#define _VECTOR_ #include "../../ulab.h" #include "../../ndarray.h" @@ -45,10 +45,6 @@ MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tan_obj); MP_DECLARE_CONST_FUN_OBJ_1(vectorise_tanh_obj); MP_DECLARE_CONST_FUN_OBJ_KW(vectorise_vectorize_obj); -#if !ULAB_NUMPY_COMPATIBILITY -extern mp_obj_module_t ulab_vectorise_module; -#endif - typedef struct _vectorized_function_obj_t { mp_obj_base_t base; uint8_t otypes; @@ -157,4 +153,4 @@ typedef struct _vectorized_function_obj_t { return vectorise_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \ } -#endif +#endif /* _VECTOR_ */ diff --git a/code/scipy/optimize/optimize.c b/code/scipy/optimize/optimize.c index 9680eb1..3a8ed6c 100644 --- a/code/scipy/optimize/optimize.c +++ b/code/scipy/optimize/optimize.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ @@ -392,18 +392,18 @@ MP_DEFINE_CONST_FUN_OBJ_KW(optimize_newton_obj, 2, optimize_newton); static const mp_rom_map_elem_t ulab_scipy_optimize_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_optimize) }, - #if ULAB_SCIPY_OPTIMIZE_HAS_BISECT - { MP_OBJ_NEW_QSTR(MP_QSTR_bisect), (mp_obj_t)&optimize_bisect_obj }, - #endif - #if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT - { MP_OBJ_NEW_QSTR(MP_QSTR_curve_fit), (mp_obj_t)&optimize_curve_fit_obj }, - #endif - #if ULAB_SCIPY_OPTIMIZE_HAS_FMIN - { MP_OBJ_NEW_QSTR(MP_QSTR_fmin), (mp_obj_t)&optimize_fmin_obj }, - #endif - #if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON - { MP_OBJ_NEW_QSTR(MP_QSTR_newton), (mp_obj_t)&optimize_newton_obj }, - #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_BISECT + { MP_OBJ_NEW_QSTR(MP_QSTR_bisect), (mp_obj_t)&optimize_bisect_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_CURVE_FIT + { MP_OBJ_NEW_QSTR(MP_QSTR_curve_fit), (mp_obj_t)&optimize_curve_fit_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_FMIN + { MP_OBJ_NEW_QSTR(MP_QSTR_fmin), (mp_obj_t)&optimize_fmin_obj }, + #endif + #if ULAB_SCIPY_OPTIMIZE_HAS_NEWTON + { MP_OBJ_NEW_QSTR(MP_QSTR_newton), (mp_obj_t)&optimize_newton_obj }, + #endif }; static MP_DEFINE_CONST_DICT(mp_module_ulab_scipy_optimize_globals, ulab_scipy_optimize_globals_table); diff --git a/code/scipy/optimize/optimize.h b/code/scipy/optimize/optimize.h index f570391..261e544 100644 --- a/code/scipy/optimize/optimize.h +++ b/code/scipy/optimize/optimize.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * */ diff --git a/code/scipy/scipy.c b/code/scipy/scipy.c index 5664e81..d0e32b5 100644 --- a/code/scipy/scipy.c +++ b/code/scipy/scipy.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ @@ -27,10 +27,10 @@ static const mp_rom_map_elem_t ulab_scipy_globals_table[] = { #if ULAB_SCIPY_HAS_OPTIMIZE_MODULE { MP_ROM_QSTR(MP_QSTR_optimize), MP_ROM_PTR(&ulab_scipy_optimize_module) }, #endif - #if ULAB_SCIPY_HAS_SIGNAL_MODULE + #if ULAB_SCIPY_HAS_SIGNAL_MODULE { MP_ROM_QSTR(MP_QSTR_signal), MP_ROM_PTR(&ulab_scipy_signal_module) }, #endif - #if ULAB_SCIPY_HAS_SPECIAL_MODULE + #if ULAB_SCIPY_HAS_SPECIAL_MODULE { MP_ROM_QSTR(MP_QSTR_special), MP_ROM_PTR(&ulab_scipy_special_module) }, #endif }; diff --git a/code/scipy/scipy.h b/code/scipy/scipy.h index 0b0fe02..bfe69eb 100644 --- a/code/scipy/scipy.h +++ b/code/scipy/scipy.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * */ diff --git a/code/scipy/signal/signal.c b/code/scipy/signal/signal.c index 417bd2b..09e92d7 100644 --- a/code/scipy/signal/signal.c +++ b/code/scipy/signal/signal.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ diff --git a/code/scipy/signal/signal.h b/code/scipy/signal/signal.h index da8e123..5e85ef0 100644 --- a/code/scipy/signal/signal.h +++ b/code/scipy/signal/signal.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * */ diff --git a/code/scipy/special/special.c b/code/scipy/special/special.c index 6fa4bc0..5354018 100644 --- a/code/scipy/special/special.c +++ b/code/scipy/special/special.c @@ -8,7 +8,7 @@ * * Copyright (c) 2020 Jeff Epler for Adafruit Industries * 2020 Scott Shawcroft for Adafruit Industries - * 2020 Zoltán Vörös + * 2020-2021 Zoltán Vörös * 2020 Taku Fukada */ @@ -16,7 +16,7 @@ #include "py/runtime.h" #include "../../ulab.h" -#include "../../numpy/vector/vectorise.h" +#include "../../numpy/vector/vector.h" static const mp_rom_map_elem_t ulab_scipy_special_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_special) }, diff --git a/code/scipy/special/special.h b/code/scipy/special/special.h index 51d47f6..a64d447 100644 --- a/code/scipy/special/special.h +++ b/code/scipy/special/special.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös * */ diff --git a/code/ulab.c b/code/ulab.c index 9bccc31..28742cb 100644 --- a/code/ulab.c +++ b/code/ulab.c @@ -6,7 +6,8 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös + * 2020 Jeff Epler for Adafruit Industries */ #include @@ -22,21 +23,24 @@ #include "ulab_create.h" #include "ndarray.h" #include "ndarray_properties.h" + +#if CIRCUITPY +#include "circuitpy/vector/vector.h" +#else #include "numpy/numpy.h" #include "scipy/scipy.h" #include "numpy/fft/fft.h" #include "numpy/linalg/linalg.h" +// TODO: we should get rid of this; array.sort depends on it #include "numpy/numerical/numerical.h" +#endif + #include "user/user.h" #define ULAB_VERSION 2.1.0 #define xstr(s) str(s) #define str(s) #s -#if ULAB_NUMPY_COMPATIBILITY -#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) xstr(-numpy) -#else -#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) xstr(-cpy) -#endif +#define ULAB_VERSION_STRING xstr(ULAB_VERSION) xstr(-) xstr(ULAB_MAX_DIMS) xstr(D) STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, ULAB_VERSION_STRING); @@ -124,35 +128,10 @@ STATIC const mp_map_elem_t ulab_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_dtype), (mp_obj_t)&ndarray_dtype_obj }, #endif /* NDARRAY_HAS_DTYPE */ #endif /* ULAB_HAS_DTYPE_OBJECT */ - #if ULAB_NUMPY_COMPATIBILITY { MP_ROM_QSTR(MP_QSTR_numpy), MP_ROM_PTR(&ulab_numpy_module) }, - #if ULAB_HAS_SCIPY - { MP_ROM_QSTR(MP_QSTR_scipy), MP_ROM_PTR(&ulab_scipy_module) }, - #endif - #else // from here, the circuitpython nomenclature - - #if ULAB_APPROX_MODULE - { MP_ROM_QSTR(MP_QSTR_approx), MP_ROM_PTR(&ulab_approx_module) }, - #endif - #if ULAB_COMPARE_MODULE - { MP_ROM_QSTR(MP_QSTR_compare), MP_ROM_PTR(&ulab_compare_module) }, - #endif - #if ULAB_FILTER_MODULE - { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&ulab_filter_module) }, - #endif - #if ULAB_LINALG_MODULE - { MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_linalg_module) }, - #endif - #if ULAB_NUMERICAL_MODULE - { MP_ROM_QSTR(MP_QSTR_numerical), MP_ROM_PTR(&ulab_numerical_module) }, - #endif - #if ULAB_POLY_MODULE - { MP_ROM_QSTR(MP_QSTR_poly), MP_ROM_PTR(&ulab_poly_module) }, - #endif - #if ULAB_VECTORISE_MODULE - { MP_ROM_QSTR(MP_QSTR_vector), MP_ROM_PTR(&ulab_vectorise_module) }, - #endif - #endif /* ULAB_NUMPY_COMPATIBILITY */ + #if ULAB_HAS_SCIPY + { MP_ROM_QSTR(MP_QSTR_scipy), MP_ROM_PTR(&ulab_scipy_module) }, + #endif #if ULAB_USER_MODULE { MP_ROM_QSTR(MP_QSTR_user), MP_ROM_PTR(&ulab_user_module) }, #endif diff --git a/code/ulab.h b/code/ulab.h index 91e2ec3..4c602eb 100644 --- a/code/ulab.h +++ b/code/ulab.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2019-2020 Zoltán Vörös + * Copyright (c) 2019-2021 Zoltán Vörös */ #ifndef __ULAB__ @@ -23,45 +23,35 @@ // A considerable amount of flash space can be saved by removing (setting // the corresponding constants to 0) the unnecessary functions and features. -// Setting this variable to 1 produces numpy-compatible firmware, -// i.e., functions can be called at the top level, -// without having to import the sub-modules (linalg and fft are exceptions, -// since those must be imported even in numpy) -#ifdef CIRCUITPY -#define ULAB_NUMPY_COMPATIBILITY (0) -#else -#define ULAB_NUMPY_COMPATIBILITY (1) -#endif - -// determines, whether scipy is defined in ulab. The sub-modules and functions +// Determines, whether scipy is defined in ulab. The sub-modules and functions // of scipy have to be defined separately #define ULAB_HAS_SCIPY (1) // The maximum number of dimensions the firmware should be able to support // Possible values lie between 1, and 4, inclusive -#define ULAB_MAX_DIMS 2 +#define ULAB_MAX_DIMS 2 // By setting this constant to 1, iteration over array dimensions will be implemented // as a function (ndarray_rewind_array), instead of writing out the loops in macros // This reduces firmware size at the expense of speed -#define ULAB_HAS_FUNCTION_ITERATOR (0) +#define ULAB_HAS_FUNCTION_ITERATOR (0) // If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function // This option saves approx. 250 bytes of flash space -#define NDARRAY_IS_ITERABLE (1) +#define NDARRAY_IS_ITERABLE (1) // Slicing can be switched off by setting this variable to 0 -#define NDARRAY_IS_SLICEABLE (1) +#define NDARRAY_IS_SLICEABLE (1) // The default threshold for pretty printing. These variables can be overwritten // at run-time via the set_printoptions() function -#define ULAB_HAS_PRINTOPTIONS (1) -#define NDARRAY_PRINT_THRESHOLD 10 -#define NDARRAY_PRINT_EDGEITEMS 3 +#define ULAB_HAS_PRINTOPTIONS (1) +#define NDARRAY_PRINT_THRESHOLD 10 +#define NDARRAY_PRINT_EDGEITEMS 3 // determines, whether the dtype is an object, or simply a character // the object implementation is numpythonic, but requires more space -#define ULAB_HAS_DTYPE_OBJECT (0) +#define ULAB_HAS_DTYPE_OBJECT (0) // the ndarray binary operators #define NDARRAY_HAS_BINARY_OPS (1) @@ -244,9 +234,10 @@ #define ULAB_SCIPY_SPECIAL_HAS_GAMMA (1) #define ULAB_SCIPY_SPECIAL_HAS_GAMMALN (1) -// user-defined module -#ifndef ULAB_USER_MODULE -#define ULAB_USER_MODULE (0) +// user-defined module; source of the module and +// its sub-modules should be placed in code/user/ +#ifndef ULAB_HAS_USER_MODULE +#define ULAB_HAS_USER_MODULE (0) #endif #endif diff --git a/code/ulab_create.c b/code/ulab_create.c index 2ede40c..64c0aed 100644 --- a/code/ulab_create.c +++ b/code/ulab_create.c @@ -6,7 +6,7 @@ * The MIT License (MIT) * * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * 2019-2020 Zoltán Vörös + * 2019-2021 Zoltán Vörös * 2020 Taku Fukada */ diff --git a/code/ulab_create.h b/code/ulab_create.h index 09b4218..8743a99 100644 --- a/code/ulab_create.h +++ b/code/ulab_create.h @@ -6,7 +6,7 @@ * The MIT License (MIT) * * Copyright (c) 2020 Jeff Epler for Adafruit Industries - * 2019-2020 Zoltán Vörös + * 2019-2021 Zoltán Vörös */ #ifndef _CREATE_ diff --git a/code/ulab_tools.c b/code/ulab_tools.c index 360f03d..090767b 100644 --- a/code/ulab_tools.c +++ b/code/ulab_tools.c @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ @@ -61,7 +61,7 @@ void *ndarray_get_float_function(uint8_t dtype) { } mp_float_t ndarray_get_float_index(void *data, uint8_t dtype, size_t index) { - // returns a single float value from an array located at index + // returns a single float value from an array located at index if(dtype == NDARRAY_UINT8) { return (mp_float_t)((uint8_t *)data)[index]; } else if(dtype == NDARRAY_INT8) { @@ -93,10 +93,10 @@ mp_float_t ndarray_get_float_value(void *data, uint8_t dtype) { #if NDARRAY_BINARY_USES_FUN_POINTER uint8_t ndarray_upcast_dtype(uint8_t ldtype, uint8_t rdtype) { - // returns a single character that corresponds to the broadcasting rules - // - if one of the operarands is a float, the result is always float + // returns a single character that corresponds to the broadcasting rules + // - if one of the operarands is a float, the result is always float // - operation on identical types preserves type - // + // // uint8 + int8 => int16 // uint8 + int16 => int16 // uint8 + uint16 => uint16 @@ -104,43 +104,43 @@ uint8_t ndarray_upcast_dtype(uint8_t ldtype, uint8_t rdtype) { // int8 + uint16 => uint16 // uint16 + int16 => float - if(ldtype == rdtype) { - // if the two dtypes are equal, the result is also of that type - return ldtype; - } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT8)) || - ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT8)) || - ((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT16)) || - ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_UINT8)) || - ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_INT16)) || - ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_INT8))) { - return NDARRAY_INT16; - } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_UINT16)) || - ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_UINT8)) || - ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT16)) || - ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_INT8))) { - return NDARRAY_UINT16; - } - return NDARRAY_FLOAT; + if(ldtype == rdtype) { + // if the two dtypes are equal, the result is also of that type + return ldtype; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_INT16)) || + ((ldtype == NDARRAY_INT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_INT16; + } else if(((ldtype == NDARRAY_UINT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_UINT8)) || + ((ldtype == NDARRAY_INT8) && (rdtype == NDARRAY_UINT16)) || + ((ldtype == NDARRAY_UINT16) && (rdtype == NDARRAY_INT8))) { + return NDARRAY_UINT16; + } + return NDARRAY_FLOAT; } void ndarray_set_float_uint8(void *data, mp_float_t datum) { - *((uint8_t *)data) = (uint8_t)datum; + *((uint8_t *)data) = (uint8_t)datum; } void ndarray_set_float_int8(void *data, int8_t datum) { - *((int8_t *)data) = (int8_t)datum; + *((int8_t *)data) = (int8_t)datum; } void ndarray_set_float_uint16(void *data, mp_float_t datum) { - *((uint16_t *)data) = (uint16_t)datum; + *((uint16_t *)data) = (uint16_t)datum; } void ndarray_set_float_int16(void *data, int8_t datum) { - *((int16_t *)data) = (int16_t)datum; + *((int16_t *)data) = (int16_t)datum; } void ndarray_set_float_float(void *data, mp_float_t datum) { - *((mp_float_t *)data) = datum; + *((mp_float_t *)data) = datum; } // returns a single function pointer, depending on the dtype diff --git a/code/ulab_tools.h b/code/ulab_tools.h index 6022c6f..acb5042 100644 --- a/code/ulab_tools.h +++ b/code/ulab_tools.h @@ -5,7 +5,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ #ifndef _TOOLS_ diff --git a/code/user/user.c b/code/user/user.c index 26b5643..9c3e4d0 100644 --- a/code/user/user.c +++ b/code/user/user.c @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ #include diff --git a/code/user/user.h b/code/user/user.h index a8e452b..4ec366a 100644 --- a/code/user/user.h +++ b/code/user/user.h @@ -6,7 +6,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2020 Zoltán Vörös + * Copyright (c) 2020-2021 Zoltán Vörös */ #ifndef _USER_ @@ -15,9 +15,6 @@ #include "../ulab.h" #include "../ndarray.h" -#if ULAB_USER_MODULE - extern mp_obj_module_t ulab_user_module; #endif -#endif diff --git a/docs/manual/Makefile b/docs/manual/Makefile index 4f92a86..a97f725 100644 --- a/docs/manual/Makefile +++ b/docs/manual/Makefile @@ -14,15 +14,11 @@ help: .PHONY: help Makefile - -stubs: - python extract_pyi.py ../../code source/ulab/ - clean: rm -rf "$(BUILDDIR)" - rm -rf source/ulab/ - + + # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile stubs +%: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/manual/autoapi/templates/python/module.rst b/docs/manual/autoapi/templates/python/module.rst deleted file mode 100644 index 62ae1c7..0000000 --- a/docs/manual/autoapi/templates/python/module.rst +++ /dev/null @@ -1,91 +0,0 @@ -{% if not obj.display %} -:orphan: - -{% endif %} -:mod:`{{ obj.name }}` -======={{ "=" * obj.name|length }} - -.. py:module:: {{ obj.name }} - -{% if obj.docstring %} -.. autoapi-nested-parse:: - - {{ obj.docstring|prepare_docstring|indent(3) }} - -{% endif %} - -{% block subpackages %} -{% set visible_subpackages = obj.subpackages|selectattr("display")|list %} -{% if visible_subpackages %} -.. toctree:: - :titlesonly: - :maxdepth: 3 - -{% for subpackage in visible_subpackages %} - {{ subpackage.short_name }}/index.rst -{% endfor %} - - -{% endif %} -{% endblock %} -{% block submodules %} -{% set visible_submodules = obj.submodules|selectattr("display")|list %} -{% if visible_submodules %} -.. toctree:: - :titlesonly: - :maxdepth: 1 - -{% for submodule in visible_submodules %} - {{ submodule.short_name }}/index.rst -{% endfor %} - - -{% endif %} -{% endblock %} -{% block content %} -{% if obj.all is not none %} -{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %} -{% elif obj.type is equalto("package") %} -{% set visible_children = obj.children|selectattr("display")|list %} -{% else %} -{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %} -{% endif %} -{% if visible_children %} -{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %} -{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %} -{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %} -{% block classes scoped %} -{% if visible_classes %} -Classes -~~~~~~~ - -.. autoapisummary:: - -{% for klass in visible_classes %} - {{ klass.id }} -{% endfor %} - - -{% endif %} -{% endblock %} - -{% block functions scoped %} -{% if visible_functions %} -Functions -~~~~~~~~~ - -.. autoapisummary:: - -{% for function in visible_functions %} - {{ function.id }} -{% endfor %} - - -{% endif %} -{% endblock %} -{% endif %} -{% for obj_item in visible_children %} -{{ obj_item.rendered|indent(0) }} -{% endfor %} -{% endif %} -{% endblock %} diff --git a/docs/manual/extract_pyi.py b/docs/manual/extract_pyi.py deleted file mode 100644 index b7ce584..0000000 --- a/docs/manual/extract_pyi.py +++ /dev/null @@ -1,223 +0,0 @@ -# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) -# -# SPDX-License-Identifier: MIT - -# Run with 'python tools/extract_pyi.py shared-bindings/ path/to/stub/dir -# You can also test a specific library in shared-bindings by putting the path -# to that directory instead - -import ast -import os -import re -import sys -import traceback - -import isort -import black - - -IMPORTS_IGNORE = frozenset({'int', 'float', 'bool', 'str', 'bytes', 'tuple', 'list', 'set', 'dict', 'bytearray', 'slice', 'file', 'buffer', 'range', 'array', 'struct_time'}) -IMPORTS_TYPING = frozenset({'Any', 'Optional', 'Union', 'Tuple', 'List', 'Sequence', 'NamedTuple', 'Iterable', 'Iterator', 'Callable', 'AnyStr', 'overload', 'Type'}) -IMPORTS_TYPES = frozenset({'TracebackType'}) -CPY_TYPING = frozenset({'ReadableBuffer', 'WriteableBuffer', 'AudioSample', 'FrameBuffer'}) - - -def is_typed(node, allow_any=False): - if node is None: - return False - if allow_any: - return True - elif isinstance(node, ast.Name) and node.id == "Any": - return False - elif isinstance(node, ast.Attribute) and type(node.value) == ast.Name \ - and node.value.id == "typing" and node.attr == "Any": - return False - return True - - -def find_stub_issues(tree): - for node in ast.walk(tree): - if isinstance(node, ast.AnnAssign): - if not is_typed(node.annotation): - yield ("WARN", f"Missing attribute type on line {node.lineno}") - if isinstance(node.value, ast.Constant) and node.value.value == Ellipsis: - yield ("WARN", f"Unnecessary Ellipsis assignment (= ...) on line {node.lineno}.") - elif isinstance(node, ast.Assign): - if isinstance(node.value, ast.Constant) and node.value.value == Ellipsis: - yield ("WARN", f"Unnecessary Ellipsis assignment (= ...) on line {node.lineno}.") - elif isinstance(node, ast.arguments): - allargs = list(node.args + node.kwonlyargs) - if sys.version_info >= (3, 8): - allargs.extend(node.posonlyargs) - for arg_node in allargs: - if not is_typed(arg_node.annotation) and (arg_node.arg != "self" and arg_node.arg != "cls"): - yield ("WARN", f"Missing argument type: {arg_node.arg} on line {arg_node.lineno}") - if node.vararg and not is_typed(node.vararg.annotation, allow_any=True): - yield ("WARN", f"Missing argument type: *{node.vararg.arg} on line {node.vararg.lineno}") - if node.kwarg and not is_typed(node.kwarg.annotation, allow_any=True): - yield ("WARN", f"Missing argument type: **{node.kwarg.arg} on line {node.kwarg.lineno}") - elif isinstance(node, ast.FunctionDef): - if not is_typed(node.returns): - yield ("WARN", f"Missing return type: {node.name} on line {node.lineno}") - - -def extract_imports(tree): - modules = set() - typing = set() - types = set() - cpy_typing = set() - - def collect_annotations(anno_tree): - if anno_tree is None: - return - for node in ast.walk(anno_tree): - if isinstance(node, ast.Name): - if node.id in IMPORTS_IGNORE: - continue - elif node.id in IMPORTS_TYPING: - typing.add(node.id) - elif node.id in IMPORTS_TYPES: - types.add(node.id) - elif node.id in CPY_TYPING: - cpy_typing.add(node.id) - elif isinstance(node, ast.Attribute): - if isinstance(node.value, ast.Name): - modules.add(node.value.id) - - for node in ast.walk(tree): - if isinstance(node, (ast.AnnAssign, ast.arg)): - collect_annotations(node.annotation) - elif isinstance(node, ast.Assign): - collect_annotations(node.value) - elif isinstance(node, ast.FunctionDef): - collect_annotations(node.returns) - for deco in node.decorator_list: - if isinstance(deco, ast.Name) and (deco.id in IMPORTS_TYPING): - typing.add(deco.id) - - return { - "modules": sorted(modules), - "typing": sorted(typing), - "types": sorted(types), - "cpy_typing": sorted(cpy_typing), - } - - -def find_references(tree): - for node in ast.walk(tree): - if isinstance(node, ast.arguments): - for node in ast.walk(node): - if isinstance(node, ast.Attribute): - if isinstance(node.value, ast.Name) and node.value.id[0].isupper(): - yield node.value.id - - -def convert_folder(top_level, stub_directory): - ok = 0 - total = 0 - filenames = sorted(os.listdir(top_level)) - stub_fragments = [] - references = set() - - for filename in filenames: - full_path = os.path.join(top_level, filename) - file_lines = [] - if os.path.isdir(full_path): - (mok, mtotal) = convert_folder(full_path, os.path.join(stub_directory, filename)) - ok += mok - total += mtotal - elif filename.endswith(".c"): - with open(full_path, "r", encoding="utf-8") as f: - for line in f: - line = line.rstrip() - if line.startswith("//|"): - if len(line) == 3: - line = "" - elif line[3] == " ": - line = line[4:] - else: - line = line[3:] - print("[WARN] There must be at least one space after '//|'") - file_lines.append(line) - elif filename.endswith(".pyi"): - with open(full_path, "r") as f: - file_lines.extend(line.rstrip() for line in f) - - fragment = "\n".join(file_lines).strip() - try: - tree = ast.parse(fragment) - except SyntaxError as e: - print(f"[ERROR] Failed to parse a Python stub from {full_path}") - traceback.print_exception(type(e), e, e.__traceback__) - return (ok, total + 1) - references.update(find_references(tree)) - - if fragment: - name = os.path.splitext(os.path.basename(filename))[0] - if name == "__init__" or (name in references): - stub_fragments.insert(0, fragment) - else: - stub_fragments.append(fragment) - - if not stub_fragments: - return (ok, total) - - stub_filename = os.path.join(stub_directory, "__init__.pyi") - print(stub_filename) - stub_contents = "\n\n".join(stub_fragments) - - # Validate the stub code. - try: - tree = ast.parse(stub_contents) - except SyntaxError as e: - traceback.print_exception(type(e), e, e.__traceback__) - return (ok, total) - - error = False - for (level, msg) in find_stub_issues(tree): - if level == "ERROR": - error = True - print(f"[{level}] {msg}") - - total += 1 - if not error: - ok += 1 - - # Add import statements - imports = extract_imports(tree) - import_lines = ["from __future__ import annotations"] - if imports["types"]: - import_lines.append("from types import " + ", ".join(imports["types"])) - if imports["typing"]: - import_lines.append("from typing import " + ", ".join(imports["typing"])) - if imports["cpy_typing"]: - import_lines.append("from _typing import " + ", ".join(imports["cpy_typing"])) - import_lines.extend(f"import {m}" for m in imports["modules"]) - import_body = "\n".join(import_lines) - m = re.match(r'(\s*""".*?""")', stub_contents, flags=re.DOTALL) - if m: - stub_contents = m.group(1) + "\n\n" + import_body + "\n\n" + stub_contents[m.end():] - else: - stub_contents = import_body + "\n\n" + stub_contents - - # Code formatting - stub_contents = isort.code(stub_contents) - stub_contents = black.format_str(stub_contents, mode=black.FileMode(is_pyi=True)) - - os.makedirs(stub_directory, exist_ok=True) - with open(stub_filename, "w") as f: - f.write(stub_contents) - - return (ok, total) - - -if __name__ == "__main__": - top_level = sys.argv[1].strip("/") - stub_directory = sys.argv[2] - - (ok, total) = convert_folder(top_level, stub_directory) - - print(f"Parsing .pyi files: {total - ok} failed, {ok} passed") - - if ok != total: - sys.exit(total - ok) diff --git a/docs/manual/source/conf.py b/docs/manual/source/conf.py index c3559cd..51b8785 100644 --- a/docs/manual/source/conf.py +++ b/docs/manual/source/conf.py @@ -23,11 +23,11 @@ from sphinx import addnodes # -- Project information ----------------------------------------------------- project = 'The ulab book' -copyright = '2019-2020, Zoltán Vörös and contributors' +copyright = '2019-2021, Zoltán Vörös and contributors' author = 'Zoltán Vörös' # The full version, including alpha/beta/rc tags -release = '1.4.0' +release = '2.1.1' # -- General configuration --------------------------------------------------- @@ -98,19 +98,6 @@ latex_documents = [ 'Zoltán Vörös', 'manual'), ] -# sphinx-autoapi -extensions.append('autoapi.extension') -autoapi_type = 'python' -autoapi_keep_files = True -autoapi_dirs = ["ulab"] -autoapi_add_toctree_entry = False -autoapi_options = ['members', 'undoc-members', 'private-members', 'show-inheritance', 'special-members'] -autoapi_template_dir = '../autoapi/templates' -autoapi_python_class_content = "both" -autoapi_python_use_implicit_namespaces = True -autoapi_root = "." - - # Read the docs theme on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: @@ -123,36 +110,3 @@ if not on_rtd: html_theme_path = ['.'] else: html_theme_path = ['.'] - - -class UlabTransform(SphinxTransform): - default_priority = 870 - - def _convert_first_paragraph_into_title(self): - title = self.document.next_node(nodes.title) - paragraph = self.document.next_node(nodes.paragraph) - if not title or not paragraph: - return - if isinstance(paragraph[0], nodes.paragraph): - paragraph = paragraph[0] - if all(isinstance(child, nodes.Text) for child in paragraph.children): - for child in paragraph.children: - title.append(nodes.Text(" \u2013 ")) - title.append(child) - paragraph.parent.remove(paragraph) - - def _enable_linking_to_nonclass_targets(self): - for desc in self.document.traverse(addnodes.desc): - for xref in desc.traverse(addnodes.pending_xref): - if xref.attributes.get("reftype") == "class": - xref.attributes.pop("refspecific", None) - - def apply(self, **kwargs): - docname = self.env.docname - if docname.startswith("ulab/"): - self._convert_first_paragraph_into_title() - self._enable_linking_to_nonclass_targets() - - -def setup(app): - app.add_transform(UlabTransform) diff --git a/docs/manual/source/index.rst b/docs/manual/source/index.rst index 9bd9887..af7084b 100644 --- a/docs/manual/source/index.rst +++ b/docs/manual/source/index.rst @@ -13,25 +13,18 @@ Welcome to the ulab book! ulab-intro -.. toctree:: - :maxdepth: 2 - :caption: API Reference - - ulab/index.rst - .. toctree:: :maxdepth: 3 :caption: User's guide: ulab-ndarray - ulab-approx - ulab-compare - ulab-fft - ulab-filter - ulab-linalg - ulab-numerical - ulab-poly - ulab-vectorise + numpy-functions + numpy-universal + numpy-fft + numpy-linalg + scipy-optimize + scipy-signal + scipy-special ulab-programming Indices and tables diff --git a/docs/manual/source/ulab-fft.rst b/docs/manual/source/numpy-fft.rst similarity index 64% rename from docs/manual/source/ulab-fft.rst rename to docs/manual/source/numpy-fft.rst index d6b2e1c..09103ec 100644 --- a/docs/manual/source/ulab-fft.rst +++ b/docs/manual/source/numpy-fft.rst @@ -1,8 +1,12 @@ +None Fourier transforms ================== -Functions related to Fourier transforms can be called by importing the -``fft`` sub-module first. +Functions related to Fourier transforms can be called by prepending them +with ``numpy.fft.``. The module defines the following two functions: + +1. `numpy.fft.fft <#fft>`__ +2. `numpy.fft.ifft <#ifft>`__ ``numpy``: https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.ifft.html @@ -30,8 +34,8 @@ it will be treated as a complex array: -**WARNING:** The array that is returned is also complex, i.e., the real -and imaginary components are cast together. In ``ulab``, the real and +**WARNING:** The array returned is also complex, i.e., the real and +imaginary components are cast together. In ``ulab``, the real and imaginary parts are treated separately: you have to pass two ``ndarray``\ s to the function, although, the second argument is optional, in which case the imaginary part is assumed to be zero. @@ -44,19 +48,17 @@ parts of the transform separately. # code to be run in micropython - import ulab as np - from ulab import vector - from ulab import fft + from ulab import numpy as np x = np.linspace(0, 10, num=1024) - y = vector.sin(x) + y = np.sin(x) z = np.zeros(len(x)) - a, b = fft.fft(x) + a, b = np.fft.fft(x) print('real part:\t', a) print('\nimaginary part:\t', b) - c, d = fft.fft(x, z) + c, d = np.fft.fft(x, z) print('\nreal part:\t', c) print('\nimaginary part:\t', d) @@ -84,18 +86,16 @@ the inverse of the transform is equal to the original array. # code to be run in micropython - import ulab as np - from ulab import vector - from ulab import fft + from ulab import numpy as np x = np.linspace(0, 10, num=1024) - y = vector.sin(x) + y = np.sin(x) - a, b = fft.fft(y) + a, b = np.fft.fft(y) print('original vector:\t', y) - y, z = fft.ifft(a, b) + y, z = np.fft.ifft(a, b) # the real part should be equal to y print('\nreal part of inverse:\t', y) # the imaginary part should be equal to zero @@ -115,71 +115,6 @@ Note that unlike in ``numpy``, the length of the array on which the Fourier transform is carried out must be a power of 2. If this is not the case, the function raises a ``ValueError`` exception. -spectrogram ------------ - -In addition to the Fourier transform and its inverse, ``ulab`` also -sports a function called ``spectrogram``, which returns the absolute -value of the Fourier transform. This could be used to find the dominant -spectral component in a time series. The arguments are treated in the -same way as in ``fft``, and ``ifft``. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import vector - from ulab import fft - - x = np.linspace(0, 10, num=1024) - y = vector.sin(x) - - a = fft.spectrogram(y) - - print('original vector:\t', y) - print('\nspectrum:\t', a) - -.. parsed-literal:: - - original vector: array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893639], dtype=float) - - spectrum: array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float) - - - - -As such, ``spectrogram`` is really just a shorthand for -``np.sqrt(a*a + b*b)``: - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import fft - from ulab import vector - - x = np.linspace(0, 10, num=1024) - y = vector.sin(x) - - a, b = fft.fft(y) - - print('\nspectrum calculated the hard way:\t', vector.sqrt(a*a + b*b)) - - a = fft.spectrogram(y) - - print('\nspectrum calculated the lazy way:\t', a) - -.. parsed-literal:: - - - spectrum calculated the hard way: array([187.8641, 315.3125, 347.8804, ..., 84.4587, 347.8803, 315.3124], dtype=float) - - spectrum calculated the lazy way: array([187.8641, 315.3125, 347.8804, ..., 84.4587, 347.8803, 315.3124], dtype=float) - - - Computation and storage costs ----------------------------- diff --git a/docs/manual/source/numpy-functions.rst b/docs/manual/source/numpy-functions.rst new file mode 100644 index 0000000..ca44575 --- /dev/null +++ b/docs/manual/source/numpy-functions.rst @@ -0,0 +1,1052 @@ +None +Numpy functions +=============== + +This section of the manual discusses those functions that were adapted +from ``numpy``. + +1. `numpy.argmax <#argmax>`__ +2. `numpy.argmin <#argmin>`__ +3. `numpy.argsort <#argsort>`__ +4. `numpy.clip <#clip>`__ +5. `numpy.convolve <#convolve>`__ +6. `numpy.diff <#diff>`__ +7. `numpy.equal <#equal>`__ +8. `numpy.flip <#flip>`__ +9. `numpy.interp <#interp>`__ +10. `numpy.max <#max>`__ +11. `numpy.maximum <#maximum>`__ +12. `numpy.mean <#mean>`__ +13. `numpy.median <#median>`__ +14. `numpy.min <#min>`__ +15. `numpy.minimum <#minimum>`__ +16. `numpy.not_equal <#not_equal>`__ +17. `numpy.polyfit <#polyfit>`__ +18. `numpy.polyval <#polyval>`__ +19. `numpy.roll <#roll>`__ +20. `numpy.sort <#sort>`__ +21. `numpy.std <#std>`__ +22. `numpy.sum <#sum>`__ +23. `numpy.trapz <#trapz>`__ + +argmax +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html + +See `numpy.max <#max>`__. + +argmin +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html + +See `numpy.max <#max>`__. + +argsort +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html + +Similarly to `sort <#sort>`__, ``argsort`` takes a positional, and a +keyword argument, and returns an unsigned short index array of type +``ndarray`` with the same dimensions as the input, or, if ``axis=None``, +as a row vector with length equal to the number of elements in the input +(i.e., the flattened array). The indices in the output sort the input in +ascending order. The routine in ``argsort`` is the same as in ``sort``, +therefore, the comments on computational expenses (time and RAM) also +apply. In particular, since no copy of the original data is required, +virtually no RAM beyond the output array is used. + +Since the underlying container of the output array is of type +``uint16_t``, neither of the output dimensions should be larger than +65535. If that happens to be the case, the function will bail out with a +``ValueError``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) + print('\na:\n', a) + b = np.argsort(a, axis=0) + print('\na sorted along vertical axis:\n', b) + + c = np.argsort(a, axis=1) + print('\na sorted along horizontal axis:\n', c) + + c = np.argsort(a, axis=None) + print('\nflattened a sorted:\n', c) + +.. parsed-literal:: + + + a: + array([[1.0, 12.0, 3.0, 0.0], + [5.0, 3.0, 4.0, 1.0], + [9.0, 11.0, 1.0, 8.0], + [7.0, 10.0, 0.0, 1.0]], dtype=float64) + + a sorted along vertical axis: + array([[0, 1, 3, 0], + [1, 3, 2, 1], + [3, 2, 0, 3], + [2, 0, 1, 2]], dtype=uint16) + + a sorted along horizontal axis: + array([[3, 0, 2, 1], + [3, 1, 2, 0], + [2, 3, 0, 1], + [2, 3, 0, 1]], dtype=uint16) + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 12, in + NotImplementedError: argsort is not implemented for flattened arrays + + + +Since during the sorting, only the indices are shuffled, ``argsort`` +does not modify the input array, as one can verify this by the following +example: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8) + print('\na:\n', a) + b = np.argsort(a, axis=0) + print('\nsorting indices:\n', b) + print('\nthe original array:\n', a) + +.. parsed-literal:: + + + a: + array([0, 5, 1, 3, 2, 4], dtype=uint8) + + sorting indices: + array([0, 2, 4, 3, 5, 1], dtype=uint16) + + the original array: + array([0, 5, 1, 3, 2, 4], dtype=uint8) + + + + +clip +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html + +Clips an array, i.e., values that are outside of an interval are clipped +to the interval edges. The function is equivalent to +``maximum(a_min, minimum(a, a_max))`` broadcasting takes place exactly +as in `minimum <#minimum>`__. If the arrays are of different ``dtype``, +the output is upcast as in `Binary operators <#Binary-operators>`__. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + print('a:\t\t', a) + print('clipped:\t', np.clip(a, 3, 7)) + + b = 3 * np.ones(len(a), dtype=np.float) + print('\na:\t\t', a) + print('b:\t\t', b) + print('clipped:\t', np.clip(a, b, 7)) + +.. parsed-literal:: + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + clipped: array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8) + + a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) + b: array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float64) + clipped: array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float64) + + + + +convolve +-------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html + +Returns the discrete, linear convolution of two one-dimensional arrays. + +Only the ``full`` mode is supported, and the ``mode`` named parameter is +not accepted. Note that all other modes can be had by slicing a ``full`` +result. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array((1, 2, 3)) + y = np.array((1, 10, 100, 1000)) + + print(np.convolve(x, y)) + +.. parsed-literal:: + + array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float64) + + + + +diff +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html + +The ``diff`` function returns the numerical derivative of the forward +scheme, or more accurately, the differences of an ``ndarray`` along a +given axis. The order of derivative can be stipulated with the ``n`` +keyword argument, which should be between 0, and 9. Default is 1. If +higher order derivatives are required, they can be gotten by repeated +calls to the function. The ``axis`` keyword argument should be -1 (last +axis, in ``ulab`` equivalent to the second axis, and this also happens +to be the default value), 0, or 1. + +Beyond the output array, the function requires only a couple of bytes of +extra RAM for the differentiation stencil. (The stencil is an ``int8`` +array, one byte longer than ``n``. This also explains, why the highest +order is 9: the coefficients of a ninth-order stencil all fit in signed +bytes, while 10 would require ``int16``.) Note that as usual in +numerical differentiation (and also in ``numpy``), the length of the +respective axis will be reduced by ``n`` after the operation. If ``n`` +is larger than, or equal to the length of the axis, an empty array will +be returned. + +**WARNING**: the ``diff`` function does not implement the ``prepend`` +and ``append`` keywords that can be found in ``numpy``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9), dtype=np.uint8) + a[3] = 10 + print('a:\n', a) + + print('\nfirst derivative:\n', np.diff(a, n=1)) + print('\nsecond derivative:\n', np.diff(a, n=2)) + + c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]]) + print('\nc:\n', c) + print('\nfirst derivative, first axis:\n', np.diff(c, axis=0)) + print('\nfirst derivative, second axis:\n', np.diff(c, axis=1)) + +.. parsed-literal:: + + a: + array([0, 1, 2, 10, 4, 5, 6, 7, 8], dtype=uint8) + + first derivative: + array([1, 1, 8, 250, 1, 1, 1, 1], dtype=uint8) + + second derivative: + array([0, 249, 14, 249, 0, 0, 0], dtype=uint8) + + c: + array([[1.0, 2.0, 3.0, 4.0], + [4.0, 3.0, 2.0, 1.0], + [1.0, 4.0, 9.0, 16.0], + [0.0, 0.0, 0.0, 0.0]], dtype=float64) + + first derivative, first axis: + array([[3.0, 1.0, -1.0, -3.0], + [-3.0, 1.0, 7.0, 15.0], + [-1.0, -4.0, -9.0, -16.0]], dtype=float64) + + first derivative, second axis: + array([[1.0, 1.0, 1.0], + [-1.0, -1.0, -1.0], + [3.0, 5.0, 7.0], + [0.0, 0.0, 0.0]], dtype=float64) + + + + +equal +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.equal.html + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html + +In ``micropython``, equality of arrays or scalars can be established by +utilising the ``==``, ``!=``, ``<``, ``>``, ``<=``, or ``=>`` binary +operators. In ``circuitpython``, ``==`` and ``!=`` will produce +unexpected results. In order to avoid this discrepancy, and to maintain +compatibility with ``numpy``, ``ulab`` implements the ``equal`` and +``not_equal`` operators that return the same results, irrespective of +the ``python`` implementation. + +These two functions take two ``ndarray``\ s, or scalars as their +arguments. No keyword arguments are implemented. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(9)) + b = np.zeros(9) + + print('a: ', a) + print('b: ', b) + print('\na == b: ', np.equal(a, b)) + print('a != b: ', np.not_equal(a, b)) + + # comparison with scalars + print('a == 2: ', np.equal(a, 2)) + +.. parsed-literal:: + + a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float64) + + a == b: array([True, False, False, False, False, False, False, False, False], dtype=bool) + a != b: array([False, True, True, True, True, True, True, True, True], dtype=bool) + a == 2: array([False, False, True, False, False, False, False, False, False], dtype=bool) + + + + +flip +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html + +The ``flip`` function takes one positional, an ``ndarray``, and one +keyword argument, ``axis = None``, and reverses the order of elements +along the given axis. If the keyword argument is ``None``, the matrix’ +entries are flipped along all axes. ``flip`` returns a new copy of the +array. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5]) + print("a: \t", a) + print("a flipped:\t", np.flip(a)) + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) + print("\na flipped horizontally\n", np.flip(a, axis=1)) + print("\na flipped vertically\n", np.flip(a, axis=0)) + print("\na flipped horizontally+vertically\n", np.flip(a)) + +.. parsed-literal:: + + a: array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float64) + a flipped: array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float64) + + a flipped horizontally + array([[3, 2, 1], + [6, 5, 4], + [9, 8, 7]], dtype=uint8) + + a flipped vertically + array([[7, 8, 9], + [4, 5, 6], + [1, 2, 3]], dtype=uint8) + + a flipped horizontally+vertically + array([9, 8, 7, 6, 5, 4, 3, 2, 1], dtype=uint8) + + + + +interp +------ + +``numpy``: https://docs.scipy.org/doc/numpy/numpy.interp + +The ``interp`` function returns the linearly interpolated values of a +one-dimensional numerical array. It requires three positional +arguments,\ ``x``, at which the interpolated values are evaluated, +``xp``, the array of the independent data variable, and ``fp``, the +array of the dependent values of the data. ``xp`` must be a +monotonically increasing sequence of numbers. + +Two keyword arguments, ``left``, and ``right`` can also be supplied; +these determine the return values, if ``x < xp[0]``, and ``x > xp[-1]``, +respectively. If these arguments are not supplied, ``left``, and +``right`` default to ``fp[0]``, and ``fp[-1]``, respectively. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array([1, 2, 3, 4, 5]) - 0.2 + xp = np.array([1, 2, 3, 4]) + fp = np.array([1, 2, 3, 5]) + + print(x) + print(np.interp(x, xp, fp)) + print(np.interp(x, xp, fp, left=0.0)) + print(np.interp(x, xp, fp, right=10.0)) + +.. parsed-literal:: + + array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64) + array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64) + array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64) + array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64) + + + + +mean +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + print('mean, flat: ', np.mean(a)) + print('mean, horizontal: ', np.mean(a, axis=1)) + print('mean, vertical: ', np.mean(a, axis=0)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + mean, flat: 5.0 + mean, horizontal: array([2.0, 5.0, 8.0], dtype=float64) + mean, vertical: array([4.0, 5.0, 6.0], dtype=float64) + + + + +max +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html + +**WARNING:** Difference to ``numpy``: the ``out`` keyword argument is +not implemented. + +These functions follow the same pattern, and work with generic +iterables, and ``ndarray``\ s. ``min``, and ``max`` return the minimum +or maximum of a sequence. If the input array is two-dimensional, the +``axis`` keyword argument can be supplied, in which case the +minimum/maximum along the given axis will be returned. If ``axis=None`` +(this is also the default value), the minimum/maximum of the flattened +array will be determined. + +``argmin/argmax`` return the position (index) of the minimum/maximum in +the sequence. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 0, 1, 10]) + print('a:', a) + print('min of a:', np.min(a)) + print('argmin of a:', np.argmin(a)) + + b = np.array([[1, 2, 0], [1, 10, -1]]) + print('\nb:\n', b) + print('min of b (flattened):', np.min(b)) + print('min of b (axis=0):', np.min(b, axis=0)) + print('min of b (axis=1):', np.min(b, axis=1)) + +.. parsed-literal:: + + a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float64) + min of a: 0.0 + argmin of a: 2 + + b: + array([[1.0, 2.0, 0.0], + [1.0, 10.0, -1.0]], dtype=float64) + min of b (flattened): -1.0 + min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float64) + min of b (axis=1): array([0.0, -1.0], dtype=float64) + + + + +median +------ + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html + +The function computes the median along the specified axis, and returns +the median of the array elements. If the ``axis`` keyword argument is +``None``, the arrays is flattened first. The ``dtype`` of the results is +always float. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12), dtype=np.int8).reshape((3, 4)) + print('a:\n', a) + print('\nmedian of the flattened array: ', np.median(a)) + print('\nmedian along the vertical axis: ', np.median(a, axis=0)) + print('\nmedian along the horizontal axis: ', np.median(a, axis=1)) + +.. parsed-literal:: + + a: + array([[0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9, 10, 11]], dtype=int8) + + median of the flattened array: 5.5 + + median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float64) + + median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float64) + + + + +min +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html + +See `numpy.max <#max>`__. + +.. code:: + + # code to be run in CPython + + +minimum +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html + +Returns the minimum of two arrays, or two scalars, or an array, and a +scalar. If the arrays are of different ``dtype``, the output is upcast +as in `Binary operators <#Binary-operators>`__. If both inputs are +scalars, a scalar is returned. Only positional arguments are +implemented. + +maximum +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html + +Returns the maximum of two arrays, or two scalars, or an array, and a +scalar. If the arrays are of different ``dtype``, the output is upcast +as in `Binary operators <#Binary-operators>`__. If both inputs are +scalars, a scalar is returned. Only positional arguments are +implemented. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) + b = np.array([5, 4, 3, 2, 1], dtype=np.float) + print('minimum of a, and b:') + print(np.minimum(a, b)) + + print('\nmaximum of a, and b:') + print(np.maximum(a, b)) + + print('\nmaximum of 1, and 5.5:') + print(np.maximum(1, 5.5)) + +.. parsed-literal:: + + minimum of a, and b: + array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float64) + + maximum of a, and b: + array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float64) + + maximum of 1, and 5.5: + 5.5 + + + + +not_equal +--------- + +See `numpy.equal <#equal>`__. + +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))``. + +If the lengths of ``x``, and ``y`` are not the same, the function raises +a ``ValueError``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.array([0, 1, 2, 3, 4, 5, 6]) + y = np.array([9, 4, 1, 0, 1, 4, 9]) + print('independent values:\t', x) + print('dependent values:\t', y) + print('fitted values:\t\t', np.polyfit(x, y, 2)) + + # the same with missing x + print('\ndependent values:\t', y) + print('fitted values:\t\t', np.polyfit(y, 2)) + +.. parsed-literal:: + + independent values: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64) + dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64) + fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float64) + + dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64) + fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float64) + + + + +Execution time +~~~~~~~~~~~~~~ + +``polyfit`` is based on the inversion of a matrix (there is more on the +background in https://en.wikipedia.org/wiki/Polynomial_regression), and +it requires the intermediate storage of ``2*N*(deg+1)`` floats, where +``N`` is the number of entries in the input array, and ``deg`` is the +fit’s degree. The additional computation costs of the matrix inversion +discussed in `linalg.inv <#inv>`__ also apply. The example from above +needs around 150 microseconds to return: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def time_polyfit(x, y, n): + return np.polyfit(x, y, n) + + x = np.array([0, 1, 2, 3, 4, 5, 6]) + y = np.array([9, 4, 1, 0, 1, 4, 9]) + + time_polyfit(x, y, 2) + +.. parsed-literal:: + + execution time: 153 us + + +polyval +------- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html + +``polyval`` takes two arguments, both arrays or generic ``micropython`` +iterables returning scalars. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + p = [1, 1, 1, 0] + x = [0, 1, 2, 3, 4] + print('coefficients: ', p) + print('independent values: ', x) + print('\nvalues of p(x): ', np.polyval(p, x)) + + # the same works with one-dimensional ndarrays + a = np.array(x) + print('\nndarray (a): ', a) + print('value of p(a): ', np.polyval(p, a)) + +.. parsed-literal:: + + coefficients: [1, 1, 1, 0] + independent values: [0, 1, 2, 3, 4] + + values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64) + + ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64) + value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64) + + + + +roll +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html + +The roll function shifts the content of a vector by the positions given +as the second argument. If the ``axis`` keyword is supplied, the shift +is applied to the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2, 3, 4, 5, 6, 7, 8]) + print("a:\t\t\t", a) + + a = np.roll(a, 2) + print("a rolled to the left:\t", a) + + # this should be the original vector + a = np.roll(a, -2) + print("a rolled to the right:\t", a) + +.. parsed-literal:: + + a: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + a rolled to the left: array([7.0, 8.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64) + a rolled to the right: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + + + + +Rolling works with matrices, too. If the ``axis`` keyword is 0, the +matrix is rolled along its vertical axis, otherwise, horizontally. + +Horizontal rolls are faster, because they require fewer steps, and +larger memory chunks are copied, however, they also require more RAM: +basically the whole row must be stored internally. Most expensive are +the ``None`` keyword values, because with ``axis = None``, the array is +flattened first, hence the row’s length is the size of the whole matrix. + +Vertical rolls require two internal copies of single columns. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, 2, axis=0) + print("\na rolled up:\n", a) + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, -1, axis=1) + print("\na rolled to the left:\n", a) + + a = np.array(range(12)).reshape((3, 4)) + print("a:\n", a) + a = np.roll(a, 1, axis=None) + print("\na rolled with None:\n", a) + +.. parsed-literal:: + + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled up: + array([[4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0], + [0.0, 1.0, 2.0, 3.0]], dtype=float64) + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled to the left: + array([[1.0, 2.0, 3.0, 0.0], + [5.0, 6.0, 7.0, 4.0], + [9.0, 10.0, 11.0, 8.0]], dtype=float64) + a: + array([[0.0, 1.0, 2.0, 3.0], + [4.0, 5.0, 6.0, 7.0], + [8.0, 9.0, 10.0, 11.0]], dtype=float64) + + a rolled with None: + array([[11.0, 0.0, 1.0, 2.0], + [3.0, 4.0, 5.0, 6.0], + [7.0, 8.0, 9.0, 10.0]], dtype=float64) + + + + +sort +---- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html + +The sort function takes an ndarray, and sorts its elements in ascending +order along the specified axis using a heap sort algorithm. As opposed +to the ``.sort()`` method discussed earlier, this function creates a +copy of its input before sorting, and at the end, returns this copy. +Sorting takes place in place, without auxiliary storage. The ``axis`` +keyword argument takes on the possible values of -1 (the last axis, in +``ulab`` equivalent to the second axis, and this also happens to be the +default value), 0, 1, or ``None``. The first three cases are identical +to those in `diff <#diff>`__, while the last one flattens the array +before sorting. + +If descending order is required, the result can simply be ``flip``\ ped, +see `flip <#flip>`__. + +**WARNING:** ``numpy`` defines the ``kind``, and ``order`` keyword +arguments that are not implemented here. The function in ``ulab`` always +uses heap sort, and since ``ulab`` does not have the concept of data +fields, the ``order`` keyword argument would have no meaning. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) + print('\na:\n', a) + b = np.sort(a, axis=0) + print('\na sorted along vertical axis:\n', b) + + c = np.sort(a, axis=1) + print('\na sorted along horizontal axis:\n', c) + + c = np.sort(a, axis=None) + print('\nflattened a sorted:\n', c) + +.. parsed-literal:: + + + a: + array([[1.0, 12.0, 3.0, 0.0], + [5.0, 3.0, 4.0, 1.0], + [9.0, 11.0, 1.0, 8.0], + [7.0, 10.0, 0.0, 1.0]], dtype=float64) + + a sorted along vertical axis: + array([[1.0, 3.0, 0.0, 0.0], + [5.0, 10.0, 1.0, 1.0], + [7.0, 11.0, 3.0, 1.0], + [9.0, 12.0, 4.0, 8.0]], dtype=float64) + + a sorted along horizontal axis: + array([[0.0, 1.0, 3.0, 12.0], + [1.0, 3.0, 4.0, 5.0], + [1.0, 8.0, 9.0, 11.0], + [0.0, 1.0, 7.0, 10.0]], dtype=float64) + + flattened a sorted: + array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float64) + + + + +Heap sort requires :math:`\sim N\log N` operations, and notably, the +worst case costs only 20% more time than the average. In order to get an +order-of-magnitude estimate, we will take the sine of 1000 uniformly +spaced numbers between 0, and two pi, and sort them: + +.. code:: + + # code to be run in micropython + + import ulab as np + from ulab import vector + from ulab import numerical + + @timeit + def sort_time(array): + return numerical.sort(array) + + b = vector.sin(np.linspace(0, 6.28, num=1000)) + print('b: ', b) + sort_time(b) + print('\nb sorted:\n', b) +std +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + print('sum, flat array: ', np.std(a)) + print('std, vertical: ', np.std(a, axis=0)) + print('std, horizonal: ', np.std(a, axis=1)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + sum, flat array: 2.581988897471611 + std, vertical: array([2.449489742783178, 2.449489742783178, 2.449489742783178], dtype=float64) + std, horizonal: array([0.8164965809277261, 0.8164965809277261, 0.8164965809277261], dtype=float64) + + + + +sum +--- + +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html + +If the axis keyword is not specified, it assumes the default value of +``None``, and returns the result of the computation for the flattened +array. Otherwise, the calculation is along the given axis. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + print('a: \n', a) + + print('sum, flat array: ', np.sum(a)) + print('sum, horizontal: ', np.sum(a, axis=1)) + print('std, vertical: ', np.sum(a, axis=0)) + +.. parsed-literal:: + + a: + array([[1.0, 2.0, 3.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]], dtype=float64) + sum, flat array: 45.0 + sum, horizontal: array([6.0, 15.0, 24.0], dtype=float64) + std, vertical: array([12.0, 15.0, 18.0], dtype=float64) + + + + +trapz +----- + +``numpy``: +https://numpy.org/doc/stable/reference/generated/numpy.trapz.html + +The function takes one or two one-dimensional ``ndarray``\ s, and +integrates the dependent values (``y``) using the trapezoidal rule. If +the independent variable (``x``) is given, that is taken as the sample +points corresponding to ``y``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + x = np.linspace(0, 9, num=10) + y = x*x + + print('x: ', x) + print('y: ', y) + print('============================') + print('integral of y: ', np.trapz(y)) + print('integral of y at x: ', np.trapz(y, x=x)) + +.. parsed-literal:: + + x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64) + y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float64) + ============================ + integral of y: 244.5 + integral of y at x: 244.5 + + + + +.. code:: + + # code to be run in CPython + diff --git a/docs/manual/source/ulab-linalg.rst b/docs/manual/source/numpy-linalg.rst similarity index 81% rename from docs/manual/source/ulab-linalg.rst rename to docs/manual/source/numpy-linalg.rst index 6bba6c6..a5de54d 100644 --- a/docs/manual/source/ulab-linalg.rst +++ b/docs/manual/source/numpy-linalg.rst @@ -1,93 +1,111 @@ +None Linalg ====== -Functions in the ``linalg`` module can be called by importing the -sub-module first. +Functions in the ``linalg`` module can be called by prepending them by +``numpy.linalg.``. The module defines the following seven functions: -inv ---- +1. `numpy.linalg.cholesky <#cholesky>`__ +2. `numpy.linalg.det <#det>`__ +3. `numpy.linalg.dot <#dot>`__ +4. `numpy.linalg.eig <#eig>`__ +5. `numpy.linalg.inv <#inv>`__ +6. `numpy.linalg.norm <#norm>`__ +7. `numpy.linalg.trace <#trace>`__ + +cholesky +-------- ``numpy``: -https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html -A square matrix, provided that it is not singular, can be inverted by -calling the ``inv`` function that takes a single argument. The inversion -is based on successive elimination of elements in the lower left -triangle, and raises a ``ValueError`` exception, if the matrix turns out -to be singular (i.e., one of the diagonal entries is zero). +The function of the Cholesky decomposition takes a positive definite, +symmetric square matrix as its single argument, and returns the *square +root matrix* in the lower triangular form. If the input argument does +not fulfill the positivity or symmetry condition, a ``ValueError`` is +raised. .. code:: # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np - m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) - - print(linalg.inv(m)) + a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]]) + print('a: ', a) + print('\n' + '='*20 + '\nCholesky decomposition\n', np.linalg.cholesky(a)) .. parsed-literal:: - array([[-2.166666, 1.499999, -0.8333326, 1.0], - [1.666666, -3.333331, 1.666666, -4.768516e-08], - [0.1666672, 2.166666, -0.8333327, -1.0], - [-0.1666666, -0.3333334, 4.96705e-08, 0.5]], dtype=float) + a: array([[25.0, 15.0, -5.0], + [15.0, 18.0, 0.0], + [-5.0, 0.0, 11.0]], dtype=float) + + ==================== + Cholesky decomposition + array([[5.0, 0.0, 0.0], + [3.0, 3.0, 0.0], + [-1.0, 1.0, 3.0]], dtype=float) + -Computation expenses -~~~~~~~~~~~~~~~~~~~~ +det +--- -Note that the cost of inverting a matrix is approximately twice as many -floats (RAM), as the number of entries in the original matrix, and -approximately as many operations, as the number of entries. Here are a -couple of numbers: +``numpy``: +https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html + +The ``det`` function takes a square matrix as its single argument, and +calculates the determinant. The calculation is based on successive +elimination of the matrix elements, and the return value is a float, +even if the input array was of integer type. .. code:: # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np + + a = np.array([[1, 2], [3, 4]], dtype=np.uint8) + print(np.linalg.det(a)) + +.. parsed-literal:: + + -2.0 + + + +Benchmark +~~~~~~~~~ + +Since the routine for calculating the determinant is pretty much the +same as for finding the `inverse of a matrix <#inv>`__, the execution +times are similar: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np @timeit - def invert_matrix(m): - return linalg.inv(m) - - m = np.array([[1, 2,], [4, 5]]) - print('2 by 2 matrix:') - invert_matrix(m) - - m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) - print('\n4 by 4 matrix:') - invert_matrix(m) + def matrix_det(m): + return np.linalg.inv(m) m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]]) - print('\n8 by 8 matrix:') - invert_matrix(m) + + matrix_det(m) .. parsed-literal:: - 2 by 2 matrix: - execution time: 65 us - - 4 by 4 matrix: - execution time: 105 us - - 8 by 8 matrix: - execution time: 299 us + execution time: 294 us -The above-mentioned scaling is not obeyed strictly. The reason for the -discrepancy is that the function call is still the same for all three -cases: the input must be inspected, the output array must be created, -and so on. - dot --- @@ -109,15 +127,14 @@ below. # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np m = np.array([[1, 2, 3], [4, 5, 6], [7, 10, 9]], dtype=np.uint8) - n = linalg.inv(m) + n = np.linalg.inv(m) print("m:\n", m) print("\nm^-1:\n", n) # this should be the unit matrix - print("\nm*m^-1:\n", linalg.dot(m, n)) + print("\nm*m^-1:\n", np.linalg.dot(m, n)) .. parsed-literal:: @@ -147,14 +164,13 @@ right-hand-side matrix rows): # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np m = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.uint8) n = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype=np.uint8) print(m) print(n) - print(linalg.dot(m, n)) + print(np.linalg.dot(m, n)) .. parsed-literal:: @@ -170,61 +186,6 @@ right-hand-side matrix rows): -det ---- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html - -The ``det`` function takes a square matrix as its single argument, and -calculates the determinant. The calculation is based on successive -elimination of the matrix elements, and the return value is a float, -even if the input array was of integer type. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import linalg - - a = np.array([[1, 2], [3, 4]], dtype=np.uint8) - print(linalg.det(a)) - -.. parsed-literal:: - - -2.0 - - - -Benchmark -~~~~~~~~~ - -Since the routine for calculating the determinant is pretty much the -same as for finding the `inverse of a matrix <#inv>`__, the execution -times are similar: - -.. code:: - - # code to be run in micropython - - @timeit - def matrix_det(m): - return linalg.inv(m) - - m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], - [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], - [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], - [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]]) - - matrix_det(m) - -.. parsed-literal:: - - execution time: 294 us - - - eig --- @@ -242,11 +203,10 @@ stabilisation routines for robots. # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8) - x, y = linalg.eig(a) + x, y = np.linalg.eig(a) print('eigenvectors of a:\n', y) print('\neigenvalues of a:\n', x) @@ -311,12 +271,11 @@ least, be guessed based on the measurement below: # code to be run in micropython - import ulab as np - from ulab import linalg + from ulab import numpy as np @timeit def matrix_eig(a): - return linalg.eig(a) + return np.linalg.eig(a) a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8) @@ -328,43 +287,89 @@ least, be guessed based on the measurement below: -Cholesky decomposition ----------------------- +inv +--- ``numpy``: -https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html -``cholesky`` takes a positive definite, symmetric square matrix as its -single argument, and returns *square root matrix* in the lower -triangular form. If the input argument does not fulfill the positivity -or symmetry condition, a ``ValueError`` is raised. +A square matrix, provided that it is not singular, can be inverted by +calling the ``inv`` function that takes a single argument. The inversion +is based on successive elimination of elements in the lower left +triangle, and raises a ``ValueError`` exception, if the matrix turns out +to be singular (i.e., one of the diagonal entries is zero). .. code:: # code to be run in micropython - import ulab - from ulab import linalg + from ulab import numpy as np - a = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]]) - print('a: ', a) - print('\n' + '='*20 + '\nCholesky decomposition\n', linalg.cholesky(a)) + m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) + + print(np.linalg.inv(m)) .. parsed-literal:: - a: array([[25.0, 15.0, -5.0], - [15.0, 18.0, 0.0], - [-5.0, 0.0, 11.0]], dtype=float) - - ==================== - Cholesky decomposition - array([[5.0, 0.0, 0.0], - [3.0, 3.0, 0.0], - [-1.0, 1.0, 3.0]], dtype=float) + array([[-2.166666666666667, 1.500000000000001, -0.8333333333333337, 1.0], + [1.666666666666667, -3.333333333333335, 1.666666666666668, -0.0], + [0.1666666666666666, 2.166666666666668, -0.8333333333333337, -1.0], + [-0.1666666666666667, -0.3333333333333333, 0.0, 0.5]], dtype=float64) +Computation expenses +~~~~~~~~~~~~~~~~~~~~ + +Note that the cost of inverting a matrix is approximately twice as many +floats (RAM), as the number of entries in the original matrix, and +approximately as many operations, as the number of entries. Here are a +couple of numbers: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + @timeit + def invert_matrix(m): + return np.linalg.inv(m) + + m = np.array([[1, 2,], [4, 5]]) + print('2 by 2 matrix:') + invert_matrix(m) + + m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]]) + print('\n4 by 4 matrix:') + invert_matrix(m) + + m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], + [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], + [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], + [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]]) + print('\n8 by 8 matrix:') + invert_matrix(m) + +.. parsed-literal:: + + 2 by 2 matrix: + execution time: 65 us + + 4 by 4 matrix: + execution time: 105 us + + 8 by 8 matrix: + execution time: 299 us + + + +The above-mentioned scaling is not obeyed strictly. The reason for the +discrepancy is that the function call is still the same for all three +cases: the input must be inspected, the output array must be created, +and so on. + norm ---- @@ -378,14 +383,13 @@ The function takes a vector or matrix without options, and returns its # code to be run in micropython - import ulab - from ulab import linalg + from ulab import numpy as np - a = ulab.array([1, 2, 3, 4, 5]) - b = ulab.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + a = np.array([1, 2, 3, 4, 5]) + b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - print('norm of a:', linalg.norm(a)) - print('norm of b:', linalg.norm(b)) + print('norm of a:', np.linalg.norm(a)) + print('norm of b:', np.linalg.norm(b)) .. parsed-literal:: @@ -413,17 +417,16 @@ point trace. # code to be run in micropython - import ulab - from ulab import linalg + from ulab import numpy as np - a = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=ulab.int8) + a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.int8) print('a: ', a) - print('\ntrace of a: ', linalg.trace(a)) + print('\ntrace of a: ', np.linalg.trace(a)) - b = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=ulab.float) + b = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.float) print('='*20 + '\nb: ', b) - print('\ntrace of b: ', linalg.trace(b)) + print('\ntrace of b: ', np.linalg.trace(b)) .. parsed-literal:: diff --git a/docs/manual/source/ulab-vectorise.rst b/docs/manual/source/numpy-universal.rst similarity index 73% rename from docs/manual/source/ulab-vectorise.rst rename to docs/manual/source/numpy-universal.rst index 30e8cc3..f072eaa 100644 --- a/docs/manual/source/ulab-vectorise.rst +++ b/docs/manual/source/numpy-universal.rst @@ -1,64 +1,68 @@ +None Universal functions =================== -Standard mathematical functions are defined in the ``vector`` -sub-module, and can be calculated on any scalar, scalar-valued iterable -(ranges, lists, tuples containing numbers), and on ``ndarray``\ s -without having to change the call signature. In all cases the functions -return a new ``ndarray`` of typecode ``float`` (since these functions -usually generate float values, anyway). The functions execute faster -with ``ndarray`` arguments than with iterables, because the values of -the input vector can be extracted faster. +Standard mathematical functions can be calculated on any scalar, +scalar-valued iterable (ranges, lists, tuples containing numbers), and +on ``ndarray``\ s without having to change the call signature. In all +cases the functions return a new ``ndarray`` of typecode ``float`` +(since these functions usually generate float values, anyway). The +functions execute faster with ``ndarray`` arguments than with iterables, +because the values of the input vector can be extracted faster. At present, the following functions are supported: ``acos``, ``acosh``, ``arctan2``, ``around``, ``asin``, ``asinh``, ``atan``, ``arctan2``, ``atanh``, ``ceil``, ``cos``, ``degrees``, -``erf``, ``erfc``, ``exp``, ``expm1``, ``floor``, ``tgamma``, -``lgamma``, ``log``, ``log10``, ``log2``, ``radians``, ``sin``, -``sinh``, ``sqrt``, ``tan``, ``tanh``. +``exp``, ``expm1``, ``floor``, ``log``, ``log10``, ``log2``, +``radians``, ``sin``, ``sinh``, ``sqrt``, ``tan``, ``tanh``. These functions are applied element-wise to the arguments, thus, e.g., -the exponential of a matrix cannot be calculated in this way. The -functions can be invoked by importing the ``vector`` sub-module first. +the exponential of a matrix cannot be calculated in this way. .. code:: # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np a = range(9) b = np.array(a) # works with ranges, lists, tuples etc. print('a:\t', a) - print('exp(a):\t', vector.exp(a)) + print('exp(a):\t', np.exp(a)) # with 1D arrays - print('\nb:\t', b) - print('exp(b):\t', vector.exp(b)) + print('\n=============\nb:\n', b) + print('exp(b):\n', np.exp(b)) # as well as with matrices - c = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - print('\nc:\t', c) - print('exp(c):\t', vector.exp(c)) + c = np.array(range(9)).reshape((3, 3)) + print('\n=============\nc:\n', c) + print('exp(c):\n', np.exp(c)) .. parsed-literal:: a: range(0, 9) - exp(a): array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float) + exp(a): array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64) - b: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) - exp(b): array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float) + ============= + b: + array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + exp(b): + array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64) + + ============= + c: + array([[0.0, 1.0, 2.0], + [3.0, 4.0, 5.0], + [6.0, 7.0, 8.0]], dtype=float64) + exp(c): + array([[1.0, 2.718281828459045, 7.38905609893065], + [20.08553692318767, 54.59815003314424, 148.4131591025766], + [403.4287934927351, 1096.633158428459, 2980.957987041728]], dtype=float64) - c: array([[1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0]], dtype=float) - exp(c): array([[2.718282, 7.389056, 20.08554], - [54.59816, 148.4132, 403.4288], - [1096.633, 2980.958, 8103.084]], dtype=float) @@ -80,8 +84,7 @@ the resulting list to an ``ndarray``. # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np import math a = [0]*1000 @@ -89,7 +92,7 @@ the resulting list to an ``ndarray``. @timeit def timed_vector(iterable): - return vector.exp(iterable) + return np.exp(iterable) @timeit def timed_list(iterable): @@ -117,6 +120,85 @@ the resulting list to an ``ndarray``. +arctan2 +------- + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html + +The two-argument inverse tangent function is also part of the ``vector`` +sub-module. The function implements broadcasting as discussed in the +section on ``ndarray``\ s. Scalars (``micropython`` integers or floats) +are also allowed. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2.2, 33.33, 444.444]) + print('a:\n', a) + print('\narctan2(a, 1.0)\n', np.arctan2(a, 1.0)) + print('\narctan2(1.0, a)\n', np.arctan2(1.0, a)) + print('\narctan2(a, a): \n', np.arctan2(a, a)) + +.. parsed-literal:: + + a: + array([1.0, 2.2, 33.33, 444.444], dtype=float64) + + arctan2(a, 1.0) + array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float64) + + arctan2(1.0, a) + array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float64) + + arctan2(a, a): + array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float64) + + + + +around +------ + +``numpy``: +https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html + +``numpy``\ ’s ``around`` function can also be found in the ``vector`` +sub-module. The function implements the ``decimals`` keyword argument +with default value ``0``. The first argument must be an ``ndarray``. If +this is not the case, the function raises a ``TypeError`` exception. +Note that ``numpy`` accepts general iterables. The ``out`` keyword +argument known from ``numpy`` is not accepted. The function always +returns an ndarray of type ``mp_float_t``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + + a = np.array([1, 2.2, 33.33, 444.444]) + print('a:\t\t', a) + print('\ndecimals = 0\t', np.around(a, decimals=0)) + print('\ndecimals = 1\t', np.around(a, decimals=1)) + print('\ndecimals = -1\t', np.around(a, decimals=-1)) + +.. parsed-literal:: + + a: array([1.0, 2.2, 33.33, 444.444], dtype=float64) + + decimals = 0 array([1.0, 2.0, 33.0, 444.0], dtype=float64) + + decimals = 1 array([1.0, 2.2, 33.3, 444.4], dtype=float64) + + decimals = -1 array([0.0, 0.0, 30.0, 440.0], dtype=float64) + + + + Vectorising generic python functions ------------------------------------ @@ -143,13 +225,12 @@ single argument. # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np def f(x): return x*x - vf = vector.vectorize(f) + vf = np.vectorize(f) # calling with a scalar print('{:20}'.format('f on a scalar: '), vf(44.0)) @@ -163,9 +244,9 @@ single argument. .. parsed-literal:: - f on a scalar: array([1936.0], dtype=float) - f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float) - f on a list: array([4.0, 9.0, 16.0], dtype=float) + f on a scalar: array([1936.0], dtype=float64) + f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float64) + f on a list: array([4.0, 9.0, 16.0], dtype=float64) @@ -180,15 +261,14 @@ function object must be created. # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np l = [1, 2, 3, 4] def f(x): return x*x - vf1 = vector.vectorize(f, otypes=np.uint8) - vf2 = vector.vectorize(f, otypes=np.float) + vf1 = np.vectorize(f, otypes=np.uint8) + vf2 = np.vectorize(f, otypes=np.float) print('{:20}'.format('output is uint8: '), vf1(l)) print('{:20}'.format('output is float: '), vf2(l)) @@ -196,7 +276,7 @@ function object must be created. .. parsed-literal:: output is uint8: array([1, 4, 9, 16], dtype=uint8) - output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float) + output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float64) @@ -209,15 +289,14 @@ type, an exception will be raised: # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np int_list = [1, 2, 3, 4] float_list = [1.0, 2.0, 3.0, 4.0] def f(x): return x*x - vf = vector.vectorize(f, otypes=np.uint8) + vf = np.vectorize(f, otypes=np.uint8) print('{:20}'.format('integer list: '), vf(int_list)) # this will raise a TypeError exception @@ -258,13 +337,12 @@ the case, when the square is calculated entirely in ``ulab``. # code to be run in micropython - import ulab as np - from ulab import vector + from ulab import numpy as np def f(x): return x*x - vf = vector.vectorize(f) + vf = np.vectorize(f) @timeit def timed_vectorised_square(iterable): @@ -336,80 +414,3 @@ can still access the hardware in the meantime. So, e.g., return x*x is perfectly valid code. - -around ------- - -``numpy``: -https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html - -``numpy``\ ’s ``around`` function can also be found in the ``vector`` -sub-module. The function implements the ``decimals`` keyword argument -with default value ``0``. The first argument must be an ``ndarray``. If -this is not the case, the function raises a ``TypeError`` exception. -Note that ``numpy`` accepts general iterables. The ``out`` keyword -argument known from ``numpy`` is not accepted. The function always -returns an ndarray of type ``mp_float_t``. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import vector - - a = np.array([1, 2.2, 33.33, 444.444]) - print('a:\t\t', a) - print('\ndecimals = 0\t', vector.around(a, decimals=0)) - print('\ndecimals = 1\t', vector.around(a, decimals=1)) - print('\ndecimals = -1\t', vector.around(a, decimals=-1)) - -.. parsed-literal:: - - a: array([1.0, 2.2, 33.33, 444.444], dtype=float) - - decimals = 0 array([1.0, 2.0, 33.0, 444.0], dtype=float) - - decimals = 1 array([1.0, 2.2, 33.3, 444.4], dtype=float) - - decimals = -1 array([0.0, 0.0, 30.0, 440.0], dtype=float) - - - - -arctan2 -------- - -``numpy``: -https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html - -The two-argument inverse tangent function is also part of the ``vector`` -sub-module. The function implements broadcasting as discussed in the -section on ``ndarray``\ s. Scalars (``micropython`` integers or floats) -are also allowed. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import vector - - a = np.array([1, 2.2, 33.33, 444.444]) - print('a:\t\t', a) - print('\narctan2(a, 1.0)\t', vector.arctan2(a, 1.0)) - print('\narctan2(1.0, a)\t', vector.arctan2(1.0, a)) - print('\narctan2(a, a): \t', vector.arctan2(a, a)) - -.. parsed-literal:: - - a: array([1.0, 2.2, 33.33, 444.444], dtype=float) - - arctan2(a, 1.0) array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float) - - arctan2(1.0, a) array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float) - - arctan2(a, a): array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float) - - - diff --git a/docs/manual/source/scipy-optimize.rst b/docs/manual/source/scipy-optimize.rst new file mode 100644 index 0000000..fc21a79 --- /dev/null +++ b/docs/manual/source/scipy-optimize.rst @@ -0,0 +1,173 @@ +None +Optimize +======== + +Functions in the ``optimize`` module can be called by prepending them by +``scipy.optimize.``. The module defines the following three functions: + +1. `scipy.optimize.bisect <#bisect>`__ +2. `scipy.optimize.fmin <#fmin>`__ +3. `scipy.optimize.newton <#newton>`__ + +Note that routines that work with user-defined functions still have to +call the underlying ``python`` code, and therefore, gains in speed are +not as significant as with other vectorised operations. As a rule of +thumb, a factor of two can be expected, when compared to an optimised +``python`` implementation. + +bisect +------ + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html + +``bisect`` finds the root of a function of one variable using a simple +bisection routine. It takes three positional arguments, the function +itself, and two starting points. The function must have opposite signs +at the starting points. Returned is the position of the root. + +Two keyword arguments, ``xtol``, and ``maxiter`` can be supplied to +control the accuracy, and the number of bisections, respectively. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return x*x - 1 + + print(spy.optimize.bisect(f, 0, 4)) + + print('only 8 bisections: ', spy.optimize.bisect(f, 0, 4, maxiter=8)) + + print('with 0.1 accuracy: ', spy.optimize.bisect(f, 0, 4, xtol=0.1)) + +.. parsed-literal:: + + 0.9999997615814209 + only 8 bisections: 0.984375 + with 0.1 accuracy: 0.9375 + + + + +Performance +~~~~~~~~~~~ + +Since the ``bisect`` routine calls user-defined ``python`` functions, +the speed gain is only about a factor of two, if compared to a purely +``python`` implementation. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return (x-1)*(x-1) - 2.0 + + def bisect(f, a, b, xtol=2.4e-7, maxiter=100): + if f(a) * f(b) > 0: + raise ValueError + + rtb = a if f(a) < 0.0 else b + dx = b - a if f(a) < 0.0 else a - b + for i in range(maxiter): + dx *= 0.5 + x_mid = rtb + dx + mid_value = f(x_mid) + if mid_value < 0: + rtb = x_mid + if abs(dx) < xtol: + break + + return rtb + + @timeit + def bisect_scipy(f, a, b): + return spy.optimize.bisect(f, a, b) + + @timeit + def bisect_timed(f, a, b): + return bisect(f, a, b) + + print('bisect running in python') + bisect_timed(f, 3, 2) + + print('bisect running in C') + bisect_scipy(f, 3, 2) + +.. parsed-literal:: + + bisect running in python + execution time: 1270 us + bisect running in C + execution time: 642 us + + + +fmin +---- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html + +The ``fmin`` function finds the position of the minimum of a +user-defined function by using the downhill simplex method. Requires two +positional arguments, the function, and the initial value. Three keyword +arguments, ``xatol``, ``fatol``, and ``maxiter`` stipulate conditions +for stopping. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return (x-1)**2 - 1 + + print(spy.optimize.fmin(f, 3.0)) + print(spy.optimize.fmin(f, 3.0, xatol=0.1)) + +.. parsed-literal:: + + 0.9996093749999952 + 1.199999999999996 + + + + +newton +------ + +``scipy``:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html + +``newton`` finds a zero of a real, user-defined function using the +Newton-Raphson (or secant or Halley’s) method. The routine requires two +positional arguments, the function, and the initial value. Three keyword +arguments can be supplied to control the iteration. These are the +absolute and relative tolerances ``tol``, and ``rtol``, respectively, +and the number of iterations before stopping, ``maxiter``. The function +retuns a single scalar, the position of the root. + +.. code:: + + # code to be run in micropython + + from ulab import scipy as spy + + def f(x): + return x*x*x - 2.0 + + print(spy.optimize.newton(f, 3., tol=0.001, rtol=0.01)) + +.. parsed-literal:: + + 1.260135727246117 + + + diff --git a/docs/manual/source/scipy-signal.rst b/docs/manual/source/scipy-signal.rst new file mode 100644 index 0000000..dac772e --- /dev/null +++ b/docs/manual/source/scipy-signal.rst @@ -0,0 +1,135 @@ +None +Signal +====== + +Functions in the ``signal`` module can be called by prepending them by +``scipy.signal.``. The module defines the following two functions: + +1. `scipy.signal.sosfilt <#sosfilt>`__ +2. `scipy.signal.spectrogram <#spectrogram>`__ + +sosfilt +------- + +``scipy``: +https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sosfilt.html + +Filter data along one dimension using cascaded second-order sections. + +The function takes two positional arguments, ``sos``, the filter +segments of length 6, and the one-dimensional, uniformly sampled data +set to be filtered. Returns the filtered data, or the filtered data and +the final filter delays, if the ``zi`` keyword arguments is supplied. +The keyword argument must be a float ``ndarray`` of shape +``(n_sections, 2)``. If ``zi`` is not passed to the function, the +initial values are assumed to be 0. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] + y = spy.signal.sosfilt(sos, x) + print('y: ', y) + +.. parsed-literal:: + + y: array([0.0, 1.0, -4.0, 24.0, -104.0, 440.0, -1728.0, 6532.000000000001, -23848.0, 84864.0], dtype=float) + + + + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] + # initial conditions of the filter + zi = np.array([[1, 2], [3, 4]]) + + y, zf = spy.signal.sosfilt(sos, x, zi=zi) + print('y: ', y) + print('\n' + '='*40 + '\nzf: ', zf) + +.. parsed-literal:: + + y: array([4.0, -16.0, 63.00000000000001, -227.0, 802.9999999999999, -2751.0, 9271.000000000001, -30775.0, 101067.0, -328991.0000000001], dtype=float) + + ======================================== + zf: array([[37242.0, 74835.0], + [1026187.0, 1936542.0]], dtype=float) + + + + +spectrogram +----------- + +In addition to the Fourier transform and its inverse, ``ulab`` also +sports a function called ``spectrogram``, which returns the absolute +value of the Fourier transform. This could be used to find the dominant +spectral component in a time series. The arguments are treated in the +same way as in ``fft``, and ``ifft``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + a = spy.signal.spectrogram(y) + + print('original vector:\t', y) + print('\nspectrum:\t', a) + +.. parsed-literal:: + + original vector: array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893639], dtype=float64) + + spectrum: array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + + + +As such, ``spectrogram`` is really just a shorthand for +``np.sqrt(a*a + b*b)``: + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + x = np.linspace(0, 10, num=1024) + y = np.sin(x) + + a, b = np.fft.fft(y) + + print('\nspectrum calculated the hard way:\t', np.sqrt(a*a + b*b)) + + a = spy.signal.spectrogram(y) + + print('\nspectrum calculated the lazy way:\t', a) + +.. parsed-literal:: + + + spectrum calculated the hard way: array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + spectrum calculated the lazy way: array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64) + + + diff --git a/docs/manual/source/scipy-special.rst b/docs/manual/source/scipy-special.rst new file mode 100644 index 0000000..fc2c087 --- /dev/null +++ b/docs/manual/source/scipy-special.rst @@ -0,0 +1,44 @@ +None +Special functions +================= + +``scipy``\ ’s ``special`` module defines several functions that behave +as do the standard mathematical functions of the ``numpy``, i.e., they +can be called on any scalar, scalar-valued iterable (ranges, lists, +tuples containing numbers), and on ``ndarray``\ s without having to +change the call signature. In all cases the functions return a new +``ndarray`` of typecode ``float`` (since these functions usually +generate float values, anyway). + +At present, ``ulab``\ ’s ``special`` module contains the following +functions: + +``erf``, ``erfc``, ``gamma``, and ``gammaln``, and they can be called by +prepending them by ``scipy.special.``. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + from ulab import scipy as spy + + a = range(9) + b = np.array(a) + + print('a: ', a) + print(spy.special.erf(a)) + + print('\nb: ', b) + print(spy.special.erfc(b)) + +.. parsed-literal:: + + a: range(0, 9) + array([0.0, 0.8427007929497149, 0.9953222650189527, 0.9999779095030014, 0.9999999845827421, 1.0, 1.0, 1.0, 1.0], dtype=float64) + + b: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) + array([1.0, 0.1572992070502851, 0.004677734981047265, 2.209049699858544e-05, 1.541725790028002e-08, 1.537459794428035e-12, 2.151973671249892e-17, 4.183825607779414e-23, 1.122429717298293e-29], dtype=float64) + + + diff --git a/docs/manual/source/ulab-approx.rst b/docs/manual/source/ulab-approx.rst deleted file mode 100644 index 5f5e15d..0000000 --- a/docs/manual/source/ulab-approx.rst +++ /dev/null @@ -1,251 +0,0 @@ -Interpolation, root finding, and function minimisation -====================================================== - -The ``approx`` sub-module defines functions for interpolating numerical -data, and finding the roots and the minimum of arbitrary functions -defined in ``python``. Note that routines that work with user-defined -functions still have to call the underlying ``python`` code, and -therefore, gains in speed are not as significant as with other -vectorised operations. As a rule of thumb, a factor of two can be -expected, when compared to an optimised python implementation. - -interp ------- - -``numpy``: https://docs.scipy.org/doc/numpy/numpy.interp - -The ``interp`` function returns the linearly interpolated values of a -one-dimensional numerical array. It requires three positional -arguments,\ ``x``, at which the interpolated values are evaluated, -``xp``, the array of the independent variables of the data, and ``fp``, -the array of the dependent values of the data. ``xp`` must be a -monotonically increasing sequence of numbers. - -Two keyword arguments, ``left``, and ``right`` can also be supplied; -these determine the return values, if ``x < xp[0]``, and ``x > xp[-1]``, -respectively. If these arguments are not supplied, ``left``, and -``right`` default to ``fp[0]``, and ``fp[-1]``, respectively. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import approx - - x = ulab.array([1, 2, 3, 4, 5]) - xp = ulab.array([1, 2, 3, 4]) - fp = ulab.array([1, 2, 3, 5]) - x = x - 0.2 - print(x) - print(approx.interp(x, xp, fp)) - print(approx.interp(x, xp, fp, left=0.0)) - print(approx.interp(x, xp, fp, right=10.0)) - -.. parsed-literal:: - - array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float) - array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float) - array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float) - array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float) - - - - -newton ------- - -``scipy``:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html - -``newton`` finds a zero of a real, user-defined function using the -Newton-Raphson (or secant or Halley’s) method. The routine requires two -positional arguments, the function, and the initial value. Three keyword -arguments can be supplied to control the iteration. These are the -absolute and relative tolerances ``tol``, and ``rtol``, respectively, -and the number of iterations before stopping, ``maxiter``. The function -retuns a single scalar, the position of the root. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import approx - - def f(x): - return x*x*x - 2.0 - - print(approx.newton(f, 3., tol=0.001, rtol=0.01)) - -.. parsed-literal:: - - 1.260135727246117 - - - - -bisect ------- - -``scipy``: -https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html - -``bisect`` finds the root of a function of one variable using a simple -bisection routine. It takes three positional arguments, the function -itself, and two starting points. The function must have opposite signs -at the starting points. Returned is the position of the root. - -Two keyword arguments, ``xtol``, and ``maxiter`` can be supplied to -control the accuracy, and the number of bisections, respectively. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import approx - - def f(x): - return x*x - 1 - - print(approx.bisect(f, 0, 4)) - - print('only 8 bisections: ', approx.bisect(f, 0, 4, maxiter=8)) - - print('with 0.1 accuracy: ', approx.bisect(f, 0, 4, xtol=0.1)) - -.. parsed-literal:: - - 0.9999997615814209 - only 8 bisections: 0.984375 - with 0.1 accuracy: 0.9375 - - - - -Performance -~~~~~~~~~~~ - -Since the ``bisect`` routine calls user-defined ``python`` functions, -the speed gain is only about a factor of two, if compared to a purely -``python`` implementation. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import approx - - def f(x): - return (x-1)*(x-1) - 2.0 - - def bisect(f, a, b, xtol=2.4e-7, maxiter=100): - if f(a) * f(b) > 0: - raise ValueError - - rtb = a if f(a) < 0.0 else b - dx = b - a if f(a) < 0.0 else a - b - for i in range(maxiter): - dx *= 0.5 - x_mid = rtb + dx - mid_value = f(x_mid) - if mid_value < 0: - rtb = x_mid - if abs(dx) < xtol: - break - - return rtb - - @timeit - def bisect_approx(f, a, b): - return approx.bisect(f, a, b) - - @timeit - def bisect_timed(f, a, b): - return bisect(f, a, b) - - print('bisect running in python') - bisect_timed(f, 3, 2) - - print('bisect running in C') - bisect_approx(f, 3, 2) - -.. parsed-literal:: - - bisect running in python - execution time: 1270 us - bisect running in C - execution time: 642 us - - - -fmin ----- - -``scipy``: -https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html - -The ``fmin`` function finds the position of the minimum of a -user-defined function by using the downhill simplex method. Requires two -positional arguments, the function, and the initial value. Three keyword -arguments, ``xatol``, ``fatol``, and ``maxiter`` stipulate conditions -for stopping. - -.. code:: - - # code to be run in micropython - import ulab - from ulab import approx - - def f(x): - return (x-1)**2 - 1 - - print(approx.fmin(f, 3.0)) - print(approx.fmin(f, 3.0, xatol=0.1)) - -.. parsed-literal:: - - 0.9996093749999952 - 1.199999999999996 - - - - -trapz ------ - -``numpy``: -https://numpy.org/doc/stable/reference/generated/numpy.trapz.html - -The function takes one or two one-dimensional ``ndarray``\ s, and -integrates the dependent values (``y``) using the trapezoidal rule. If -the independent variable (``x``) is given, that is taken as the sample -points corresponding to ``y``. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import approx - - x = ulab.linspace(0, 9, num=10) - y = x*x - - print('x: ', x) - print('y: ', y) - print('============================') - print('integral of y: ', approx.trapz(y)) - print('integral of y at x: ', approx.trapz(y, x=x)) - -.. parsed-literal:: - - x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float) - y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float) - ============================ - integral of y: 244.5 - integral of y at x: 244.5 - - - diff --git a/docs/manual/source/ulab-compare.rst b/docs/manual/source/ulab-compare.rst deleted file mode 100644 index 1ffbc2b..0000000 --- a/docs/manual/source/ulab-compare.rst +++ /dev/null @@ -1,149 +0,0 @@ -Comparison of arrays -==================== - -Functions in the ``compare`` module can be called by importing the -sub-module first. - -equal, not_equal ----------------- - -``numpy``: -https://numpy.org/doc/stable/reference/generated/numpy.equal.html - -``numpy``: -https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html - -In ``micropython``, equality of arrays or scalars can be established by -utilising the ``==``, ``!=``, ``<``, ``>``, ``<=``, or ``=>`` binary -operators. In ``circuitpython``, ``==`` and ``!=`` will produce -unexpected results. In order to avoid this discrepancy, and to maintain -compatibility with ``numpy``, ``ulab`` implements the ``equal`` and -``not_equal`` operators that return the same results, irrespective of -the ``python`` implementation. - -These two functions take two ``ndarray``\ s, or scalars as their -arguments. No keyword arguments are implemented. - -.. code:: - - # code to be run in micropython - - import ulab as np - - a = np.array(range(9)) - b = np.zeros(9) - - print('a: ', a) - print('b: ', b) - print('\na == b: ', np.compare.equal(a, b)) - print('a != b: ', np.compare.not_equal(a, b)) - - # comparison with scalars - print('a == 2: ', np.compare.equal(a, 2)) - -.. parsed-literal:: - - a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) - b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float) - - a == b: [True, False, False, False, False, False, False, False, False] - a != b: [False, True, True, True, True, True, True, True, True] - a == 2: [False, False, True, False, False, False, False, False, False] - - - - -minimum -------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html - -Returns the minimum of two arrays, or two scalars, or an array, and a -scalar. Partial broadcasting is implemented. If the arrays are of -different ``dtype``, the output is upcast as in `Binary -operators <#Binary-operators>`__. If both inputs are scalars, a scalar -is returned. Only positional arguments are implemented. - -maximum -------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html - -Returns the maximum of two arrays, or two scalars, or an array, and a -scalar. Partial broadcasting is implemented. If the arrays are of -different ``dtype``, the output is upcast as in `Binary -operators <#Binary-operators>`__. If both inputs are scalars, a scalar -is returned. Only positional arguments are implemented. - -.. code:: - - # code to be run in micropython - - import ulab - - a = ulab.array([1, 2, 3, 4, 5], dtype=ulab.uint8) - b = ulab.array([5, 4, 3, 2, 1], dtype=ulab.float) - print('minimum of a, and b:') - print(ulab.compare.minimum(a, b)) - - print('\nmaximum of a, and b:') - print(ulab.compare.maximum(a, b)) - - print('\nmaximum of 1, and 5.5:') - print(ulab.compare.maximum(1, 5.5)) - -.. parsed-literal:: - - minimum of a, and b: - array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float) - - maximum of a, and b: - array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float) - - maximum of 1, and 5.5: - 5.5 - - - - -clip ----- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html - -Clips an array, i.e., values that are outside of an interval are clipped -to the interval edges. The function is equivalent to -``maximum(a_min, minimum(a, a_max))``. or two scalars, hence partial -broadcasting takes place exactly as in `minimum <#minimum>`__. If the -arrays are of different ``dtype``, the output is upcast as in `Binary -operators <#Binary-operators>`__. - -.. code:: - - # code to be run in micropython - - import ulab - - a = ulab.array(range(9), dtype=ulab.uint8) - print('a:\t\t', a) - print('clipped:\t', ulab.compare.clip(a, 3, 7)) - - b = 3 * ulab.ones(len(a), dtype=ulab.float) - print('\na:\t\t', a) - print('b:\t\t', b) - print('clipped:\t', ulab.compare.clip(a, b, 7)) - -.. parsed-literal:: - - a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) - clipped: array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8) - - a: array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) - b: array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float) - clipped: array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float) - - - diff --git a/docs/manual/source/ulab-filter.rst b/docs/manual/source/ulab-filter.rst deleted file mode 100644 index d22c475..0000000 --- a/docs/manual/source/ulab-filter.rst +++ /dev/null @@ -1,99 +0,0 @@ -Filter routines -=============== - -Functions in the ``filter`` module can be called by importing the -sub-module first. - -convolve --------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html - -Returns the discrete, linear convolution of two one-dimensional -sequences. - -Only the ``full`` mode is supported, and the ``mode`` named parameter is -not accepted. Note that all other modes can be had by slicing a ``full`` -result. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import filter - - x = np.array((1,2,3)) - y = np.array((1,10,100,1000)) - - print(filter.convolve(x, y)) - -.. parsed-literal:: - - array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float) - - - - -sosfilt -------- - -``scipy``: -https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.sosfilt.html - -Filter data along one dimension using cascaded second-order sections. - -The function takes two positional arguments, ``sos``, the filter -segments of length 6, and the one-dimensional, uniformly sample data set -to be filtered. Returns the filtered data, or the filtered data and the -final filter delays, if the ``zi`` keyword arguments is supplied. The -keyword argument be a float ``ndarray`` of shape ``(n_sections, 2)``. If -``zi`` is not passed to the function, the initial values are assumed to -be 0. - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import filter as filter - - x = ulab.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] - y = filter.sosfilt(sos, x) - print('y: ', y) - -.. parsed-literal:: - - y: array([0.0, 1.0, -4.0, 24.0, -104.0, 440.0, -1728.0, 6532.000000000001, -23848.0, 84864.0], dtype=float) - - - - -.. code:: - - # code to be run in micropython - - import ulab - from ulab import filter as filter - - x = ulab.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]] - # initial conditions of the filter - zi = ulab.array([[1, 2], [3, 4]]) - - y, zf = filter.sosfilt(sos, x, zi=zi) - print('y: ', y) - print('\n' + '='*40 + '\nzf: ', zf) - -.. parsed-literal:: - - y: array([4.0, -16.0, 63.00000000000001, -227.0, 802.9999999999999, -2751.0, 9271.000000000001, -30775.0, 101067.0, -328991.0000000001], dtype=float) - - ======================================== - zf: array([[37242.0, 74835.0], - [1026187.0, 1936542.0]], dtype=float) - - - diff --git a/docs/manual/source/ulab-intro.rst b/docs/manual/source/ulab-intro.rst index b7e77d8..9ea663f 100644 --- a/docs/manual/source/ulab-intro.rst +++ b/docs/manual/source/ulab-intro.rst @@ -1,3 +1,4 @@ +None Introduction ============ @@ -6,15 +7,16 @@ Enter ulab ``ulab`` is a ``numpy``-like module for ``micropython`` and its derivatives, meant to simplify and speed up common mathematical -operations on arrays. ``ulab`` implements a small subset of ``numpy``. -The functions were chosen such that they might be useful in the context -of a microcontroller. However, the project is a living one, and -suggestions for new functions are always welcome. +operations on arrays. ``ulab`` implements a small subset of ``numpy`` +and ``scipy``. The functions were chosen such that they might be useful +in the context of a microcontroller. However, the project is a living +one, and suggestions for new features are always welcome. This document discusses how you can use the library, starting from building your own firmware, through questions like what affects the firmware size, what are the trade-offs, and what are the most important -differences to ``numpy``. The document is organised as follows: +differences to ``numpy`` and ``scipy``, respectively. The document is +organised as follows: The chapter after this one helps you with firmware customisation. @@ -90,11 +92,11 @@ The main points of ``ulab`` are - polynomial fits to numerical data, and evaluation of polynomials - fast Fourier transforms - filtering of data (convolution and second-order filters) -- function minimasation, fitting, and numerical approximation routines +- function minimisation, fitting, and numerical approximation routines ``ulab`` implements close to a hundred functions and array methods. At -the time of writing this manual (for version 1.0.0), the library adds -approximately 100 kB of extra compiled code to the ``micropython`` +the time of writing this manual (for version 2.1.0), the library adds +approximately 120 kB of extra compiled code to the ``micropython`` (pyboard.v.11) firmware. However, if you are tight with flash space, you can easily shave tens of kB off the firmware. In fact, if only a small sub-set of functions are needed, you can get away with less than 10 kB @@ -120,8 +122,8 @@ please, raise a `ulab issue <#https://github.com/v923z/micropython-ulab/issues>`__ on github, so that the community can profit from your experiences. -Even better, if you find the project useful, and think that it could be -made better, faster, tighter, and shinier, please, consider +Even better, if you find the project to be useful, and think that it +could be made better, faster, tighter, and shinier, please, consider contributing, and issue a pull request with the implementation of your improvements and new features. ``ulab`` can only become successful, if it offers what the community needs. @@ -144,10 +146,9 @@ generous and enthusiastic support of Jeff Epler from `Adafruit Industries `__. There are, however, a couple of instances, where the usage in the two -environments is different at the python level. These are how the -packages can be imported, and how the class properties can be accessed. -We will point out the differences and possible workarounds at the -relevant places in this document. +environments is different at the python level. These are how the class +properties can be accessed. We will point out the differences and +possible workarounds at the relevant places in this document. Customising ``ulab`` ==================== @@ -157,16 +158,13 @@ conception, which also means that it might no longer fit on the microcontroller of your choice. There are, however, a couple of ways of customising the firmware, and thereby reducing its size. -All options are listed in a single header file, +All ``ulab`` options are listed in a single header file, `ulab.h `__, which contains pre-processor flags for each feature that can be fine-tuned. The first couple of lines of the file look like this .. code:: c - #ifndef __ULAB__ - #define __ULAB__ - // The pre-processor constants in this file determine how ulab behaves: // // - how many dimensions ulab can handle @@ -178,51 +176,59 @@ fine-tuned. The first couple of lines of the file look like this // A considerable amount of flash space can be saved by removing (setting // the corresponding constants to 0) the unnecessary functions and features. - // Setting this variable to 1 produces numpy-compatible firmware, - // i.e., functions can be called at the top level, - // without having to import the sub-modules (linalg and fft are exceptions, - // since those must be imported even in numpy) - #define ULAB_NUMPY_COMPATIBILITY (1) + // Determines, whether scipy is defined in ulab. The sub-modules and functions + // of scipy have to be defined separately + #define ULAB_HAS_SCIPY (1) // The maximum number of dimensions the firmware should be able to support // Possible values lie between 1, and 4, inclusive - #define ULAB_MAX_DIMS 2 + #define ULAB_MAX_DIMS 2 // By setting this constant to 1, iteration over array dimensions will be implemented // as a function (ndarray_rewind_array), instead of writing out the loops in macros // This reduces firmware size at the expense of speed - #define ULAB_HAS_FUNCTION_ITERATOR (0) + #define ULAB_HAS_FUNCTION_ITERATOR (0) // If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function // This option saves approx. 250 bytes of flash space - #define NDARRAY_IS_ITERABLE (1) + #define NDARRAY_IS_ITERABLE (1) // Slicing can be switched off by setting this variable to 0 - #define NDARRAY_IS_SLICEABLE (1) + #define NDARRAY_IS_SLICEABLE (1) // The default threshold for pretty printing. These variables can be overwritten // at run-time via the set_printoptions() function - #define ULAB_HAS_PRINTOPTIONS (1) - #define NDARRAY_PRINT_THRESHOLD 10 - #define NDARRAY_PRINT_EDGEITEMS 3 + #define ULAB_HAS_PRINTOPTIONS (1) + #define NDARRAY_PRINT_THRESHOLD 10 + #define NDARRAY_PRINT_EDGEITEMS 3 - // determines, whether pi, and e are defined in ulab itself - #define ULAB_HAS_MATH_CONSTANTS (1) - - // determines, whether the ndinfo function is available - #define ULAB_HAS_NDINFO (1) + // determines, whether the dtype is an object, or simply a character + // the object implementation is numpythonic, but requires more space + #define ULAB_HAS_DTYPE_OBJECT (0) // the ndarray binary operators #define NDARRAY_HAS_BINARY_OPS (1) + + // Firmware size can be reduced at the expense of speed by using function + // pointers in iterations. For each operator, he function pointer saves around + // 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case. + + #define NDARRAY_BINARY_USES_FUN_POINTER (0) + #define NDARRAY_HAS_BINARY_OP_ADD (1) #define NDARRAY_HAS_BINARY_OP_EQUAL (1) #define NDARRAY_HAS_BINARY_OP_LESS (1) #define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1) #define NDARRAY_HAS_BINARY_OP_MORE (1) #define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1) - ... + #define NDARRAY_HAS_BINARY_OP_MULTIPLY (1) + #define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1) + #define NDARRAY_HAS_BINARY_OP_POWER (1) + #define NDARRAY_HAS_BINARY_OP_SUBTRACT (1) + #define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1) + ... -The meaning of flags with names ``_HAS_`` should obvious, so we will +The meaning of flags with names ``_HAS_`` should be obvious, so we will just explain the other options. To see how much you can gain by un-setting the functions that you do not @@ -234,76 +240,60 @@ everything else, you get away with less than 5 kB extra. Compatibility with numpy ------------------------ -Working with sub-modules -~~~~~~~~~~~~~~~~~~~~~~~~ - -The functions implemented in ``ulab`` are organised in sub-modules at -the C level. This modularity is eleveted to python, if - -.. code:: c - - #define ULAB_NUMPY_COMPATIBILITY (0) - -meaning that if you want to access a particular function, you would have -to import the corresponding sub-module first. +The functions implemented in ``ulab`` are organised in three sub-modules +at the C level, namely, ``numpy``, ``scipy``, and ``user``. This +modularity is elevated to ``python``, meaning that in order to use +functions that are part of ``numpy``, you have to import ``numpy`` as .. code:: python - import ulab - from ulab import poly + from ulab import numpy as np - x = ulab.array([4, 5, 6]) - p = ulab.array([1, 2, 3]) - poly.polyval(p, x) + x = np.array([4, 5, 6]) + p = np.array([1, 2, 3]) + np.polyval(p, x) -The idea of such grouping of functions and methods at the python level -is to provide a means for granularity. At first, having to import -everything in this way might appear to be overly complicated, but there -is a very good reason behind all this: you can find out at the time of -importing, whether a function or sub-module is part of your ``ulab`` -firmware, or not. The alternative, namely, that you do not have to -import anything beyond ``ulab``, could prove catastrophic: you would -learn only at run time (at the moment of calling the function in your -code) that a particular function is not in the firmware, and that is -most probably too late. - -Generating numpy-compatible firmware -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``circuitpython`` follows the approach above, setting the -``ULAB_NUMPY_COMPATIBILITY`` flag to 0. On the other hand, if you want -to generate truly ``numpy``-compatible firmware, you can set - -.. code:: c - - #define ULAB_NUMPY_COMPATIBILITY (1) - -If ``ULAB_NUMPY_COMPATIBILITY`` equals 1, functions will be bound at the -top level, meaning that the example above now would look like +There are a couple of exceptions to this rule, namely ``fft``, and +``linalg``, which are sub-modules even in ``numpy``, thus you have to +write them out as .. code:: python - import ulab as numpy + from ulab import numpy as np - x = numpy.array([4, 5, 6]) - p = numpy.array([1, 2, 3]) - numpy.polyval(p, x) + A = np.array([1, 2, 3, 4]).reshape() + np.linalg.trace(A) -There are two exceptions to this rule, namely ``fft``, and ``linalg``, -which are sub-modules even in ``numpy``, thus you have to write them out -as +Some of the functions in ``ulab`` are re-implementations of ``scipy`` +functions, and they are to be imported as .. code:: python - import ulab - from ulab import linalg + from ulab import numpy as np + from ulab import scipy as spy - A = ulab.array([1, 2, 3, 4]).reshape() - linalg.trace(A) -We should also note that the ``numpy``-compatible firmware is a couple -of hundred bytes smaller than the one with sub-modules, because defining -the sub-modules requires some space. + x = np.array([1, 2, 3]) + spy.special.erf(x) + +``numpy``-compatibility has an enormous benefit : namely, by +``try``\ ing to ``import``, we can guarantee that the same, unmodified +code runs in ``CPython``, as in ``micropython``. The following snippet +is platform-independent, thus, the ``python`` code can be tested and +debugged on a computer before loading it onto the microcontroller. + +.. code:: python + + + try: + from ulab import numpy as np + from ulab import scipy as spy + except ImportError: + import numpy as np + import scipy as spy + + x = np.array([1, 2, 3]) + spy.special.erf(x) The impact of dimensionality ---------------------------- @@ -313,7 +303,7 @@ Reducing the number of dimensions ``ulab`` supports tensors of rank four, but this is expensive in terms of flash: with all available functions and options, the library adds -around 100 kB to the flash. However, if such high dimensions are not +around 100 kB to the firmware. However, if such high dimensions are not required, significant reductions in size can be gotten by changing the value of @@ -401,7 +391,7 @@ number of data types. As an example, the innocent-looking expression .. code:: python - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) @@ -423,13 +413,13 @@ version can be found be querying the ``__version__`` string. # code to be run in micropython - import ulab as np + import ulab - print('you are running ulab version', np.__version__) + print('you are running ulab version', ulab.__version__) .. parsed-literal:: - you are running ulab version 0.99.0-2D-numpy + you are running ulab version 2.1.0-2D @@ -443,18 +433,41 @@ implemented. ``2D`` tells us that the particular firmware supports tensors of rank 2 (defined by ``ULAB_MAX_DIMS`` in -`ulab.h `__), -and the string ``numpy`` means that the firmware is ``numpy``-compatible -in the sense explained above. Otherwise, you would find ``cpy``, i.e., -firmware that conforms to ``circuitpython``\ ’s conventions. +`ulab.h `__). If you find a bug, please, include the version string in your report! +Should you need the numerical value of ``ULAB_MAX_DIMS``, you can get it +from the version string in the following way: + +.. code:: + + # code to be run in micropython + + import ulab + + version = ulab.__version__ + version_dims = version.split('-')[1] + version_num = int(version_dims.replace('D', '')) + + print('version string: ', version) + print('version dimensions: ', version_dims) + print('numerical value of dimensions: ', version_num) + +.. parsed-literal:: + + version string: 2.1.0-2D + version dimensions: 2D + numerical value of dimensions: 2 + + + + Finding out what your firmware supports --------------------------------------- ``ulab`` implements a number of array operators and functions, but this -doesn’t mean that all of these functions and methods are actually +does not mean that all of these functions and methods are actually compiled into the firmware. You can fine-tune your firmware by setting/unsetting any of the ``_HAS_`` constants in `ulab.h `__. @@ -471,24 +484,46 @@ firmware is calling ``dir`` with ``ulab`` as its argument. # code to be run in micropython - import ulab as np + from ulab import numpy as np + from ulab import scipy as spy - print('class-level functions: \n', dir(np)) + + print('===== constants, functions, and modules of numpy =====\n\n', dir(np)) # since fft and linalg are sub-modules, print them separately - print('\nfunctions included in the fft module: \n', dir(np.fft)) - print('\nfunctions included in the linalg module: \n', dir(np.linalg)) + print('\nfunctions included in the fft module:\n', dir(np.fft)) + print('\nfunctions included in the linalg module:\n', dir(np.linalg)) + + print('\n\n===== modules of scipy =====\n\n', dir(spy)) + print('\nfunctions included in the optimize module:\n', dir(spy.optimize)) + print('\nfunctions included in the signal module:\n', dir(spy.signal)) + print('\nfunctions included in the special module:\n', dir(spy.special)) .. parsed-literal:: - class-level functions: - ['__class__', '__name__', 'bool', 'sort', 'sum', '__version__', 'acos', 'acosh', 'arange', 'arctan2', 'argmax', 'argmin', 'argsort', 'around', 'array', 'asin', 'asinh', 'atan', 'atanh', 'bisect', 'ceil', 'clip', 'concatenate', 'convolve', 'cos', 'cosh', 'cross', 'degrees', 'diff', 'e', 'equal', 'erf', 'erfc', 'exp', 'expm1', 'eye', 'fft', 'flip', 'float', 'floor', 'fmin', 'full', 'gamma', 'get_printoptions', 'int16', 'int8', 'interp', 'lgamma', 'linalg', 'linspace', 'log', 'log10', 'log2', 'logspace', 'max', 'maximum', 'mean', 'min', 'minimum', 'ndinfo', 'newton', 'not_equal', 'ones', 'pi', 'polyfit', 'polyval', 'radians', 'roll', 'set_printoptions', 'sin', 'sinh', 'sosfilt', 'sqrt', 'std', 'tan', 'tanh', 'trapz', 'uint16', 'uint8', 'user', 'vectorize', 'zeros'] + ===== constants, functions, and modules of numpy ===== - functions included in the fft module: - ['__class__', '__name__', 'fft', 'ifft', 'spectrogram'] + ['__class__', '__name__', 'bool', 'sort', 'sum', 'acos', 'acosh', 'arange', 'arctan2', 'argmax', 'argmin', 'argsort', 'around', 'array', 'asin', 'asinh', 'atan', 'atanh', 'ceil', 'clip', 'concatenate', 'convolve', 'cos', 'cosh', 'cross', 'degrees', 'diag', 'diff', 'e', 'equal', 'exp', 'expm1', 'eye', 'fft', 'flip', 'float', 'floor', 'frombuffer', 'full', 'get_printoptions', 'inf', 'int16', 'int8', 'interp', 'linalg', 'linspace', 'log', 'log10', 'log2', 'logspace', 'max', 'maximum', 'mean', 'median', 'min', 'minimum', 'nan', 'ndinfo', 'not_equal', 'ones', 'pi', 'polyfit', 'polyval', 'radians', 'roll', 'set_printoptions', 'sin', 'sinh', 'sqrt', 'std', 'tan', 'tanh', 'trapz', 'uint16', 'uint8', 'vectorize', 'zeros'] - functions included in the linalg module: - ['__class__', '__name__', 'cholesky', 'det', 'dot', 'eig', 'inv', 'norm', 'size', 'trace'] + functions included in the fft module: + ['__class__', '__name__', 'fft', 'ifft'] + + functions included in the linalg module: + ['__class__', '__name__', 'cholesky', 'det', 'dot', 'eig', 'inv', 'norm', 'trace'] + + + ===== modules of scipy ===== + + ['__class__', '__name__', 'optimize', 'signal', 'special'] + + functions included in the optimize module: + ['__class__', '__name__', 'bisect', 'fmin', 'newton'] + + functions included in the signal module: + ['__class__', '__name__', 'sosfilt', 'spectrogram'] + + functions included in the special module: + ['__class__', '__name__', 'erf', 'erfc', 'gamma', 'gammaln'] @@ -498,20 +533,20 @@ Methods included in the firmware The ``dir`` function applied to the module or its sub-modules gives information on what the module and sub-modules include, but is not -enough to find out which methods the ``ndarray`` supports. We can list -the methods by calling ``dir`` with the ``array`` object itself: +enough to find out which methods the ``ndarray`` class supports. We can +list the methods by calling ``dir`` with the ``array`` object itself: .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np print(dir(np.array)) .. parsed-literal:: - ['__class__', '__name__', 'copy', '__bases__', '__dict__', 'flatten', 'itemsize', 'reshape', 'shape', 'size', 'strides', 'tobytes', 'transpose'] + ['__class__', '__name__', 'copy', 'sort', '__bases__', '__dict__', 'dtype', 'flatten', 'itemsize', 'reshape', 'shape', 'size', 'strides', 'tobytes', 'transpose'] @@ -519,15 +554,15 @@ the methods by calling ``dir`` with the ``array`` object itself: Operators included in the firmware ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A list of operators cannot be generated as shown above. If you need to -find out, whether, e.g., the ``**`` operator is supported by the +A list of operators cannot be generated as shown above. If you really +need to find out, whether, e.g., the ``**`` operator is supported by the firmware, you have to ``try`` it: .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) @@ -543,3 +578,17 @@ firmware, you have to ``try`` it: + +The exception above would be raised, if the firmware was compiled with +the + +.. code:: c + + #define NDARRAY_HAS_BINARY_OP_POWER (0) + +definition. + +.. code:: + + # code to be run in CPython + diff --git a/docs/manual/source/ulab-ndarray.rst b/docs/manual/source/ulab-ndarray.rst index 28d511d..699bd87 100644 --- a/docs/manual/source/ulab-ndarray.rst +++ b/docs/manual/source/ulab-ndarray.rst @@ -1,3 +1,4 @@ +None ndarray, the basic container ============================ @@ -28,9 +29,10 @@ the smallest reasonable one. Five such types are defined, namely ``float``, which occupies four or eight bytes per datum. The precision/size of the ``float`` type depends on the definition of ``mp_float_t``. Some platforms, e.g., the PYBD, implement ``double``\ s, -but some, e.g., the pyboard.v.11, don’t. You can find out, what type of +but some, e.g., the pyboard.v.11, do not. You can find out, what type of float your particular platform implements by looking at the output of -the `.itemsize <#.itemsize>`__ class property. +the `.itemsize <#.itemsize>`__ class property, or looking at the exact +``dtype``, when you print out an array. In addition to the five above-mentioned numerical types, it is also possible to define Boolean arrays, which can be used in the indexing of @@ -68,7 +70,7 @@ the printout, you should call the dedicated ``shape``, ``strides``, or # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(5), dtype=np.float) b = np.array(range(25), dtype=np.uint8).reshape((5, 5)) @@ -82,7 +84,7 @@ the printout, you should call the dedicated ``shape``, ``strides``, or shape: (5,) strides: (8,) itemsize: 8 - data pointer: 0x7f2bafabd220 + data pointer: 0x7f8f6fa2e240 type: float @@ -90,7 +92,7 @@ the printout, you should call the dedicated ``shape``, ``strides``, or shape: (5, 5) strides: (5, 1) itemsize: 1 - data pointer: 0x7f2bafabd3a0 + data pointer: 0x7f8f6fa2e2e0 type: uint8 @@ -121,7 +123,7 @@ default. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = [1, 2, 3, 4, 5, 6, 7, 8] b = np.array(a) @@ -140,11 +142,11 @@ default. .. parsed-literal:: a: [1, 2, 3, 4, 5, 6, 7, 8] - b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) + b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) c: array([[0, 1, 2, 3, 4], - [20, 21, 22, 23, 24], - [44, 55, 66, 77, 88]], dtype=uint8) + [20, 21, 22, 23, 24], + [44, 55, 66, 77, 88]], dtype=uint8) Traceback (most recent call last): File "/dev/shm/micropython.py", line 15, in @@ -168,7 +170,7 @@ copying. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = [1, 2, 3, 4, 5, 6, 7, 8] b = np.array(a) @@ -184,9 +186,9 @@ copying. a: [1, 2, 3, 4, 5, 6, 7, 8] - b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) + b: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) - c: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) + c: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64) d: array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) @@ -201,7 +203,7 @@ take place, except, when the output type is specifically supplied. I.e., # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(5), dtype=np.uint8) b = np.array(a) @@ -212,20 +214,20 @@ take place, except, when the output type is specifically supplied. I.e., a: array([0, 1, 2, 3, 4], dtype=uint8) - b: array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float) + b: array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64) will iterate over the elements in ``a``, since in the assignment -``b = np.array(a)`` no output type was given, therefore, ``float`` was +``b = np.array(a)``, no output type was given, therefore, ``float`` was assumed. On the other hand, .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(5), dtype=np.uint8) b = np.array(a, dtype=np.uint8) @@ -249,8 +251,6 @@ Array initialisation functions ------------------------------ There are seven functions that can be used for initialising an array. -These are bound to ``ulab`` itself at the top level, i.e., no module has -to be imported for the function invocations. arange ~~~~~~ @@ -266,19 +266,19 @@ keyword argument. # code to be run in micropython - import ulab + from ulab import numpy as np - print(ulab.arange(10)) - print(ulab.arange(2, 10)) - print(ulab.arange(2, 10, 3)) - print(ulab.arange(2, 10, 3, dtype=ulab.float)) + print(np.arange(10)) + print(np.arange(2, 10)) + print(np.arange(2, 10, 3)) + print(np.arange(2, 10, 3, dtype=np.float)) .. parsed-literal:: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int16) array([2, 3, 4, 5, 6, 7, 8, 9], dtype=int16) array([2, 5, 8], dtype=int16) - array([2.0, 5.0, 8.0], dtype=float) + array([2.0, 5.0, 8.0], dtype=float64) @@ -290,13 +290,14 @@ concatenate https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html The function joins a sequence of arrays, if they are compatible in -shape, if all shapes except the one along the joining axis are equal. +shape, i.e., if all shapes except the one along the joining axis are +equal. .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(25), dtype=np.uint8).reshape((5, 5)) b = np.array(range(15), dtype=np.uint8).reshape((3, 5)) @@ -307,13 +308,13 @@ shape, if all shapes except the one along the joining axis are equal. .. parsed-literal:: array([[0, 1, 2, 3, 4], - [5, 6, 7, 8, 9], - [10, 11, 12, 13, 14], - [15, 16, 17, 18, 19], - [20, 21, 22, 23, 24], - [0, 1, 2, 3, 4], - [5, 6, 7, 8, 9], - [10, 11, 12, 13, 14]], dtype=uint8) + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24], + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14]], dtype=uint8) @@ -329,7 +330,7 @@ concatenation works: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(25), dtype=np.uint8).reshape((5, 5)) b = np.array(range(15), dtype=np.float).reshape((5, 3)) @@ -342,22 +343,22 @@ concatenation works: .. parsed-literal:: a: array([[0, 1, 2, 3, 4], - [5, 6, 7, 8, 9], - [10, 11, 12, 13, 14], - [15, 16, 17, 18, 19], - [20, 21, 22, 23, 24]], dtype=uint8) + [5, 6, 7, 8, 9], + [10, 11, 12, 13, 14], + [15, 16, 17, 18, 19], + [20, 21, 22, 23, 24]], dtype=uint8) ==================== d: array([[1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [10, 11, 12], - [13, 14, 15]], dtype=uint8) + [4, 5, 6], + [7, 8, 9], + [10, 11, 12], + [13, 14, 15]], dtype=uint8) ==================== c: array([[1, 2, 3, 0, 1, 2, 3, 4], - [4, 5, 6, 5, 6, 7, 8, 9], - [7, 8, 9, 10, 11, 12, 13, 14], - [10, 11, 12, 15, 16, 17, 18, 19], - [13, 14, 15, 20, 21, 22, 23, 24]], dtype=uint8) + [4, 5, 6, 5, 6, 7, 8, 9], + [7, 8, 9, 10, 11, 12, 13, 14], + [10, 11, 12, 15, 16, 17, 18, 19], + [13, 14, 15, 20, 21, 22, 23, 24]], dtype=uint8) @@ -387,7 +388,7 @@ With a single argument # code to be run in micropython - import ulab as np + from ulab import numpy as np print(np.eye(5)) @@ -397,7 +398,7 @@ With a single argument [0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 1.0]], dtype=float) + [0.0, 0.0, 0.0, 0.0, 1.0]], dtype=float64) @@ -409,18 +410,16 @@ Specifying the dimensions of the matrix # code to be run in micropython - import ulab as np + from ulab import numpy as np print(np.eye(4, M=6, k=-1, dtype=np.int16)) .. parsed-literal:: - array([[0, 0, 0, 0], - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - [0, 0, 0, 0]], dtype=int16) + array([[0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0]], dtype=int16) @@ -429,18 +428,16 @@ Specifying the dimensions of the matrix # code to be run in micropython - import ulab as np + from ulab import numpy as np print(np.eye(4, M=6, dtype=np.int8)) .. parsed-literal:: - array([[1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1], - [0, 0, 0, 0], - [0, 0, 0, 0]], dtype=int8) + array([[1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0]], dtype=int8) @@ -460,7 +457,7 @@ with a default value of ``float`` can also be supplied. # code to be run in micropython - import ulab as np + from ulab import numpy as np # create an array with the default type print(np.full((2, 4), 3)) @@ -472,12 +469,12 @@ with a default value of ``float`` can also be supplied. .. parsed-literal:: array([[3.0, 3.0, 3.0, 3.0], - [3.0, 3.0, 3.0, 3.0]], dtype=float) + [3.0, 3.0, 3.0, 3.0]], dtype=float64) ==================== array([[3, 3, 3, 3], - [3, 3, 3, 3]], dtype=uint8) + [3, 3, 3, 3]], dtype=uint8) @@ -502,7 +499,7 @@ consequence of rounding. (This is also the ``numpy`` behaviour.) # code to be run in micropython - import ulab as np + from ulab import numpy as np # generate a sequence with defaults print('default sequence:\t', np.linspace(0, 10)) @@ -518,9 +515,9 @@ consequence of rounding. (This is also the ``numpy`` behaviour.) .. parsed-literal:: - default sequence: array([0.0, 0.2040816396474838, 0.4081632792949677, ..., 9.591833114624023, 9.795914649963379, 9.999996185302734], dtype=float) - num=5: array([0.0, 2.5, 5.0, 7.5, 10.0], dtype=float) - num=5: array([0.0, 2.0, 4.0, 6.0, 8.0], dtype=float) + default sequence: array([0.0, 0.2040816326530612, 0.4081632653061225, ..., 9.591836734693871, 9.795918367346932, 9.999999999999993], dtype=float64) + num=5: array([0.0, 2.5, 5.0, 7.5, 10.0], dtype=float64) + num=5: array([0.0, 2.0, 4.0, 6.0, 8.0], dtype=float64) num=5: array([0, 0, 1, 2, 2, 3, 4], dtype=uint8) @@ -554,7 +551,7 @@ also accepts the ``base`` argument. The default value is 10. # code to be run in micropython - import ulab as np + from ulab import numpy as np # generate a sequence with defaults print('default sequence:\t', np.logspace(0, 3)) @@ -570,10 +567,10 @@ also accepts the ``base`` argument. The default value is 10. .. parsed-literal:: - default sequence: array([1.0, 1.151395399326447, 1.325711365590109, ..., 754.3120063354646, 868.5113737513561, 1000.000000000004], dtype=float) - num=5: array([10.0, 1778.279410038923, 316227.766016838, 56234132.5190349, 10000000000.0], dtype=float) - num=5: array([10.0, 630.9573444801933, 39810.71705534974, 2511886.431509581, 158489319.2461114], dtype=float) - num=5: array([2.0, 6.964404506368993, 24.25146506416637, 84.44850628946524, 294.066778879241], dtype=float) + default sequence: array([1.0, 1.151395399326447, 1.325711365590109, ..., 754.3120063354646, 868.5113737513561, 1000.000000000004], dtype=float64) + num=5: array([10.0, 1778.279410038923, 316227.766016838, 56234132.5190349, 10000000000.0], dtype=float64) + num=5: array([10.0, 630.9573444801933, 39810.71705534974, 2511886.431509581, 158489319.2461114], dtype=float64) + num=5: array([2.0, 6.964404506368993, 24.25146506416637, 84.44850628946524, 294.066778879241], dtype=float64) @@ -596,30 +593,55 @@ calling one of the ``ones``, or ``zeros`` functions. ``ones`` and ones(shape, dtype=float) zeros(shape, dtype=float) -where shape is either an integer, or a 2-tuple. +where shape is either an integer, or a tuple specifying the shape. .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np print(np.ones(6, dtype=np.uint8)) + print(np.zeros((6, 4))) .. parsed-literal:: array([1, 1, 1, 1, 1, 1], dtype=uint8) array([[0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0], - [0.0, 0.0, 0.0, 0.0]], dtype=float) + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0]], dtype=float64) +When specifying the shape, make sure that the length of the tuple is not +larger than the maximum dimension of your firmware. + +.. code:: + + # code to be run in micropython + + from ulab import numpy as np + import ulab + + print('maximum number of dimensions: ', ulab.__version__) + + print(np.zeros((2, 2, 2))) + +.. parsed-literal:: + + maximum number of dimensions: 2.1.0-2D + + Traceback (most recent call last): + File "/dev/shm/micropython.py", line 7, in + TypeError: too many dimensions + + + Customising array printouts --------------------------- @@ -632,14 +654,14 @@ last three entries will be printed. Also note that, as opposed to # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(200)) print("a:\t", a) .. parsed-literal:: - a: array([0.0, 1.0, 2.0, ..., 197.0, 198.0, 199.0], dtype=float) + a: array([0.0, 1.0, 2.0, ..., 197.0, 198.0, 199.0], dtype=float64) @@ -660,7 +682,7 @@ the ellipsis, if the array is longer than ``threshold``. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(20)) print("a printed with defaults:\t", a) @@ -673,11 +695,11 @@ the ellipsis, if the array is longer than ``threshold``. .. parsed-literal:: - a printed with defaults: array([0.0, 1.0, 2.0, ..., 17.0, 18.0, 19.0], dtype=float) + a printed with defaults: array([0.0, 1.0, 2.0, ..., 17.0, 18.0, 19.0], dtype=float64) - a printed in full: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0], dtype=float) + a printed in full: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0], dtype=float64) - a truncated with 2 edgeitems: array([0.0, 1.0, ..., 18.0, 19.0], dtype=float) + a truncated with 2 edgeitems: array([0.0, 1.0, ..., 18.0, 19.0], dtype=float64) @@ -693,7 +715,7 @@ function returns a *dictionary* with two keys. # code to be run in micropython - import ulab as np + from ulab import numpy as np np.set_printoptions(threshold=100, edgeitems=20) print(np.get_printoptions()) @@ -723,7 +745,7 @@ entries of the source array are *copied* into the target array. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) b = a.copy() @@ -759,7 +781,7 @@ object, and returns a single character (number) instead. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) b = np.array([5, 6, 7], dtype=a.dtype) @@ -783,7 +805,7 @@ object, and returns a single character (number) instead. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) b = np.array([5, 6, 7], dtype=a.dtype()) @@ -802,14 +824,19 @@ object, and returns a single character (number) instead. If the ``ulab.h`` header file sets the pre-processor constant -``ULAB_HAS_DTYPE_OBJECT`` to 0, then the output of the previous snippet -will be +``ULAB_HAS_DTYPE_OBJECT`` to 0 as + +.. code:: c + + #define ULAB_HAS_DTYPE_OBJECT (0) + +then the output of the previous snippet will be .. code:: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) b = np.array([5, 6, 7], dtype=a.dtype()) @@ -828,7 +855,8 @@ will be Here 98 is nothing but the ASCII value of the character ``b``, which is -the type code for signed 8-bit integers. +the type code for signed 8-bit integers. The object definition adds +around 600 bytes to the firmware. .flatten ~~~~~~~~ @@ -844,7 +872,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.htm # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) print("a: \t\t", a) @@ -862,7 +890,7 @@ https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.htm a flattened: array([1, 2, 3, 4], dtype=int8) b: array([[1, 2, 3], - [4, 5, 6]], dtype=int8) + [4, 5, 6]], dtype=int8) b flattened (C): array([1, 2, 3, 4, 5, 6], dtype=int8) b flattened (F): array([1, 4, 2, 5, 3, 6], dtype=int8) @@ -884,15 +912,15 @@ elements in the array. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3], dtype=np.int8) print("a:\n", a) - print("itemsize of a:", a.itemsize) + print("itemsize of a:", a.itemsize b= np.array([[1, 2], [3, 4]], dtype=np.float) print("\nb:\n", b) - print("itemsize of b:", b.itemsize) + print("itemsize of b:", b.itemsize .. parsed-literal:: @@ -902,7 +930,7 @@ elements in the array. b: array([[1.0, 2.0], - [3.0, 4.0]], dtype=float) + [3.0, 4.0]], dtype=float64) itemsize of b: 8 @@ -914,7 +942,7 @@ elements in the array. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3], dtype=np.int8) print("a:\n", a) @@ -928,11 +956,11 @@ elements in the array. a: array([1, 2, 3], dtype=int8) - itemsize of a: 1 + itemsize of a: > b: array([[1.0, 2.0], - [3.0, 4.0]], dtype=float) + [3.0, 4.0]], dtype=float64) itemsize of b: 8 @@ -954,7 +982,7 @@ consistent with the old, a ``ValueError`` exception will be raised. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], dtype=np.uint8) print('a (4 by 4):', a) @@ -964,12 +992,12 @@ consistent with the old, a ``ValueError`` exception will be raised. .. parsed-literal:: a (4 by 4): array([[1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - [13, 14, 15, 16]], dtype=uint8) + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16]], dtype=uint8) a (2 by 8): array([[1, 2, 3, 4, 5, 6, 7, 8], - [9, 10, 11, 12, 13, 14, 15, 16]], dtype=uint8) - a (1 by 16): array([1, 2, 3, ..., 14, 15, 16], dtype=uint8) + [9, 10, 11, 12, 13, 14, 15, 16]], dtype=uint8) + a (1 by 16): array([[1, 2, 3, ..., 14, 15, 16]], dtype=uint8) @@ -990,7 +1018,7 @@ property, i.e., # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) print("a:\n", a) @@ -998,17 +1026,17 @@ property, i.e., b= np.array([[1, 2], [3, 4]], dtype=np.int8) print("\nb:\n", b) - print("shape of b:", b.shape) + print("shape of b:", b.shape .. parsed-literal:: a: array([1, 2, 3, 4], dtype=int8) - shape of a: (1, 4) + shape of a: (4,) b: array([[1, 2], - [3, 4]], dtype=int8) + [3, 4]], dtype=int8) shape of b: (2, 2) @@ -1021,11 +1049,11 @@ property, i.e., # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.int8) print("a:\n", a) - print("shape of a:", a.shape) + print("shape of a:", a.shape()) b= np.array([[1, 2], [3, 4]], dtype=np.int8) print("\nb:\n", b) @@ -1035,11 +1063,11 @@ property, i.e., a: array([1, 2, 3, 4], dtype=int8) - shape of a: (1, 4) + shape of a: (4,) b: array([[1, 2], - [3, 4]], dtype=int8) + [3, 4]], dtype=int8) shape of b: (2, 2) @@ -1061,7 +1089,7 @@ i.e., # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3], dtype=np.int8) print("a:\n", a) @@ -1091,11 +1119,11 @@ i.e., # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3], dtype=np.int8) print("a:\n", a) - print("size of a:", a.size) + print("size of a:", a.size()) b= np.array([[1, 2], [3, 4]], dtype=np.int8) print("\nb:\n", b) @@ -1109,7 +1137,7 @@ i.e., b: array([[1, 2], - [3, 4]], dtype=int8) + [3, 4]], dtype=int8) size of b: 4 @@ -1136,7 +1164,7 @@ not dense (i.e., it has already been sliced). # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(8), dtype=np.uint8) print('a: ', a) @@ -1174,7 +1202,7 @@ dimensions is larger than 1. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], dtype=np.uint8) print('a:\n', a) @@ -1214,7 +1242,7 @@ In-place sorting of an ``ndarray``. For a more detailed exposition, see # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) print('\na:\n', a) @@ -1222,11 +1250,11 @@ In-place sorting of an ``ndarray``. For a more detailed exposition, see print('\na sorted along vertical axis:\n', a) a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) - a.sort(a, axis=1) + a.sort(axis=1) print('\na sorted along horizontal axis:\n', a) a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8) - a.sort(a, axis=None) + a.sort(axis=None) print('\nflattened a sorted:\n', a) .. parsed-literal:: @@ -1234,21 +1262,21 @@ In-place sorting of an ``ndarray``. For a more detailed exposition, see a: array([[1, 12, 3, 0], - [5, 3, 4, 1], - [9, 11, 1, 8], - [7, 10, 0, 1]], dtype=uint8) + [5, 3, 4, 1], + [9, 11, 1, 8], + [7, 10, 0, 1]], dtype=uint8) a sorted along vertical axis: array([[1, 3, 0, 0], - [5, 10, 1, 1], - [7, 11, 3, 1], - [9, 12, 4, 8]], dtype=uint8) + [5, 10, 1, 1], + [7, 11, 3, 1], + [9, 12, 4, 8]], dtype=uint8) a sorted along horizontal axis: array([[0, 1, 3, 12], - [1, 3, 4, 5], - [1, 8, 9, 11], - [0, 1, 7, 10]], dtype=uint8) + [1, 3, 4, 5], + [1, 8, 9, 11], + [0, 1, 7, 10]], dtype=uint8) flattened a sorted: array([0, 0, 1, ..., 10, 11, 12], dtype=uint8) @@ -1272,7 +1300,7 @@ length of the first axis. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) b = np.array([range(5), range(5), range(5), range(5)], dtype=np.uint8) @@ -1288,13 +1316,13 @@ length of the first axis. a: array([1, 2, 3, 4, 5], dtype=uint8) length of a: 5 - shape of a: (1, 5) + shape of a: (5,) b: array([[0, 1, 2, 3, 4], - [0, 1, 2, 3, 4], - [0, 1, 2, 3, 4], - [0, 1, 2, 3, 4]], dtype=uint8) - length of b: 4 + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4], + [0, 1, 2, 3, 4]], dtype=uint8) + length of b: 2 shape of b: (4, 5) @@ -1318,7 +1346,7 @@ unexpected, as in the example below: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([0, -1, -100], dtype=np.int8) print("a:\t\t", a) @@ -1351,7 +1379,7 @@ returned immediately, and no calculation takes place. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([0, -1, -100], dtype=np.int8) print("a:\t\t\t ", a) @@ -1375,7 +1403,7 @@ element in the array. Unsigned values are wrapped. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([10, -1, 1], dtype=np.int8) print("a:\t\t", a) @@ -1406,7 +1434,7 @@ array. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([10, -1, 1], dtype=np.int8) print("a:\t\t", a) @@ -1440,8 +1468,9 @@ side, when compared to scalars. This means that the following works # code to be run in micropython - import ulab - a = ulab.array([1, 2, 3]) + from ulab import numpy as np + + a = np.array([1, 2, 3]) print(a > 2) .. parsed-literal:: @@ -1458,15 +1487,16 @@ exception: # code to be run in micropython - import ulab - a = ulab.array([1, 2, 3]) + from ulab import numpy as np + + a = np.array([1, 2, 3]) print(2 < a) .. parsed-literal:: Traceback (most recent call last): - File "/dev/shm/micropython.py", line 4, in + File "/dev/shm/micropython.py", line 5, in TypeError: unsupported types for __lt__: 'int', 'ndarray' @@ -1528,7 +1558,7 @@ Upcasting can be seen in action in the following snippet: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4], dtype=np.uint8) b = np.array([1, 2, 3, 4], dtype=np.int8) @@ -1548,8 +1578,8 @@ Upcasting can be seen in action in the following snippet: a+b: array([2, 4, 6, 8], dtype=int16) a: array([1, 2, 3, 4], dtype=uint8) - c: array([1.0, 2.0, 3.0, 4.0], dtype=float) - a*c: array([1.0, 4.0, 9.0, 16.0], dtype=float) + c: array([1.0, 2.0, 3.0, 4.0], dtype=float64) + a*c: array([1.0, 4.0, 9.0, 16.0], dtype=float64) @@ -1585,7 +1615,7 @@ take the following snippet from the micropython manual: # code to be run in micropython - import ulab as np + from ulab import numpy as np @timeit def py_add(a, b): @@ -1658,7 +1688,7 @@ operators return a vector of Booleans indicating the positions # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8) print(a < 5) @@ -1707,7 +1737,7 @@ reduced-dimensional *view* is created and returned. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([1, 2, 3, 4, 5], dtype=np.uint8) b = np.array([range(5), range(10, 15, 1), range(20, 25, 1), range(30, 35, 1)], dtype=np.uint8) @@ -1732,9 +1762,9 @@ reduced-dimensional *view* is created and returned. element 4 in a: 5 b: array([[0, 1, 2, 3, 4], - [10, 11, 12, 13, 14], - [20, 21, 22, 23, 24], - [30, 31, 32, 33, 34]], dtype=uint8) + [10, 11, 12, 13, 14], + [20, 21, 22, 23, 24], + [30, 31, 32, 33, 34]], dtype=uint8) element 0 in b: array([0, 1, 2, 3, 4], dtype=uint8) element 1 in b: array([10, 11, 12, 13, 14], dtype=uint8) element 2 in b: array([20, 21, 22, 23, 24], dtype=uint8) @@ -1793,7 +1823,7 @@ Now, we can do the same with numerical arrays. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(10), dtype=np.uint8) print('a:\t', a) @@ -1822,7 +1852,7 @@ pointer* entry is the same in the two printouts. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(10), dtype=np.uint8) print('a: ', a, '\n') @@ -1869,7 +1899,7 @@ square brackets as in # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(10), dtype=np.uint8) print("a: ", a) @@ -1895,7 +1925,7 @@ dimensions, a new *view* is returned, otherwise, we get a single number. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(9), dtype=np.uint8).reshape((3, 3)) print("a:\n", a) @@ -1926,7 +1956,7 @@ future version of ``ulab``. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(9), dtype=np.float) print("a:\t", a) @@ -1947,7 +1977,7 @@ is a very concise way of comparing two vectors, e.g.: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(9), dtype=np.uint8) b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8) @@ -1980,7 +2010,7 @@ wherever some condition is fulfilled. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(9), dtype=np.uint8) b = np.array(range(9)) + 12 @@ -2004,7 +2034,7 @@ On the right hand side of the assignment we can even have another array. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array(range(9), dtype=np.uint8) b = np.array(range(9)) + 12 @@ -2032,7 +2062,7 @@ array. Slices are special python objects of the form # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) print('a:\n', a) @@ -2087,7 +2117,7 @@ column. A couple of examples should make these statements clearer: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.zeros((3, 3), dtype=np.uint8) print('a:\n', a) @@ -2130,7 +2160,7 @@ here are the results: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.zeros((3, 3), dtype=np.uint8) b = a[:,:] @@ -2168,7 +2198,7 @@ home: # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.zeros((3, 3), dtype=np.uint8) b = a.copy() @@ -2223,7 +2253,7 @@ that leaves ``a`` unchanged. # code to be run in micropython - import ulab as np + from ulab import numpy as np a = np.zeros((3, 3), dtype=np.uint8) b = a[0].copy() diff --git a/docs/manual/source/ulab-numerical.rst b/docs/manual/source/ulab-numerical.rst deleted file mode 100644 index 2dd0bc7..0000000 --- a/docs/manual/source/ulab-numerical.rst +++ /dev/null @@ -1,704 +0,0 @@ -Numerical -========= - -Function in the ``numerical`` sub-module can be called by importing the -sub-module first. - -min, argmin, max, argmax ------------------------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html - -**WARNING:** Difference to ``numpy``: the ``out`` keyword argument is -not implemented. - -These functions follow the same pattern, and work with generic -iterables, and ``ndarray``\ s. ``min``, and ``max`` return the minimum -or maximum of a sequence. If the input array is two-dimensional, the -``axis`` keyword argument can be supplied, in which case the -minimum/maximum along the given axis will be returned. If ``axis=None`` -(this is also the default value), the minimum/maximum of the flattened -array will be determined. - -``argmin/argmax`` return the position (index) of the minimum/maximum in -the sequence. - -.. code:: - - # code to be run in micropython - - import ulab as np - - a = np.array([1, 2, 3]) - print(a) - print(a[-1:-1:-3]) - try: - sa = list(a[-1:-1:-3]) - la = len(sa) - except IndexError as e: - sa = str(e) - la = -1 - - print(sa, la) - - a[-1:-1:-3] = np.ones(0) - print(a) - - b = np.ones(0) + 1 - print(b) - # print('b', b.shape()) - -.. parsed-literal:: - - array([1.0, 2.0, 3.0], dtype=float) - array([], dtype=float) - [] 0 - array([1.0, 2.0, 3.0], dtype=float) - array([], dtype=float) - - - - -.. code:: - - # code to be run in micropython - - import ulab as np - a = np.array([1, 2, 3]) - print(a[0:1:-3]) - -.. parsed-literal:: - - 0, 1, -3array([], dtype=float) - - - - -.. code:: - - # code to be run in CPython - - l = list(range(13)) - - l[0:10:113] - - - -.. parsed-literal:: - - [0] - - - -.. code:: - - # code to be run in CPython - - a = np.array([1, 2, 3]) - np.ones(0, dtype=uint8) / np.zeros(0, dtype=uint16) - np.ones(0).shape - - - -.. parsed-literal:: - - (0,) - - - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([1, 2, 0, 1, 10]) - print('a:', a) - print('min of a:', numerical.min(a)) - print('argmin of a:', numerical.argmin(a)) - - b = np.array([[1, 2, 0], [1, 10, -1]]) - print('\nb:\n', b) - print('min of b (flattened):', numerical.min(b)) - print('min of b (axis=0):', numerical.min(b, axis=0)) - print('min of b (axis=1):', numerical.min(b, axis=1)) - -.. parsed-literal:: - - a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float) - min of a: 0.0 - argmin of a: 2 - - b: - array([[1.0, 2.0, 0.0], - [1.0, 10.0, -1.0]], dtype=float) - min of b (flattened): -1.0 - min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float) - min of b (axis=1): array([0.0, -1.0], dtype=float) - - - - -sum, std, mean --------------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html - -These three functions follow the same pattern: if the axis keyword is -not specified, it assumes the default value of ``None``, and returns the -result of the computation for the flattened array. Otherwise, the -calculation is along the given axis. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - print('a: \n', a) - - print('sum, flat array: ', numerical.sum(a)) - - print('mean, horizontal: ', numerical.mean(a, axis=1)) - - print('std, vertical: ', numerical.std(a, axis=0)) - -.. parsed-literal:: - - a: - array([[1.0, 2.0, 3.0], - [4.0, 5.0, 6.0], - [7.0, 8.0, 9.0]], dtype=float) - sum, flat array: 45.0 - mean, horizontal: array([2.0, 5.0, 8.0], dtype=float) - std, vertical: array([2.44949, 2.44949, 2.44949], dtype=float) - - - -roll ----- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html - -The roll function shifts the content of a vector by the positions given -as the second argument. If the ``axis`` keyword is supplied, the shift -is applied to the given axis. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([1, 2, 3, 4, 5, 6, 7, 8]) - print("a:\t\t\t", a) - - numerical.roll(a, 2) - print("a rolled to the left:\t", a) - - # this should be the original vector - numerical.roll(a, -2) - print("a rolled to the right:\t", a) - -.. parsed-literal:: - - a: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) - a rolled to the left: array([3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 1.0, 2.0], dtype=float) - a rolled to the right: array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float) - - - - -Rolling works with matrices, too. If the ``axis`` keyword is 0, the -matrix is rolled along its vertical axis, otherwise, horizontally. - -Horizontal rolls are faster, because they require fewer steps, and -larger memory chunks are copied, however, they also require more RAM: -basically the whole row must be stored internally. Most expensive are -the ``None`` keyword values, because with ``axis = None``, the array is -flattened first, hence the row’s length is the size of the whole matrix. - -Vertical rolls require two internal copies of single columns. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([[1, 2, 3, 4], [5, 6, 7, 8]]) - print("a:\n", a) - - numerical.roll(a, 2) - print("\na rolled to the left:\n", a) - - numerical.roll(a, -1, axis=1) - print("\na rolled up:\n", a) - - numerical.roll(a, 1, axis=None) - print("\na rolled with None:\n", a) - -.. parsed-literal:: - - a: - array([[1.0, 2.0, 3.0, 4.0], - [5.0, 6.0, 7.0, 8.0]], dtype=float) - - a rolled to the left: - array([[3.0, 4.0, 5.0, 6.0], - [7.0, 8.0, 1.0, 2.0]], dtype=float) - - a rolled up: - array([[6.0, 3.0, 4.0, 5.0], - [2.0, 7.0, 8.0, 1.0]], dtype=float) - - a rolled with None: - array([[3.0, 4.0, 5.0, 2.0], - [7.0, 8.0, 1.0, 6.0]], dtype=float) - - - - -Simple running weighted average -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -As a demonstration of the conciseness of ``ulab/numpy`` operations, we -will calculate an exponentially weighted running average of a -measurement vector in just a couple of lines. I chose this particular -example, because I think that this can indeed be used in real-life -applications. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - from ulab import vector - - def dummy_adc(): - # dummy adc function, so that the results are reproducible - return 2 - - n = 10 - # These are the normalised weights; the last entry is the most dominant - weight = vector.exp([1, 2, 3, 4, 5]) - weight = weight/numerical.sum(weight) - - print(weight) - # initial array of samples - samples = np.array([0]*n) - - for i in range(n): - # a new datum is inserted on the right hand side. This simply overwrites whatever was in the last slot - samples[-1] = dummy_adc() - print(numerical.mean(samples[-5:]*weight)) - print(samples[-5:]) - # the data are shifted by one position to the left - numerical.roll(samples, 1) - -.. parsed-literal:: - - array([0.01165623031556606, 0.03168492019176483, 0.08612854033708572, 0.234121635556221, 0.6364086270332336], dtype=float) - 0.2545634508132935 - array([0.0, 0.0, 0.0, 0.0, 2.0], dtype=float) - 0.3482121050357819 - array([0.0, 0.0, 0.0, 2.0, 2.0], dtype=float) - 0.3826635211706161 - array([0.0, 0.0, 2.0, 2.0, 2.0], dtype=float) - 0.3953374892473221 - array([0.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - 0.3999999813735485 - array([2.0, 2.0, 2.0, 2.0, 2.0], dtype=float) - - - - -flip ----- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html - -The ``flip`` function takes one positional, an ``ndarray``, and one -keyword argument, ``axis = None``, and reverses the order of elements -along the given axis. If the keyword argument is ``None``, the matrix’ -entries are flipped along all axes. ``flip`` returns a new copy of the -array. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([1, 2, 3, 4, 5]) - print("a: \t", a) - print("a flipped:\t", np.flip(a)) - - a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8) - print("\na flipped horizontally\n", numerical.flip(a, axis=1)) - print("\na flipped vertically\n", numerical.flip(a, axis=0)) - print("\na flipped horizontally+vertically\n", numerical.flip(a)) - -.. parsed-literal:: - - a: array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float) - a flipped: array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float) - - a flipped horizontally - array([[3, 2, 1], - [6, 5, 4], - [9, 8, 7]], dtype=uint8) - - a flipped vertically - array([[7, 8, 9], - [4, 5, 6], - [1, 2, 3]], dtype=uint8) - - a flipped horizontally+vertically - array([[9, 8, 7], - [6, 5, 4], - [3, 2, 1]], dtype=uint8) - - - - -diff ----- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html - -The ``diff`` function returns the numerical derivative of the forward -scheme, or more accurately, the differences of an ``ndarray`` along a -given axis. The order of derivative can be stipulated with the ``n`` -keyword argument, which should be between 0, and 9. Default is 1. If -higher order derivatives are required, they can be gotten by repeated -calls to the function. The ``axis`` keyword argument should be -1 (last -axis, in ``ulab`` equivalent to the second axis, and this also happens -to be the default value), 0, or 1. - -Beyond the output array, the function requires only a couple of bytes of -extra RAM for the differentiation stencil. (The stencil is an ``int8`` -array, one byte longer than ``n``. This also explains, why the highest -order is 9: the coefficients of a ninth-order stencil all fit in signed -bytes, while 10 would require ``int16``.) Note that as usual in -numerical differentiation (and also in ``numpy``), the length of the -respective axis will be reduced by ``n`` after the operation. If ``n`` -is larger than, or equal to the length of the axis, an empty array will -be returned. - -**WARNING**: the ``diff`` function does not implement the ``prepend`` -and ``append`` keywords that can be found in ``numpy``. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array(range(9), dtype=np.uint8) - print('a:\n', a) - - print('\nfirst derivative:\n', numerical.diff(a, n=1)) - print('\nsecond derivative:\n', numerical.diff(a, n=2)) - - c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]]) - print('\nc:\n', c) - print('\nfirst derivative, first axis:\n', numerical.diff(c, axis=0)) - print('\nfirst derivative, second axis:\n', numerical.diff(c, axis=1)) - -.. parsed-literal:: - - a: - array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8) - - first derivative: - array([1, 1, 1, 1, 1, 1, 1, 1], dtype=uint8) - - second derivative: - array([0, 0, 0, 0, 0, 0, 0], dtype=uint8) - - c: - array([[1.0, 2.0, 3.0, 4.0], - [4.0, 3.0, 2.0, 1.0], - [1.0, 4.0, 9.0, 16.0], - [0.0, 0.0, 0.0, 0.0]], dtype=float) - - first derivative, first axis: - array([[3.0, 1.0, -1.0, -3.0], - [-3.0, 1.0, 7.0, 15.0], - [-1.0, -4.0, -9.0, -16.0]], dtype=float) - - first derivative, second axis: - array([[1.0, 1.0, 1.0], - [-1.0, -1.0, -1.0], - [3.0, 5.0, 7.0], - [0.0, 0.0, 0.0]], dtype=float) - - - - -median ------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html - -The function computes the median along the specified axis, and returns -the median of the array elements. If the ``axis`` keyword argument is -``None``, the arrays is flattened first. The ``dtype`` of the results is -always float. - -.. code:: - - # code to be run in micropython - - import ulab as np - - a = np.array(range(12), dtype=np.int8).reshape((3, 4)) - print('a:\n', a) - print('\nmedian of the flattened array: ', np.median(a)) - print('\nmedian along the vertical axis: ', np.median(a, axis=0)) - print('\nmedian along the horizontal axis: ', np.median(a, axis=1)) - -.. parsed-literal:: - - a: - array([[0, 1, 2, 3], - [4, 5, 6, 7], - [8, 9, 10, 11]], dtype=int8) - - median of the flattened array: 5.5 - - median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float) - - median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float) - - - - -sort ----- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html - -The sort function takes an ndarray, and sorts its elements in ascending -order along the specified axis using a heap sort algorithm. As opposed -to the ``.sort()`` method discussed earlier, this function creates a -copy of its input before sorting, and at the end, returns this copy. -Sorting takes place in place, without auxiliary storage. The ``axis`` -keyword argument takes on the possible values of -1 (the last axis, in -``ulab`` equivalent to the second axis, and this also happens to be the -default value), 0, 1, or ``None``. The first three cases are identical -to those in `diff <#diff>`__, while the last one flattens the array -before sorting. - -If descending order is required, the result can simply be ``flip``\ ped, -see `flip <#flip>`__. - -**WARNING:** ``numpy`` defines the ``kind``, and ``order`` keyword -arguments that are not implemented here. The function in ``ulab`` always -uses heap sort, and since ``ulab`` does not have the concept of data -fields, the ``order`` keyword argument would have no meaning. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) - print('\na:\n', a) - b = numerical.sort(a, axis=0) - print('\na sorted along vertical axis:\n', b) - - c = numerical.sort(a, axis=1) - print('\na sorted along horizontal axis:\n', c) - - c = numerical.sort(a, axis=None) - print('\nflattened a sorted:\n', c) - -.. parsed-literal:: - - - a: - array([[1.0, 12.0, 3.0, 0.0], - [5.0, 3.0, 4.0, 1.0], - [9.0, 11.0, 1.0, 8.0], - [7.0, 10.0, 0.0, 1.0]], dtype=float) - - a sorted along vertical axis: - array([[1.0, 3.0, 0.0, 0.0], - [5.0, 10.0, 1.0, 1.0], - [7.0, 11.0, 3.0, 1.0], - [9.0, 12.0, 4.0, 8.0]], dtype=float) - - a sorted along horizontal axis: - array([[0.0, 1.0, 3.0, 12.0], - [1.0, 3.0, 4.0, 5.0], - [1.0, 8.0, 9.0, 11.0], - [0.0, 1.0, 7.0, 10.0]], dtype=float) - - flattened a sorted: - array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float) - - - - -Heap sort requires :math:`\sim N\log N` operations, and notably, the -worst case costs only 20% more time than the average. In order to get an -order-of-magnitude estimate, we will take the sine of 1000 uniformly -spaced numbers between 0, and two pi, and sort them: - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import vector - from ulab import numerical - - @timeit - def sort_time(array): - return numerical.sort(array) - - b = vector.sin(np.linspace(0, 6.28, num=1000)) - print('b: ', b) - sort_time(b) - print('\nb sorted:\n', b) -argsort -------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html - -Similarly to `sort <#sort>`__, ``argsort`` takes a positional, and a -keyword argument, and returns an unsigned short index array of type -``ndarray`` with the same dimensions as the input, or, if ``axis=None``, -as a row vector with length equal to the number of elements in the input -(i.e., the flattened array). The indices in the output sort the input in -ascending order. The routine in ``argsort`` is the same as in ``sort``, -therefore, the comments on computational expenses (time and RAM) also -apply. In particular, since no copy of the original data is required, -virtually no RAM beyond the output array is used. - -Since the underlying container of the output array is of type -``uint16_t``, neither of the output dimensions should be larger than -65535. If that happens to be the case, the function will bail out with a -``ValueError``. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float) - print('\na:\n', a) - b = numerical.argsort(a, axis=0) - print('\na sorted along vertical axis:\n', b) - - c = numerical.argsort(a, axis=1) - print('\na sorted along horizontal axis:\n', c) - - c = numerical.argsort(a, axis=None) - print('\nflattened a sorted:\n', c) - -.. parsed-literal:: - - - a: - array([[1.0, 12.0, 3.0, 0.0], - [5.0, 3.0, 4.0, 1.0], - [9.0, 11.0, 1.0, 8.0], - [7.0, 10.0, 0.0, 1.0]], dtype=float) - - a sorted along vertical axis: - array([[0, 1, 3, 0], - [1, 3, 2, 1], - [3, 2, 0, 3], - [2, 0, 1, 2]], dtype=uint16) - - a sorted along horizontal axis: - array([[3, 0, 2, 1], - [3, 1, 2, 0], - [2, 3, 0, 1], - [2, 3, 0, 1]], dtype=uint16) - - flattened a sorted: - array([3, 14, 0, ..., 13, 9, 1], dtype=uint16) - - - - -Since during the sorting, only the indices are shuffled, ``argsort`` -does not modify the input array, as one can verify this by the following -example: - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import numerical - - a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8) - print('\na:\n', a) - b = numerical.argsort(a, axis=1) - print('\nsorting indices:\n', b) - print('\nthe original array:\n', a) - -.. parsed-literal:: - - - a: - array([0, 5, 1, 3, 2, 4], dtype=uint8) - - sorting indices: - array([0, 2, 4, 3, 5, 1], dtype=uint16) - - the original array: - array([0, 5, 1, 3, 2, 4], dtype=uint8) - - - diff --git a/docs/manual/source/ulab-poly.rst b/docs/manual/source/ulab-poly.rst deleted file mode 100644 index af9ee05..0000000 --- a/docs/manual/source/ulab-poly.rst +++ /dev/null @@ -1,122 +0,0 @@ -Polynomials -=========== - -Functions in the polynomial sub-module can be invoked by importing the -module first. - -polyval -------- - -``numpy``: -https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html - -``polyval`` takes two arguments, both arrays or other iterables. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import poly - - p = [1, 1, 1, 0] - x = [0, 1, 2, 3, 4] - print('coefficients: ', p) - print('independent values: ', x) - print('\nvalues of p(x): ', poly.polyval(p, x)) - - # the same works with one-dimensional ndarrays - a = np.array(x) - print('\nndarray (a): ', a) - print('value of p(a): ', poly.polyval(p, a)) - -.. parsed-literal:: - - coefficients: [1, 1, 1, 0] - independent values: [0, 1, 2, 3, 4] - - values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float) - - ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float) - value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float) - - - - -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, assuming uniform -sampling. - -If the length of ``x``, and ``y`` are not the same, the function raises -a ``ValueError``. - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import poly - - x = np.array([0, 1, 2, 3, 4, 5, 6]) - y = np.array([9, 4, 1, 0, 1, 4, 9]) - print('independent values:\t', x) - print('dependent values:\t', y) - print('fitted values:\t\t', poly.polyfit(x, y, 2)) - - # the same with missing x - print('\ndependent values:\t', y) - print('fitted values:\t\t', poly.polyfit(y, 2)) - -.. parsed-literal:: - - independent values: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float) - dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float) - fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float) - - dependent values: array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float) - fitted values: array([1.0, -6.0, 9.000000000000004], dtype=float) - - - - -Execution time -~~~~~~~~~~~~~~ - -``polyfit`` is based on the inversion of a matrix (there is more on the -background in https://en.wikipedia.org/wiki/Polynomial_regression), and -it requires the intermediate storage of ``2*N*(deg+1)`` floats, where -``N`` is the number of entries in the input array, and ``deg`` is the -fit’s degree. The additional computation costs of the matrix inversion -discussed in `inv <#inv>`__ also apply. The example from above needs -around 150 microseconds to return: - -.. code:: - - # code to be run in micropython - - import ulab as np - from ulab import poly - - @timeit - def time_polyfit(x, y, n): - return poly.polyfit(x, y, n) - - x = np.array([0, 1, 2, 3, 4, 5, 6]) - y = np.array([9, 4, 1, 0, 1, 4, 9]) - - time_polyfit(x, y, 2) - -.. parsed-literal:: - - execution time: 153 us - - diff --git a/docs/manual/source/ulab-programming.rst b/docs/manual/source/ulab-programming.rst index 3d5f998..4675048 100644 --- a/docs/manual/source/ulab-programming.rst +++ b/docs/manual/source/ulab-programming.rst @@ -1,3 +1,4 @@ +None Programming ulab ================ @@ -6,22 +7,15 @@ accessed in ``micropython``. This last section of the book explains, how these functions are implemented. By the end of this chapter, not only would you be able to extend ``ulab``, and write your own ``numpy``-compatible functions, but through a deeper understanding of -the inner workings of the functions, you would be able to see what the -trade-offs are at the ``python`` level. +the inner workings of the functions, you would also be able to see what +the trade-offs are at the ``python`` level. Code organisation ----------------- As mentioned earlier, the ``python`` functions are organised into -sub-modules at the C level. Functions in module ``x`` always begin with -the ``x_`` prefix, so it is relatively easy to navigate the code. -Sub-modules are all in their respective folder. E.g., the ``filter`` -sub-module is in ``./ulab/code/filter/``, with two files, -``./ulab/code/filter/filter.h``, and ``./ulab/code/filter/filter.c``. -``filter.c`` contains two functions, ``filter_convolve``, and -``filter_sosfilt``, which are bound to the name space either in -``ulab_filter_globals_table[]``, or, if ``numpy``-compatibility is -required, at the top level, in ``ulab.c``. +sub-modules at the C level. The C sub-modules can be found in +``./ulab/code/``. The ``ndarray`` object ---------------------- @@ -34,15 +28,15 @@ General comments ``mp_float_t``\ s, which, depending on the platform, are either C ``float``\ s, or C ``double``\ s). Beyond storing the actual data in the void pointer ``*array``, the type definition has eight additional -members (on top of the ``base`` type). Namely, ``dense``, which tells -us, whether the array is dense or sparse (more on this later), the -``dtype``, which tells us, how the bytes are to be interpreted. -Moreover, the ``itemsize``, which stores the size of a single entry in -the array, ``boolean``, an unsigned integer, which determines, whether -the arrays is to be treated as a set of Booleans, or as numerical data, -``ndim``, the number of dimensions (``uint8_t``), ``len``, the length of -the array, the shape (``*size_t``), the strides (``*int32_t``). The -length is simply the product of the numbers in ``shape``. +members (on top of the ``base`` type). Namely, the ``dtype``, which +tells us, how the bytes are to be interpreted. Moreover, the +``itemsize``, which stores the size of a single entry in the array, +``boolean``, an unsigned integer, which determines, whether the arrays +is to be treated as a set of Booleans, or as numerical data, ``ndim``, +the number of dimensions (``uint8_t``), ``len``, the length of the array +(the number of entries), the shape (``*size_t``), the strides +(``*int32_t``). The length is simply the product of the numbers in +``shape``. The type definition is as follows: @@ -50,7 +44,6 @@ The type definition is as follows: typedef struct _ndarray_obj_t { mp_obj_base_t base; - uint8_t dense; uint8_t dtype; uint8_t itemsize; uint8_t boolean; @@ -131,7 +124,7 @@ Iterating using the unwrapped loops ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following macro definition is taken from -`vectorise.h `__, +`vector.h `__, and demonstrates, how we can iterate over a single array in four dimensions. @@ -357,8 +350,8 @@ the two arrays, so that they can be iterated over at the same time. } A good example of how the function would be called can be found in -`vectorise.c `__, -in the ``vectorise_arctan2`` function: +`vector.c `__, +in the ``vector_arctan2`` function: .. code:: c @@ -421,7 +414,7 @@ Once the reduced ``strides`` and ``shape`` are known, we place the axis in question in the innermost loop, and wrap it with the loops, whose coordinates are in the ``strides``, and ``shape`` arrays. The ``RUN_STD`` macro from -`numerical.h `__ +`numerical.h `__ is a good example. The macro is expanded in the ``numerical_sum_mean_std_ndarray`` function. @@ -801,15 +794,15 @@ constant has been set to 1. After compilation, you can call a particular .. code:: python - import ulab + from ulab import numpy as np from ulab import user user.some_function(...) This separation of user-defined functions from the rest of the code ensures that the integrity of the main module and all its functions are -always preserved. Even in case of a catastrophic failure, you can easily -clone ``ulab`` anew, and start over. +always preserved. Even in case of a catastrophic failure, you can +exclude the ``user`` module, and start over. And now the function: diff --git a/docs/manual/source/ulab/approx/index.rst b/docs/manual/source/ulab/approx/index.rst deleted file mode 100644 index 01d2484..0000000 --- a/docs/manual/source/ulab/approx/index.rst +++ /dev/null @@ -1,70 +0,0 @@ -:mod:`ulab.approx` -================== - -.. py:module:: ulab.approx - -.. autoapi-nested-parse:: - - Numerical approximation methods - - - -.. function:: bisect(fun: Callable[[float], float], a: float, b: float, *, xtol: float = 2.4e-07, maxiter: int = 100) -> float - - :param callable f: The function to bisect - :param float a: The left side of the interval - :param float b: The right side of the interval - :param float xtol: The tolerance value - :param float maxiter: The maximum number of iterations to perform - - Find a solution (zero) of the function ``f(x)`` on the interval - (``a``..``b``) using the bisection method. The result is accurate to within - ``xtol`` unless more than ``maxiter`` steps are required. - - -.. function:: fmin(fun: Callable[[float], float], x0: float, *, xatol: float = 2.4e-07, fatol: float = 2.4e-07, maxiter: int = 200) -> float - - :param callable f: The function to bisect - :param float x0: The initial x value - :param float xatol: The absolute tolerance value - :param float fatol: The relative tolerance value - - Find a minimum of the function ``f(x)`` using the downhill simplex method. - The located ``x`` is within ``fxtol`` of the actual minimum, and ``f(x)`` - is within ``fatol`` of the actual minimum unless more than ``maxiter`` - steps are requried. - - -.. function:: interp(x: ulab.array, xp: ulab.array, fp: ulab.array, *, left: Optional[float] = None, right: Optional[float] = None) -> ulab.array - - :param ulab.array x: The x-coordinates at which to evaluate the interpolated values. - :param ulab.array xp: The x-coordinates of the data points, must be increasing - :param ulab.array fp: The y-coordinates of the data points, same length as xp - :param left: Value to return for ``x < xp[0]``, default is ``fp[0]``. - :param right: Value to return for ``x > xp[-1]``, default is ``fp[-1]``. - - Returns the one-dimensional piecewise linear interpolant to a function with given discrete data points (xp, fp), evaluated at x. - - -.. function:: newton(fun: Callable[[float], float], x0: float, *, xtol: float = 2.4e-07, rtol: float = 0.0, maxiter: int = 50) -> float - - :param callable f: The function to bisect - :param float x0: The initial x value - :param float xtol: The absolute tolerance value - :param float rtol: The relative tolerance value - :param float maxiter: The maximum number of iterations to perform - - Find a solution (zero) of the function ``f(x)`` using Newton's Method. - The result is accurate to within ``xtol * rtol * |f(x)|`` unless more than - ``maxiter`` steps are requried. - - -.. function:: trapz(y: ulab.array, x: Optional[ulab.array] = None, dx: float = 1.0) -> float - - :param 1D ulab.array y: the values of the dependent variable - :param 1D ulab.array x: optional, the coordinates of the independent variable. Defaults to uniformly spaced values. - :param float dx: the spacing between sample points, if x=None - - Returns the integral of y(x) using the trapezoidal rule. - - diff --git a/docs/manual/source/ulab/compare/index.rst b/docs/manual/source/ulab/compare/index.rst deleted file mode 100644 index 1111f4c..0000000 --- a/docs/manual/source/ulab/compare/index.rst +++ /dev/null @@ -1,51 +0,0 @@ -:mod:`ulab.compare` -=================== - -.. py:module:: ulab.compare - -.. autoapi-nested-parse:: - - Comparison functions - - - -.. function:: clip(x1: Union[ulab.array, float], x2: Union[ulab.array, float], x3: Union[ulab.array, float]) -> ulab.array - - Constrain the values from ``x1`` to be between ``x2`` and ``x3``. - ``x2`` is assumed to be less than or equal to ``x3``. - - Arguments may be ulab arrays or numbers. All array arguments - must be the same size. If the inputs are all scalars, a - single scalar is returned. - - Shorthand for ``ulab.maximum(x2, ulab.minimum(x1, x3))`` - - -.. function:: equal(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> List[bool] - - Return an array of bool which is true where x1[i] == x2[i] and false elsewhere - - -.. function:: not_equal(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> List[bool] - - Return an array of bool which is false where x1[i] == x2[i] and true elsewhere - - -.. function:: maximum(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> ulab.array - - Compute the element by element maximum of the arguments. - - Arguments may be ulab arrays or numbers. All array arguments - must be the same size. If the inputs are both scalars, a number is - returned - - -.. function:: minimum(x1: Union[ulab.array, float], x2: Union[ulab.array, float]) -> ulab.array - - Compute the element by element minimum of the arguments. - - Arguments may be ulab arrays or numbers. All array arguments - must be the same size. If the inputs are both scalars, a number is - returned - - diff --git a/docs/manual/source/ulab/fft/index.rst b/docs/manual/source/ulab/fft/index.rst deleted file mode 100644 index 3c14bb9..0000000 --- a/docs/manual/source/ulab/fft/index.rst +++ /dev/null @@ -1,40 +0,0 @@ -:mod:`ulab.fft` -=============== - -.. py:module:: ulab.fft - -.. autoapi-nested-parse:: - - Frequency-domain functions - - - -.. function:: fft(r: ulab.array, c: Optional[ulab.array] = None) -> Tuple[ulab.array, ulab.array] - - :param ulab.array r: A 1-dimension array of values whose size is a power of 2 - :param ulab.array c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value - :return tuple (r, c): The real and complex parts of the FFT - - Perform a Fast Fourier Transform from the time domain into the frequency domain - - See also ~ulab.extras.spectrum, which computes the magnitude of the fft, - rather than separately returning its real and imaginary parts. - - -.. function:: ifft(r: ulab.array, c: Optional[ulab.array] = None) -> Tuple[ulab.array, ulab.array] - - :param ulab.array r: A 1-dimension array of values whose size is a power of 2 - :param ulab.array c: An optional 1-dimension array of values whose size is a power of 2, giving the complex part of the value - :return tuple (r, c): The real and complex parts of the inverse FFT - - Perform an Inverse Fast Fourier Transform from the frequeny domain into the time domain - - -.. function:: spectrogram(r: ulab.array) -> ulab.array - - :param ulab.array r: A 1-dimension array of values whose size is a power of 2 - - Computes the spectrum of the input signal. This is the absolute value of the (complex-valued) fft of the signal. - This function is similar to scipy's ``scipy.signal.spectrogram``. - - diff --git a/docs/manual/source/ulab/filter/index.rst b/docs/manual/source/ulab/filter/index.rst deleted file mode 100644 index fb4d6df..0000000 --- a/docs/manual/source/ulab/filter/index.rst +++ /dev/null @@ -1,48 +0,0 @@ -:mod:`ulab.filter` -================== - -.. py:module:: ulab.filter - -.. autoapi-nested-parse:: - - Filtering functions - - - -.. function:: convolve(a: ulab.array, v: ulab.array) -> ulab.array - - :param ulab.array a: - :param ulab.array v: - - Returns the discrete, linear convolution of two one-dimensional sequences. - The result is always an array of float. Only the ``full`` mode is supported, - and the ``mode`` named parameter of numpy is not accepted. Note that all other - modes can be had by slicing a ``full`` result. - - Convolution filters can implement high pass, low pass, band pass, etc., - filtering operations. Convolution filters are typically constructed ahead - of time. This can be done using desktop python with scipy, or on web pages - such as https://fiiir.com/ - - Convolution is most time-efficient when both inputs are of float type. - - -.. function:: sosfilt(sos: _ArrayLike, x: _ArrayLike) -> ulab.array - - -.. function:: sosfilt(sos: _ArrayLike, x: _ArrayLike, *, zi: ulab.array) -> Tuple[ulab.array, ulab.array] - - :param ulab.array sos: Array of second-order filter coefficients, must have shape (n_sections, 6). Each row corresponds to a second-order section, with the first three columns providing the numerator coefficients and the last three providing the denominator coefficients. - :param ulab.array x: The data to be filtered - :param ulab.array zi: Optional initial conditions for the filter - :return: If ``zi`` is not specified, the filter result alone is returned. If ``zi`` is specified, the return value is a 2-tuple of the filter result and the final filter conditions. - - Filter data along one dimension using cascaded second-order sections. - - Filter a data sequence, x, using a digital IIR filter defined by sos. - - The filter function is implemented as a series of second-order filters with direct-form II transposed structure. It is designed to minimize numerical precision errors for high-order filters. - - Filter coefficients can be generated by using scipy's filter generators such as ``signal.ellip(..., output='sos')``. - - diff --git a/docs/manual/source/ulab/index.rst b/docs/manual/source/ulab/index.rst deleted file mode 100644 index ad1304c..0000000 --- a/docs/manual/source/ulab/index.rst +++ /dev/null @@ -1,439 +0,0 @@ -:mod:`ulab` -=========== - -.. py:module:: ulab - -.. autoapi-nested-parse:: - - Manipulate numeric data similar to numpy - - `ulab` is a numpy-like module for micropython, meant to simplify and - speed up common mathematical operations on arrays. The primary goal was to - implement a small subset of numpy that might be useful in the context of a - microcontroller. This means low-level data processing of linear (array) and - two-dimensional (matrix) data. - - `ulab` is adapted from micropython-ulab, and the original project's - documentation can be found at - https://micropython-ulab.readthedocs.io/en/latest/ - - `ulab` is modeled after numpy, and aims to be a compatible subset where - possible. Numpy's documentation can be found at - https://docs.scipy.org/doc/numpy/index.html - - - -.. toctree:: - :titlesonly: - :maxdepth: 3 - - approx/index.rst - compare/index.rst - fft/index.rst - filter/index.rst - linalg/index.rst - numerical/index.rst - poly/index.rst - user/index.rst - vector/index.rst - - -.. data:: _DType - - - `ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool` - - -.. data:: _float - - - Type alias of the bulitin float - - -.. data:: _bool - - - Type alias of the bulitin bool - - -.. data:: _Index - - - - -.. py:class:: array(values: Union[array, Iterable[Union[_float, _bool, Iterable[Any]]]], *, dtype: _DType = ulab.float) - - 1- and 2- dimensional array - - :param sequence values: Sequence giving the initial content of the array. - :param ~ulab._DType dtype: The type of array values, `ulab.int8`, `ulab.uint8`, `ulab.int16`, `ulab.uint16`, `ulab.float` or `ulab.bool` - - The ``values`` sequence can either be another ~ulab.array, sequence of numbers - (in which case a 1-dimensional array is created), or a sequence where each - subsequence has the same length (in which case a 2-dimensional array is - created). - - Passing a `ulab.array` and a different dtype can be used to convert an array - from one dtype to another. - - In many cases, it is more convenient to create an array from a function - like `zeros` or `linspace`. - - `ulab.array` implements the buffer protocol, so it can be used in many - places an `array.array` can be used. - - .. attribute:: shape - :annotation: :Tuple[int, ...] - - The size of the array, a tuple of length 1 or 2 - - - .. attribute:: size - :annotation: :int - - The number of elements in the array - - - .. attribute:: itemsize - :annotation: :int - - The size of a single item in the array - - - .. attribute:: strides - :annotation: :Tuple[int, ...] - - Tuple of bytes to step in each dimension, a tuple of length 1 or 2 - - - .. method:: copy(self) - - - Return a copy of the array - - - .. method:: flatten(self, *, order: str = 'C') - - - :param order: Whether to flatten by rows ('C') or columns ('F') - - Returns a new `ulab.array` object which is always 1 dimensional. - If order is 'C' (the default", then the data is ordered in rows; - If it is 'F', then the data is ordered in columns. "C" and "F" refer - to the typical storage organization of the C and Fortran languages. - - - .. method:: reshape(self, shape: Tuple[int, ...]) - - - Returns an array containing the same data with a new shape. - - - .. method:: sort(self, *, axis: Optional[int] = 1) - - - :param axis: Whether to sort elements within rows (0), columns (1), or elements (None) - - - .. method:: tobytes(self) - - - Return the raw data bytes in the array - - - .. method:: transpose(self) - - - Swap the rows and columns of a 2-dimensional array - - - .. method:: __add__(self, other: Union[array, _float]) - - - Adds corresponding elements of the two arrays, or adds a number to all - elements of the array. If both arguments are arrays, their sizes must match. - - - .. method:: __radd__(self, other: _float) - - - - .. method:: __sub__(self, other: Union[array, _float]) - - - Subtracts corresponding elements of the two arrays, or subtracts a number from all - elements of the array. If both arguments are arrays, their sizes must match. - - - .. method:: __rsub__(self, other: _float) - - - - .. method:: __mul__(self, other: Union[array, _float]) - - - Multiplies corresponding elements of the two arrays, or multiplies - all elements of the array by a number. If both arguments are arrays, - their sizes must match. - - - .. method:: __rmul__(self, other: _float) - - - - .. method:: __div__(self, other: Union[array, _float]) - - - Multiplies corresponding elements of the two arrays, or divides - all elements of the array by a number. If both arguments are arrays, - their sizes must match. - - - .. method:: __rdiv__(self, other: _float) - - - - .. method:: __pow__(self, other: Union[array, _float]) - - - Computes the power (x**y) of corresponding elements of the the two arrays, - or one number and one array. If both arguments are arrays, their sizes - must match. - - - .. method:: __rpow__(self, other: _float) - - - - .. method:: __inv__(self) - - - - .. method:: __neg__(self) - - - - .. method:: __pos__(self) - - - - .. method:: __abs__(self) - - - - .. method:: __len__(self) - - - - .. method:: __lt__(self, other: Union[array, _float]) - - - Return selfvalue. - - - .. method:: __ge__(self, other: Union[array, _float]) - - - Return self>=value. - - - .. method:: __iter__(self) - - - - .. method:: __getitem__(self, index: _Index) - - - Retrieve an element of the array. - - - .. method:: __setitem__(self, index: _Index, value: Union[array, _float]) - - - Set an element of the array. - - - -.. data:: _ArrayLike - - - `ulab.array`, ``List[float]``, ``Tuple[float]`` or `range` - - -.. data:: int8 - :annotation: :_DType - - Type code for signed integers in the range -128 .. 127 inclusive, like the 'b' typecode of `array.array` - - -.. data:: int16 - :annotation: :_DType - - Type code for signed integers in the range -32768 .. 32767 inclusive, like the 'h' typecode of `array.array` - - -.. data:: float - :annotation: :_DType - - Type code for floating point values, like the 'f' typecode of `array.array` - - -.. data:: uint8 - :annotation: :_DType - - Type code for unsigned integers in the range 0 .. 255 inclusive, like the 'H' typecode of `array.array` - - -.. data:: uint16 - :annotation: :_DType - - Type code for unsigned integers in the range 0 .. 65535 inclusive, like the 'h' typecode of `array.array` - - -.. data:: bool - :annotation: :_DType - - Type code for boolean values - - -.. function:: get_printoptions() -> Dict[str, int] - - Get printing options - - -.. function:: set_printoptions(threshold: Optional[int] = None, edgeitems: Optional[int] = None) -> None - - Set printing options - - -.. function:: ndinfo(array: ulab.array) -> None - - -.. function:: arange(stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.array - - -.. function:: arange(start: _float, stop: _float, step: _float = 1, *, dtype: _DType = ulab.float) -> ulab.array - - .. param: start - First value in the array, optional, defaults to 0 - .. param: stop - Final value in the array - .. param: step - Difference between consecutive elements, optional, defaults to 1.0 - .. param: dtype - Type of values in the array - - Return a new 1-D array with elements ranging from ``start`` to ``stop``, with step size ``step``. - - -.. function:: concatenate(arrays: Tuple[ulab.array], *, axis: int = 0) -> ulab.array - - .. param: arrays - tuple of ndarrays - .. param: axis - axis along which the arrays will be joined - - Join a sequence of arrays along an existing axis. - - -.. function:: diag(a: ulab.array, *, k: int = 0) -> ulab.array - - .. param: a - an ndarray - .. param: k - Offset of the diagonal from the main diagonal. Can be positive or negative. - - Return specified diagonals. - - -.. function:: eye(size: int, *, M: Optional[int] = None, k: int = 0, dtype: _DType = ulab.float) -> ulab.array - - Return a new square array of size, with the diagonal elements set to 1 - and the other elements set to 0. - - -.. function:: full(shape: Union[int, Tuple[int, ...]], fill_value: Union[_float, _bool], *, dtype: _DType = ulab.float) -> ulab.array - - .. param: shape - Shape of the array, either an integer (for a 1-D array) or a tuple of integers (for tensors of higher rank) - .. param: fill_value - scalar, the value with which the array is filled - .. param: dtype - Type of values in the array - - Return a new array of the given shape with all elements set to 0. - - -.. function:: linspace(start: _float, stop: _float, *, dtype: _DType = ulab.float, num: int = 50, endpoint: _bool = True, retstep: _bool = False) -> ulab.array - - .. param: start - First value in the array - .. param: stop - Final value in the array - .. param int: num - Count of values in the array. - .. param: dtype - Type of values in the array - .. param bool: endpoint - Whether the ``stop`` value is included. Note that even when - endpoint=True, the exact ``stop`` value may not be included due to the - inaccuracy of floating point arithmetic. - If True, return (`samples`, `step`), where `step` is the spacing between samples. - - Return a new 1-D array with ``num`` elements ranging from ``start`` to ``stop`` linearly. - - -.. function:: logspace(start: _float, stop: _float, *, dtype: _DType = ulab.float, num: int = 50, endpoint: _bool = True, base: _float = 10.0) -> ulab.array - - .. param: start - First value in the array - .. param: stop - Final value in the array - .. param int: num - Count of values in the array. Defaults to 50. - .. param: base - The base of the log space. The step size between the elements in - ``ln(samples) / ln(base)`` (or ``log_base(samples)``) is uniform. Defaults to 10.0. - .. param: dtype - Type of values in the array - .. param bool: endpoint - Whether the ``stop`` value is included. Note that even when - endpoint=True, the exact ``stop`` value may not be included due to the - inaccuracy of floating point arithmetic. Defaults to True. - - Return a new 1-D array with ``num`` evenly spaced elements on a log scale. - The sequence starts at ``base ** start``, and ends with ``base ** stop``. - - -.. function:: ones(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.array - - .. param: shape - Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) - .. param: dtype - Type of values in the array - - Return a new array of the given shape with all elements set to 1. - - -.. function:: zeros(shape: Union[int, Tuple[int, ...]], *, dtype: _DType = ulab.float) -> ulab.array - - .. param: shape - Shape of the array, either an integer (for a 1-D array) or a tuple of 2 integers (for a 2-D array) - .. param: dtype - Type of values in the array - - Return a new array of the given shape with all elements set to 0. - - diff --git a/docs/manual/source/ulab/linalg/index.rst b/docs/manual/source/ulab/linalg/index.rst deleted file mode 100644 index 6d76425..0000000 --- a/docs/manual/source/ulab/linalg/index.rst +++ /dev/null @@ -1,67 +0,0 @@ -:mod:`ulab.linalg` -================== - -.. py:module:: ulab.linalg - -.. autoapi-nested-parse:: - - Linear algebra functions - - - -.. function:: cholesky(A: ulab.array) -> ulab.array - - :param ~ulab.array A: a positive definite, symmetric square matrix - :return ~ulab.array L: a square root matrix in the lower triangular form - :raises ValueError: If the input does not fulfill the necessary conditions - - The returned matrix satisfies the equation m=LL* - - -.. function:: det(m: ulab.array) -> float - - :param: m, a square matrix - :return float: The determinant of the matrix - - Computes the eigenvalues and eigenvectors of a square matrix - - -.. function:: dot(m1: ulab.array, m2: ulab.array) -> Union[ulab.array, float] - - :param ~ulab.array m1: a matrix, or a vector - :param ~ulab.array m2: a matrix, or a vector - - Computes the product of two matrices, or two vectors. In the letter case, the inner product is returned. - - -.. function:: eig(m: ulab.array) -> Tuple[ulab.array, ulab.array] - - :param m: a square matrix - :return tuple (eigenvectors, eigenvalues): - - Computes the eigenvalues and eigenvectors of a square matrix - - -.. function:: inv(m: ulab.array) -> ulab.array - - :param ~ulab.array m: a square matrix - :return: The inverse of the matrix, if it exists - :raises ValueError: if the matrix is not invertible - - Computes the inverse of a square matrix - - -.. function:: norm(x: ulab.array) -> float - - :param ~ulab.array x: a vector or a matrix - - Computes the 2-norm of a vector or a matrix, i.e., ``sqrt(sum(x*x))``, however, without the RAM overhead. - - -.. function:: trace(m: ulab.array) -> float - - :param m: a square matrix - - Compute the trace of the matrix, the sum of its diagonal elements. - - diff --git a/docs/manual/source/ulab/numerical/index.rst b/docs/manual/source/ulab/numerical/index.rst deleted file mode 100644 index dc5bcdd..0000000 --- a/docs/manual/source/ulab/numerical/index.rst +++ /dev/null @@ -1,89 +0,0 @@ -:mod:`ulab.numerical` -===================== - -.. py:module:: ulab.numerical - -.. autoapi-nested-parse:: - - Numerical and Statistical functions - - Most of these functions take an "axis" argument, which indicates whether to - operate over the flattened array (None), or a particular axis (integer). - - - -.. function:: argmax(array: _ArrayLike, *, axis: Optional[int] = None) -> int - - Return the index of the maximum element of the 1D array - - -.. function:: argmin(array: _ArrayLike, *, axis: Optional[int] = None) -> int - - Return the index of the minimum element of the 1D array - - -.. function:: argsort(array: ulab.array, *, axis: int = -1) -> ulab.array - - Returns an array which gives indices into the input array from least to greatest. - - -.. function:: cross(a: ulab.array, b: ulab.array) -> ulab.array - - Return the cross product of two vectors of length 3 - - -.. function:: diff(array: ulab.array, *, n: int = 1, axis: int = -1) -> ulab.array - - Return the numerical derivative of successive elements of the array, as - an array. axis=None is not supported. - - -.. function:: flip(array: ulab.array, *, axis: Optional[int] = None) -> ulab.array - - Returns a new array that reverses the order of the elements along the - given axis, or along all axes if axis is None. - - -.. function:: max(array: _ArrayLike, *, axis: Optional[int] = None) -> float - - Return the maximum element of the 1D array - - -.. function:: mean(array: _ArrayLike, *, axis: Optional[int] = None) -> float - - Return the mean element of the 1D array, as a number if axis is None, otherwise as an array. - - -.. function:: median(array: ulab.array, *, axis: int = -1) -> ulab.array - - Find the median value in an array along the given axis, or along all axes if axis is None. - - -.. function:: min(array: _ArrayLike, *, axis: Optional[int] = None) -> float - - Return the minimum element of the 1D array - - -.. function:: roll(array: ulab.array, distance: int, *, axis: Optional[int] = None) -> None - - Shift the content of a vector by the positions given as the second - argument. If the ``axis`` keyword is supplied, the shift is applied to - the given axis. The array is modified in place. - - -.. function:: sort(array: ulab.array, *, axis: int = -1) -> ulab.array - - Sort the array along the given axis, or along all axes if axis is None. - The array is modified in place. - - -.. function:: std(array: _ArrayLike, *, axis: Optional[int] = None, ddof: int = 0) -> float - - Return the standard deviation of the array, as a number if axis is None, otherwise as an array. - - -.. function:: sum(array: _ArrayLike, *, axis: Optional[int] = None) -> Union[float, int, ulab.array] - - Return the sum of the array, as a number if axis is None, otherwise as an array. - - diff --git a/docs/manual/source/ulab/poly/index.rst b/docs/manual/source/ulab/poly/index.rst deleted file mode 100644 index ee08f9a..0000000 --- a/docs/manual/source/ulab/poly/index.rst +++ /dev/null @@ -1,25 +0,0 @@ -:mod:`ulab.poly` -================ - -.. py:module:: ulab.poly - -.. autoapi-nested-parse:: - - Polynomial functions - - - -.. function:: polyfit(y: _ArrayLike, degree: int) -> ulab.array - - -.. function:: polyfit(x: _ArrayLike, y: _ArrayLike, degree: int) -> ulab.array - - Return a polynomial of given degree that approximates the function - f(x)=y. If x is not supplied, it is the range(len(y)). - - -.. function:: polyval(p: _ArrayLike, x: _ArrayLike) -> ulab.array - - Evaluate the polynomial p at the points x. x must be an array. - - diff --git a/docs/manual/source/ulab/user/index.rst b/docs/manual/source/ulab/user/index.rst deleted file mode 100644 index ea6f94e..0000000 --- a/docs/manual/source/ulab/user/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -:mod:`ulab.user` -================ - -.. py:module:: ulab.user - -.. autoapi-nested-parse:: - - This module should hold arbitrary user-defined functions. - - - diff --git a/docs/manual/source/ulab/vector/index.rst b/docs/manual/source/ulab/vector/index.rst deleted file mode 100644 index ee66a0c..0000000 --- a/docs/manual/source/ulab/vector/index.rst +++ /dev/null @@ -1,167 +0,0 @@ -:mod:`ulab.vector` -================== - -.. py:module:: ulab.vector - -.. autoapi-nested-parse:: - - Element-by-element functions - - These functions can operate on numbers, 1-D iterables, 1-D arrays, or 2-D arrays by - applying the function to every element in the array. This is typically - much more efficient than expressing the same operation as a Python loop. - - - -.. function:: acos(a: _ArrayLike) -> ulab.array - - Computes the inverse cosine function - - -.. function:: acosh(a: _ArrayLike) -> ulab.array - - Computes the inverse hyperbolic cosine function - - -.. function:: asin(a: _ArrayLike) -> ulab.array - - Computes the inverse sine function - - -.. function:: asinh(a: _ArrayLike) -> ulab.array - - Computes the inverse hyperbolic sine function - - -.. function:: around(a: _ArrayLike, *, decimals: int = 0) -> ulab.array - - Returns a new float array in which each element is rounded to - ``decimals`` places. - - -.. function:: atan(a: _ArrayLike) -> ulab.array - - Computes the inverse tangent function; the return values are in the - range [-pi/2,pi/2]. - - -.. function:: arctan2(ya: _ArrayLike, xa: _ArrayLike) -> ulab.array - - Computes the inverse tangent function of y/x; the return values are in - the range [-pi, pi]. - - -.. function:: atanh(a: _ArrayLike) -> ulab.array - - Computes the inverse hyperbolic tangent function - - -.. function:: ceil(a: _ArrayLike) -> ulab.array - - Rounds numbers up to the next whole number - - -.. function:: cos(a: _ArrayLike) -> ulab.array - - Computes the cosine function - - -.. function:: cosh(a: _ArrayLike) -> ulab.array - - Computes the hyperbolic cosine function - - -.. function:: degrees(a: _ArrayLike) -> ulab.array - - Converts angles from radians to degrees - - -.. function:: erf(a: _ArrayLike) -> ulab.array - - Computes the error function, which has applications in statistics - - -.. function:: erfc(a: _ArrayLike) -> ulab.array - - Computes the complementary error function, which has applications in statistics - - -.. function:: exp(a: _ArrayLike) -> ulab.array - - Computes the exponent function. - - -.. function:: expm1(a: _ArrayLike) -> ulab.array - - Computes $e^x-1$. In certain applications, using this function preserves numeric accuracy better than the `exp` function. - - -.. function:: floor(a: _ArrayLike) -> ulab.array - - Rounds numbers up to the next whole number - - -.. function:: gamma(a: _ArrayLike) -> ulab.array - - Computes the gamma function - - -.. function:: lgamma(a: _ArrayLike) -> ulab.array - - Computes the natural log of the gamma function - - -.. function:: log(a: _ArrayLike) -> ulab.array - - Computes the natural log - - -.. function:: log10(a: _ArrayLike) -> ulab.array - - Computes the log base 10 - - -.. function:: log2(a: _ArrayLike) -> ulab.array - - Computes the log base 2 - - -.. function:: radians(a: _ArrayLike) -> ulab.array - - Converts angles from degrees to radians - - -.. function:: sin(a: _ArrayLike) -> ulab.array - - Computes the sine function - - -.. function:: sinh(a: _ArrayLike) -> ulab.array - - Computes the hyperbolic sine - - -.. function:: sqrt(a: _ArrayLike) -> ulab.array - - Computes the square root - - -.. function:: tan(a: _ArrayLike) -> ulab.array - - Computes the tangent - - -.. function:: tanh(a: _ArrayLike) -> ulab.array - - Computes the hyperbolic tangent - - -.. function:: vectorize(f: Union[Callable[[int], float], Callable[[float], float]], *, otypes: Optional[_DType] = None) -> Callable[[_ArrayLike], ulab.array] - - :param callable f: The function to wrap - :param otypes: List of array types that may be returned by the function. None is interpreted to mean the return value is float. - - Wrap a Python function ``f`` so that it can be applied to arrays. - The callable must return only values of the types specified by ``otypes``, or the result is undefined. - - diff --git a/docs/ulab-fft.ipynb b/docs/numpy-fft.ipynb similarity index 81% rename from docs/ulab-fft.ipynb rename to docs/numpy-fft.ipynb index ab4a078..ac23048 100644 --- a/docs/ulab-fft.ipynb +++ b/docs/numpy-fft.ipynb @@ -225,7 +225,10 @@ "source": [ "# Fourier transforms\n", "\n", - "Functions related to Fourier transforms can be called by importing the `fft` sub-module first.\n", + "Functions related to Fourier transforms can be called by prepending them with `numpy.fft.`. The module defines the following two functions:\n", + "\n", + "1. [numpy.fft.fft](#fft)\n", + "1. [numpy.fft.ifft](#ifft)\n", "\n", "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.fft.ifft.html\n", "\n", @@ -264,7 +267,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**WARNING:** The array that is returned is also complex, i.e., the real and imaginary components are cast together. In `ulab`, the real and imaginary parts are treated separately: you have to pass two `ndarray`s to the function, although, the second argument is optional, in which case the imaginary part is assumed to be zero.\n", + "**WARNING:** The array returned is also complex, i.e., the real and imaginary components are cast together. In `ulab`, the real and imaginary parts are treated separately: you have to pass two `ndarray`s to the function, although, the second argument is optional, in which case the imaginary part is assumed to be zero.\n", "\n", "**WARNING:** The function, as opposed to `numpy`, returns a 2-tuple, whose elements are two `ndarray`s, holding the real and imaginary parts of the transform separately. " ] @@ -297,19 +300,17 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", - "from ulab import fft\n", + "from ulab import numpy as np\n", "\n", "x = np.linspace(0, 10, num=1024)\n", - "y = vector.sin(x)\n", + "y = np.sin(x)\n", "z = np.zeros(len(x))\n", "\n", - "a, b = fft.fft(x)\n", + "a, b = np.fft.fft(x)\n", "print('real part:\\t', a)\n", "print('\\nimaginary part:\\t', b)\n", "\n", - "c, d = fft.fft(x, z)\n", + "c, d = np.fft.fft(x, z)\n", "print('\\nreal part:\\t', c)\n", "print('\\nimaginary part:\\t', d)" ] @@ -349,18 +350,16 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", - "from ulab import fft\n", + "from ulab import numpy as np\n", "\n", "x = np.linspace(0, 10, num=1024)\n", - "y = vector.sin(x)\n", + "y = np.sin(x)\n", "\n", - "a, b = fft.fft(y)\n", + "a, b = np.fft.fft(y)\n", "\n", "print('original vector:\\t', y)\n", "\n", - "y, z = fft.ifft(a, b)\n", + "y, z = np.fft.ifft(a, b)\n", "# the real part should be equal to y\n", "print('\\nreal part of inverse:\\t', y)\n", "# the imaginary part should be equal to zero\n", @@ -374,101 +373,6 @@ "Note that unlike in `numpy`, the length of the array on which the Fourier transform is carried out must be a power of 2. If this is not the case, the function raises a `ValueError` exception." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## spectrogram\n", - "\n", - "In addition to the Fourier transform and its inverse, `ulab` also sports a function called `spectrogram`, which returns the absolute value of the Fourier transform. This could be used to find the dominant spectral component in a time series. The arguments are treated in the same way as in `fft`, and `ifft`." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "ExecuteTime": { - "end_time": "2020-03-10T20:09:54.473041Z", - "start_time": "2020-03-10T20:09:54.457178Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "original vector:\t array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893639], dtype=float)\n", - "\n", - "spectrum:\t array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "from ulab import vector\n", - "from ulab import fft\n", - "\n", - "x = np.linspace(0, 10, num=1024)\n", - "y = vector.sin(x)\n", - "\n", - "a = fft.spectrogram(y)\n", - "\n", - "print('original vector:\\t', y)\n", - "print('\\nspectrum:\\t', a)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As such, `spectrogram` is really just a shorthand for `np.sqrt(a*a + b*b)`:" - ] - }, - { - "cell_type": "code", - "execution_count": 461, - "metadata": { - "ExecuteTime": { - "end_time": "2019-10-19T13:08:58.196537Z", - "start_time": "2019-10-19T13:08:58.155036Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "spectrum calculated the hard way:\t array([187.8641, 315.3125, 347.8804, ..., 84.4587, 347.8803, 315.3124], dtype=float)\n", - "\n", - "spectrum calculated the lazy way:\t array([187.8641, 315.3125, 347.8804, ..., 84.4587, 347.8803, 315.3124], dtype=float)\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -pyboard 1\n", - "\n", - "import ulab as np\n", - "from ulab import fft\n", - "from ulab import vector\n", - "\n", - "x = np.linspace(0, 10, num=1024)\n", - "y = vector.sin(x)\n", - "\n", - "a, b = fft.fft(y)\n", - "\n", - "print('\\nspectrum calculated the hard way:\\t', vector.sqrt(a*a + b*b))\n", - "\n", - "a = fft.spectrogram(y)\n", - "\n", - "print('\\nspectrum calculated the lazy way:\\t', a)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -555,7 +459,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/numpy-functions.ipynb b/docs/numpy-functions.ipynb new file mode 100644 index 0000000..52994cd --- /dev/null +++ b/docs/numpy-functions.ipynb @@ -0,0 +1,1600 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T15:57:33.452434Z", + "start_time": "2021-01-13T15:57:33.236531Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T15:57:34.464768Z", + "start_time": "2021-01-13T15:57:34.457873Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T15:57:35.036920Z", + "start_time": "2021-01-13T15:57:34.993058Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Numpy functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This section of the manual discusses those functions that were adapted from `numpy`.\n", + "\n", + "1. [numpy.argmax](#argmax)\n", + "1. [numpy.argmin](#argmin)\n", + "1. [numpy.argsort](#argsort)\n", + "1. [numpy.clip](#clip)\n", + "1. [numpy.convolve](#convolve)\n", + "1. [numpy.diff](#diff)\n", + "1. [numpy.equal](#equal)\n", + "1. [numpy.flip](#flip)\n", + "1. [numpy.interp](#interp)\n", + "1. [numpy.max](#max)\n", + "1. [numpy.maximum](#maximum)\n", + "1. [numpy.mean](#mean)\n", + "1. [numpy.median](#median)\n", + "1. [numpy.min](#min)\n", + "1. [numpy.minimum](#minimum)\n", + "1. [numpy.not_equal](#not_equal)\n", + "1. [numpy.polyfit](#polyfit)\n", + "1. [numpy.polyval](#polyval)\n", + "1. [numpy.roll](#roll)\n", + "1. [numpy.sort](#sort)\n", + "1. [numpy.std](#std)\n", + "1. [numpy.sum](#sum)\n", + "1. [numpy.trapz](#trapz)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argmax\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argmin\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## argsort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html\n", + "\n", + "Similarly to [sort](#sort), `argsort` takes a positional, and a keyword argument, and returns an unsigned short index array of type `ndarray` with the same dimensions as the input, or, if `axis=None`, as a row vector with length equal to the number of elements in the input (i.e., the flattened array). The indices in the output sort the input in ascending order. The routine in `argsort` is the same as in `sort`, therefore, the comments on computational expenses (time and RAM) also apply. In particular, since no copy of the original data is required, virtually no RAM beyond the output array is used. \n", + "\n", + "Since the underlying container of the output array is of type `uint16_t`, neither of the output dimensions should be larger than 65535. If that happens to be the case, the function will bail out with a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:33:33.292717Z", + "start_time": "2021-01-13T16:33:33.280144Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + " [5.0, 3.0, 4.0, 1.0],\n", + " [9.0, 11.0, 1.0, 8.0],\n", + " [7.0, 10.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[0, 1, 3, 0],\n", + " [1, 3, 2, 1],\n", + " [3, 2, 0, 3],\n", + " [2, 0, 1, 2]], dtype=uint16)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[3, 0, 2, 1],\n", + " [3, 1, 2, 0],\n", + " [2, 3, 0, 1],\n", + " [2, 3, 0, 1]], dtype=uint16)\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 12, in \n", + "NotImplementedError: argsort is not implemented for flattened arrays\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = np.argsort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = np.argsort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = np.argsort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since during the sorting, only the indices are shuffled, `argsort` does not modify the input array, as one can verify this by the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:34:48.446211Z", + "start_time": "2021-01-13T16:34:48.424276Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "sorting indices:\n", + " array([0, 2, 4, 3, 5, 1], dtype=uint16)\n", + "\n", + "the original array:\n", + " array([0, 5, 1, 3, 2, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, 5, 1, 3, 2, 4], dtype=np.uint8)\n", + "print('\\na:\\n', a)\n", + "b = np.argsort(a, axis=0)\n", + "print('\\nsorting indices:\\n', b)\n", + "print('\\nthe original array:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## clip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html\n", + "\n", + "Clips an array, i.e., values that are outside of an interval are clipped to the interval edges. The function is equivalent to `maximum(a_min, minimum(a, a_max))` broadcasting takes place exactly as in [minimum](#minimum). If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:22:14.147310Z", + "start_time": "2021-01-08T13:22:14.123961Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "clipped:\t array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8)\n", + "\n", + "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "b:\t\t array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float64)\n", + "clipped:\t array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "print('a:\\t\\t', a)\n", + "print('clipped:\\t', np.clip(a, 3, 7))\n", + "\n", + "b = 3 * np.ones(len(a), dtype=np.float)\n", + "print('\\na:\\t\\t', a)\n", + "print('b:\\t\\t', b)\n", + "print('clipped:\\t', np.clip(a, b, 7))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## convolve\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html\n", + "\n", + "Returns the discrete, linear convolution of two one-dimensional arrays.\n", + "\n", + "Only the ``full`` mode is supported, and the ``mode`` named parameter is not accepted. Note that all other modes can be had by slicing a ``full`` result." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T15:57:39.028884Z", + "start_time": "2021-01-13T15:57:39.008749Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array((1, 2, 3))\n", + "y = np.array((1, 10, 100, 1000))\n", + "\n", + "print(np.convolve(x, y))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## diff\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.diff.html\n", + "\n", + "The `diff` function returns the numerical derivative of the forward scheme, or more accurately, the differences of an `ndarray` along a given axis. The order of derivative can be stipulated with the `n` keyword argument, which should be between 0, and 9. Default is 1. If higher order derivatives are required, they can be gotten by repeated calls to the function. The `axis` keyword argument should be -1 (last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, or 1. \n", + "\n", + "Beyond the output array, the function requires only a couple of bytes of extra RAM for the differentiation stencil. (The stencil is an `int8` array, one byte longer than `n`. This also explains, why the highest order is 9: the coefficients of a ninth-order stencil all fit in signed bytes, while 10 would require `int16`.) Note that as usual in numerical differentiation (and also in `numpy`), the length of the respective axis will be reduced by `n` after the operation. If `n` is larger than, or equal to the length of the axis, an empty array will be returned.\n", + "\n", + "**WARNING**: the `diff` function does not implement the `prepend` and `append` keywords that can be found in `numpy`. " + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-14T16:06:27.468909Z", + "start_time": "2021-01-14T16:06:27.439067Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([0, 1, 2, 10, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "first derivative:\n", + " array([1, 1, 8, 250, 1, 1, 1, 1], dtype=uint8)\n", + "\n", + "second derivative:\n", + " array([0, 249, 14, 249, 0, 0, 0], dtype=uint8)\n", + "\n", + "c:\n", + " array([[1.0, 2.0, 3.0, 4.0],\n", + " [4.0, 3.0, 2.0, 1.0],\n", + " [1.0, 4.0, 9.0, 16.0],\n", + " [0.0, 0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "first derivative, first axis:\n", + " array([[3.0, 1.0, -1.0, -3.0],\n", + " [-3.0, 1.0, 7.0, 15.0],\n", + " [-1.0, -4.0, -9.0, -16.0]], dtype=float64)\n", + "\n", + "first derivative, second axis:\n", + " array([[1.0, 1.0, 1.0],\n", + " [-1.0, -1.0, -1.0],\n", + " [3.0, 5.0, 7.0],\n", + " [0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "a[3] = 10\n", + "print('a:\\n', a)\n", + "\n", + "print('\\nfirst derivative:\\n', np.diff(a, n=1))\n", + "print('\\nsecond derivative:\\n', np.diff(a, n=2))\n", + "\n", + "c = np.array([[1, 2, 3, 4], [4, 3, 2, 1], [1, 4, 9, 16], [0, 0, 0, 0]])\n", + "print('\\nc:\\n', c)\n", + "print('\\nfirst derivative, first axis:\\n', np.diff(c, axis=0))\n", + "print('\\nfirst derivative, second axis:\\n', np.diff(c, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## equal\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.equal.html\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.not_equal.html\n", + "\n", + "In `micropython`, equality of arrays or scalars can be established by utilising the `==`, `!=`, `<`, `>`, `<=`, or `=>` binary operators. In `circuitpython`, `==` and `!=` will produce unexpected results. In order to avoid this discrepancy, and to maintain compatibility with `numpy`, `ulab` implements the `equal` and `not_equal` operators that return the same results, irrespective of the `python` implementation.\n", + "\n", + "These two functions take two `ndarray`s, or scalars as their arguments. No keyword arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T14:22:13.990898Z", + "start_time": "2021-01-08T14:22:13.941896Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float64)\n", + "\n", + "a == b: array([True, False, False, False, False, False, False, False, False], dtype=bool)\n", + "a != b: array([False, True, True, True, True, True, True, True, True], dtype=bool)\n", + "a == 2: array([False, False, True, False, False, False, False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9))\n", + "b = np.zeros(9)\n", + "\n", + "print('a: ', a)\n", + "print('b: ', b)\n", + "print('\\na == b: ', np.equal(a, b))\n", + "print('a != b: ', np.not_equal(a, b))\n", + "\n", + "# comparison with scalars\n", + "print('a == 2: ', np.equal(a, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## flip\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.flip.html\n", + "\n", + "The `flip` function takes one positional, an `ndarray`, and one keyword argument, `axis = None`, and reverses the order of elements along the given axis. If the keyword argument is `None`, the matrix' entries are flipped along all axes. `flip` returns a new copy of the array." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:25:08.425583Z", + "start_time": "2021-01-13T16:25:08.407004Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \t array([1.0, 2.0, 3.0, 4.0, 5.0], dtype=float64)\n", + "a flipped:\t array([5.0, 4.0, 3.0, 2.0, 1.0], dtype=float64)\n", + "\n", + "a flipped horizontally\n", + " array([[3, 2, 1],\n", + " [6, 5, 4],\n", + " [9, 8, 7]], dtype=uint8)\n", + "\n", + "a flipped vertically\n", + " array([[7, 8, 9],\n", + " [4, 5, 6],\n", + " [1, 2, 3]], dtype=uint8)\n", + "\n", + "a flipped horizontally+vertically\n", + " array([9, 8, 7, 6, 5, 4, 3, 2, 1], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "print(\"a: \\t\", a)\n", + "print(\"a flipped:\\t\", np.flip(a))\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print(\"\\na flipped horizontally\\n\", np.flip(a, axis=1))\n", + "print(\"\\na flipped vertically\\n\", np.flip(a, axis=0))\n", + "print(\"\\na flipped horizontally+vertically\\n\", np.flip(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## interp\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/numpy.interp\n", + "\n", + "The `interp` function returns the linearly interpolated values of a one-dimensional numerical array. It requires three positional arguments,`x`, at which the interpolated values are evaluated, `xp`, the array\n", + "of the independent data variable, and `fp`, the array of the dependent values of the data. `xp` must be a monotonically increasing sequence of numbers.\n", + "\n", + "Two keyword arguments, `left`, and `right` can also be supplied; these determine the return values, if `x < xp[0]`, and `x > xp[-1]`, respectively. If these arguments are not supplied, `left`, and `right` default to `fp[0]`, and `fp[-1]`, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:00:43.505722Z", + "start_time": "2021-01-13T16:00:43.489060Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", + "array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([1, 2, 3, 4, 5]) - 0.2\n", + "xp = np.array([1, 2, 3, 4])\n", + "fp = np.array([1, 2, 3, 5])\n", + "\n", + "print(x)\n", + "print(np.interp(x, xp, fp))\n", + "print(np.interp(x, xp, fp, left=0.0))\n", + "print(np.interp(x, xp, fp, right=10.0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## mean\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:15:39.921212Z", + "start_time": "2021-01-13T16:15:39.908217Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "mean, flat: 5.0\n", + "mean, horizontal: array([2.0, 5.0, 8.0], dtype=float64)\n", + "mean, vertical: array([4.0, 5.0, 6.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "print('mean, flat: ', np.mean(a))\n", + "print('mean, horizontal: ', np.mean(a, axis=1))\n", + "print('mean, vertical: ', np.mean(a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## max\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.max.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmax.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.argmin.html\n", + "\n", + "**WARNING:** Difference to `numpy`: the `out` keyword argument is not implemented.\n", + "\n", + "These functions follow the same pattern, and work with generic iterables, and `ndarray`s. `min`, and `max` return the minimum or maximum of a sequence. If the input array is two-dimensional, the `axis` keyword argument can be supplied, in which case the minimum/maximum along the given axis will be returned. If `axis=None` (this is also the default value), the minimum/maximum of the flattened array will be determined.\n", + "\n", + "`argmin/argmax` return the position (index) of the minimum/maximum in the sequence." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:08:56.986619Z", + "start_time": "2021-01-13T16:08:56.964492Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1.0, 2.0, 0.0, 1.0, 10.0], dtype=float64)\n", + "min of a: 0.0\n", + "argmin of a: 2\n", + "\n", + "b:\n", + " array([[1.0, 2.0, 0.0],\n", + " [1.0, 10.0, -1.0]], dtype=float64)\n", + "min of b (flattened): -1.0\n", + "min of b (axis=0): array([1.0, 2.0, -1.0], dtype=float64)\n", + "min of b (axis=1): array([0.0, -1.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 0, 1, 10])\n", + "print('a:', a)\n", + "print('min of a:', np.min(a))\n", + "print('argmin of a:', np.argmin(a))\n", + "\n", + "b = np.array([[1, 2, 0], [1, 10, -1]])\n", + "print('\\nb:\\n', b)\n", + "print('min of b (flattened):', np.min(b))\n", + "print('min of b (axis=0):', np.min(b, axis=0))\n", + "print('min of b (axis=1):', np.min(b, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## median\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.median.html\n", + "\n", + "The function computes the median along the specified axis, and returns the median of the array elements. If the `axis` keyword argument is `None`, the arrays is flattened first. The `dtype` of the results is always float." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:31:13.833800Z", + "start_time": "2021-01-13T16:31:13.809560Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2, 3],\n", + " [4, 5, 6, 7],\n", + " [8, 9, 10, 11]], dtype=int8)\n", + "\n", + "median of the flattened array: 5.5\n", + "\n", + "median along the vertical axis: array([4.0, 5.0, 6.0, 7.0], dtype=float64)\n", + "\n", + "median along the horizontal axis: array([1.5, 5.5, 9.5], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12), dtype=np.int8).reshape((3, 4))\n", + "print('a:\\n', a)\n", + "print('\\nmedian of the flattened array: ', np.median(a))\n", + "print('\\nmedian along the vertical axis: ', np.median(a, axis=0))\n", + "print('\\nmedian along the horizontal axis: ', np.median(a, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## min\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.min.html\n", + "\n", + "See [numpy.max](#max)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## minimum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html\n", + "\n", + "Returns the minimum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented.\n", + "\n", + "## maximum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html\n", + "\n", + "Returns the maximum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:21:17.151280Z", + "start_time": "2021-01-08T13:21:17.123768Z" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "minimum of a, and b:\n", + "array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float64)\n", + "\n", + "maximum of a, and b:\n", + "array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float64)\n", + "\n", + "maximum of 1, and 5.5:\n", + "5.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([5, 4, 3, 2, 1], dtype=np.float)\n", + "print('minimum of a, and b:')\n", + "print(np.minimum(a, b))\n", + "\n", + "print('\\nmaximum of a, and b:')\n", + "print(np.maximum(a, b))\n", + "\n", + "print('\\nmaximum of 1, and 5.5:')\n", + "print(np.maximum(1, 5.5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## not_equal\n", + "\n", + "See [numpy.equal](#equal)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyfit\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyfit.html\n", + "\n", + "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))`.\n", + "\n", + "If the lengths of `x`, and `y` are not the same, the function raises a `ValueError`." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:23:39.238450Z", + "start_time": "2021-01-13T18:23:39.221063Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "independent values:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64)\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float64)\n", + "\n", + "dependent values:\t array([9.0, 4.0, 1.0, 0.0, 1.0, 4.0, 9.0], dtype=float64)\n", + "fitted values:\t\t array([1.0, -6.0, 9.000000000000004], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "print('independent values:\\t', x)\n", + "print('dependent values:\\t', y)\n", + "print('fitted values:\\t\\t', np.polyfit(x, y, 2))\n", + "\n", + "# the same with missing x\n", + "print('\\ndependent values:\\t', y)\n", + "print('fitted values:\\t\\t', np.polyfit(y, 2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Execution time\n", + "\n", + "`polyfit` is based on the inversion of a matrix (there is more on the background in https://en.wikipedia.org/wiki/Polynomial_regression), and it requires the intermediate storage of `2*N*(deg+1)` floats, where `N` is the number of entries in the input array, and `deg` is the fit's degree. The additional computation costs of the matrix inversion discussed in [linalg.inv](#inv) also apply. The example from above needs around 150 microseconds to return:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:31:40.919764Z", + "start_time": "2021-01-13T18:31:40.912817Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "execution time: 153 us\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def time_polyfit(x, y, n):\n", + " return np.polyfit(x, y, n)\n", + "\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6])\n", + "y = np.array([9, 4, 1, 0, 1, 4, 9])\n", + "\n", + "time_polyfit(x, y, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## polyval\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.polyval.html\n", + "\n", + "`polyval` takes two arguments, both arrays or generic `micropython` iterables returning scalars." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:12:56.736643Z", + "start_time": "2021-01-13T18:12:56.668042Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "coefficients: [1, 1, 1, 0]\n", + "independent values: [0, 1, 2, 3, 4]\n", + "\n", + "values of p(x): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64)\n", + "\n", + "ndarray (a): array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "value of p(a): array([0.0, 3.0, 14.0, 39.0, 84.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "p = [1, 1, 1, 0]\n", + "x = [0, 1, 2, 3, 4]\n", + "print('coefficients: ', p)\n", + "print('independent values: ', x)\n", + "print('\\nvalues of p(x): ', np.polyval(p, x))\n", + "\n", + "# the same works with one-dimensional ndarrays\n", + "a = np.array(x)\n", + "print('\\nndarray (a): ', a)\n", + "print('value of p(a): ', np.polyval(p, a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## roll\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.roll.html\n", + "\n", + "The roll function shifts the content of a vector by the positions given as the second argument. If the `axis` keyword is supplied, the shift is applied to the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:18:30.387043Z", + "start_time": "2021-01-13T16:18:30.363374Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "a rolled to the left:\t array([7.0, 8.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0], dtype=float64)\n", + "a rolled to the right:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8])\n", + "print(\"a:\\t\\t\\t\", a)\n", + "\n", + "a = np.roll(a, 2)\n", + "print(\"a rolled to the left:\\t\", a)\n", + "\n", + "# this should be the original vector\n", + "a = np.roll(a, -2)\n", + "print(\"a rolled to the right:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rolling works with matrices, too. If the `axis` keyword is 0, the matrix is rolled along its vertical axis, otherwise, horizontally. \n", + "\n", + "Horizontal rolls are faster, because they require fewer steps, and larger memory chunks are copied, however, they also require more RAM: basically the whole row must be stored internally. Most expensive are the `None` keyword values, because with `axis = None`, the array is flattened first, hence the row's length is the size of the whole matrix.\n", + "\n", + "Vertical rolls require two internal copies of single columns. " + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:23:52.025977Z", + "start_time": "2021-01-13T16:23:52.001252Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled up:\n", + " array([[4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0],\n", + " [0.0, 1.0, 2.0, 3.0]], dtype=float64)\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled to the left:\n", + " array([[1.0, 2.0, 3.0, 0.0],\n", + " [5.0, 6.0, 7.0, 4.0],\n", + " [9.0, 10.0, 11.0, 8.0]], dtype=float64)\n", + "a:\n", + " array([[0.0, 1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0, 7.0],\n", + " [8.0, 9.0, 10.0, 11.0]], dtype=float64)\n", + "\n", + "a rolled with None:\n", + " array([[11.0, 0.0, 1.0, 2.0],\n", + " [3.0, 4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0, 10.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, 2, axis=0)\n", + "print(\"\\na rolled up:\\n\", a)\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, -1, axis=1)\n", + "print(\"\\na rolled to the left:\\n\", a)\n", + "\n", + "a = np.array(range(12)).reshape((3, 4))\n", + "print(\"a:\\n\", a)\n", + "a = np.roll(a, 1, axis=None)\n", + "print(\"\\na rolled with None:\\n\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html\n", + "\n", + "The sort function takes an ndarray, and sorts its elements in ascending order along the specified axis using a heap sort algorithm. As opposed to the `.sort()` method discussed earlier, this function creates a copy of its input before sorting, and at the end, returns this copy. Sorting takes place in place, without auxiliary storage. The `axis` keyword argument takes on the possible values of -1 (the last axis, in `ulab` equivalent to the second axis, and this also happens to be the default value), 0, 1, or `None`. The first three cases are identical to those in [diff](#diff), while the last one flattens the array before sorting. \n", + "\n", + "If descending order is required, the result can simply be `flip`ped, see [flip](#flip).\n", + "\n", + "**WARNING:** `numpy` defines the `kind`, and `order` keyword arguments that are not implemented here. The function in `ulab` always uses heap sort, and since `ulab` does not have the concept of data fields, the `order` keyword argument would have no meaning." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:32:07.748972Z", + "start_time": "2021-01-13T16:32:07.730498Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1.0, 12.0, 3.0, 0.0],\n", + " [5.0, 3.0, 4.0, 1.0],\n", + " [9.0, 11.0, 1.0, 8.0],\n", + " [7.0, 10.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[1.0, 3.0, 0.0, 0.0],\n", + " [5.0, 10.0, 1.0, 1.0],\n", + " [7.0, 11.0, 3.0, 1.0],\n", + " [9.0, 12.0, 4.0, 8.0]], dtype=float64)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[0.0, 1.0, 3.0, 12.0],\n", + " [1.0, 3.0, 4.0, 5.0],\n", + " [1.0, 8.0, 9.0, 11.0],\n", + " [0.0, 1.0, 7.0, 10.0]], dtype=float64)\n", + "\n", + "flattened a sorted:\n", + " array([0.0, 0.0, 1.0, ..., 10.0, 11.0, 12.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.float)\n", + "print('\\na:\\n', a)\n", + "b = np.sort(a, axis=0)\n", + "print('\\na sorted along vertical axis:\\n', b)\n", + "\n", + "c = np.sort(a, axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', c)\n", + "\n", + "c = np.sort(a, axis=None)\n", + "print('\\nflattened a sorted:\\n', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Heap sort requires $\\sim N\\log N$ operations, and notably, the worst case costs only 20% more time than the average. In order to get an order-of-magnitude estimate, we will take the sine of 1000 uniformly spaced numbers between 0, and two pi, and sort them:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import ulab as np\n", + "from ulab import vector\n", + "from ulab import numerical\n", + "\n", + "@timeit\n", + "def sort_time(array):\n", + " return numerical.sort(array)\n", + "\n", + "b = vector.sin(np.linspace(0, 6.28, num=1000))\n", + "print('b: ', b)\n", + "sort_time(b)\n", + "print('\\nb sorted:\\n', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## std\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:14:54.051061Z", + "start_time": "2021-01-13T16:14:54.029924Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "sum, flat array: 2.581988897471611\n", + "std, vertical: array([2.449489742783178, 2.449489742783178, 2.449489742783178], dtype=float64)\n", + "std, horizonal: array([0.8164965809277261, 0.8164965809277261, 0.8164965809277261], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "print('sum, flat array: ', np.std(a))\n", + "print('std, vertical: ', np.std(a, axis=0))\n", + "print('std, horizonal: ', np.std(a, axis=1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sum\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html\n", + "\n", + "If the axis keyword is not specified, it assumes the default value of `None`, and returns the result of the computation for the flattened array. Otherwise, the calculation is along the given axis." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:14:34.576723Z", + "start_time": "2021-01-13T16:14:34.556304Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \n", + " array([[1.0, 2.0, 3.0],\n", + " [4.0, 5.0, 6.0],\n", + " [7.0, 8.0, 9.0]], dtype=float64)\n", + "sum, flat array: 45.0\n", + "sum, horizontal: array([6.0, 15.0, 24.0], dtype=float64)\n", + "std, vertical: array([12.0, 15.0, 18.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "print('a: \\n', a)\n", + "\n", + "print('sum, flat array: ', np.sum(a))\n", + "print('sum, horizontal: ', np.sum(a, axis=1))\n", + "print('std, vertical: ', np.sum(a, axis=0))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## trapz\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.trapz.html\n", + "\n", + "The function takes one or two one-dimensional `ndarray`s, and integrates the dependent values (`y`) using the trapezoidal rule. If the independent variable (`x`) is given, that is taken as the sample points corresponding to `y`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T16:03:42.566302Z", + "start_time": "2021-01-13T16:03:42.545630Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float64)\n", + "y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float64)\n", + "============================\n", + "integral of y: 244.5\n", + "integral of y at x: 244.5\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "x = np.linspace(0, 9, num=10)\n", + "y = x*x\n", + "\n", + "print('x: ', x)\n", + "print('y: ', y)\n", + "print('============================')\n", + "print('integral of y: ', np.trapz(y))\n", + "print('integral of y at x: ', np.trapz(y, x=x))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/ulab-linalg.ipynb b/docs/numpy-linalg.ipynb similarity index 88% rename from docs/ulab-linalg.ipynb rename to docs/numpy-linalg.ipynb index 81499e7..2a7703f 100644 --- a/docs/ulab-linalg.ipynb +++ b/docs/numpy-linalg.ipynb @@ -5,8 +5,8 @@ "execution_count": 1, "metadata": { "ExecuteTime": { - "end_time": "2020-11-03T20:12:21.089435Z", - "start_time": "2020-11-03T20:12:20.903465Z" + "end_time": "2021-01-13T06:16:40.844266Z", + "start_time": "2021-01-13T06:16:39.992092Z" } }, "outputs": [ @@ -31,11 +31,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T15:58:29.644332Z", - "start_time": "2020-11-18T15:58:29.634658Z" + "end_time": "2021-01-13T06:16:40.857076Z", + "start_time": "2021-01-13T06:16:40.852721Z" } }, "outputs": [], @@ -49,11 +49,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T15:58:30.973054Z", - "start_time": "2020-11-18T15:58:30.944054Z" + "end_time": "2021-01-13T06:16:40.947944Z", + "start_time": "2021-01-13T06:16:40.865720Z" } }, "outputs": [], @@ -225,27 +225,123 @@ "source": [ "# Linalg\n", "\n", - "Functions in the `linalg` module can be called by importing the sub-module first." + "Functions in the `linalg` module can be called by prepending them by `numpy.linalg.`. The module defines the following seven functions:\n", + "\n", + "1. [numpy.linalg.cholesky](#cholesky)\n", + "1. [numpy.linalg.det](#det)\n", + "1. [numpy.linalg.dot](#dot)\n", + "1. [numpy.linalg.eig](#eig)\n", + "1. [numpy.linalg.inv](#inv)\n", + "1. [numpy.linalg.norm](#norm)\n", + "1. [numpy.linalg.trace](#trace)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## inv\n", + "## cholesky\n", "\n", - "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html\n", "\n", - "A square matrix, provided that it is not singular, can be inverted by calling the `inv` function that takes a single argument. The inversion is based on successive elimination of elements in the lower left triangle, and raises a `ValueError` exception, if the matrix turns out to be singular (i.e., one of the diagonal entries is zero)." + "The function of the Cholesky decomposition takes a positive definite, symmetric square matrix as its single argument, and returns the *square root matrix* in the lower triangular form. If the input argument does not fulfill the positivity or symmetry condition, a `ValueError` is raised." ] }, { "cell_type": "code", - "execution_count": 538, + "execution_count": 18, "metadata": { "ExecuteTime": { - "end_time": "2019-10-20T06:55:25.025726Z", - "start_time": "2019-10-20T06:55:24.982557Z" + "end_time": "2020-03-10T19:25:21.754166Z", + "start_time": "2020-03-10T19:25:21.740726Z" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[25.0, 15.0, -5.0],\n", + "\t [15.0, 18.0, 0.0],\n", + "\t [-5.0, 0.0, 11.0]], dtype=float)\n", + "\n", + "====================\n", + "Cholesky decomposition\n", + " array([[5.0, 0.0, 0.0],\n", + "\t [3.0, 3.0, 0.0],\n", + "\t [-1.0, 1.0, 3.0]], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]])\n", + "print('a: ', a)\n", + "print('\\n' + '='*20 + '\\nCholesky decomposition\\n', np.linalg.cholesky(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## det\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html\n", + "\n", + "The `det` function takes a square matrix as its single argument, and calculates the determinant. The calculation is based on successive elimination of the matrix elements, and the return value is a float, even if the input array was of integer type." + ] + }, + { + "cell_type": "code", + "execution_count": 495, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:27:24.246995Z", + "start_time": "2019-10-19T13:27:24.228698Z" + }, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-2.0\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2], [3, 4]], dtype=np.uint8)\n", + "print(np.linalg.det(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmark\n", + "\n", + "Since the routine for calculating the determinant is pretty much the same as for finding the [inverse of a matrix](#inv), the execution times are similar:" + ] + }, + { + "cell_type": "code", + "execution_count": 557, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:14:59.778987Z", + "start_time": "2019-10-20T07:14:59.740021Z" } }, "outputs": [ @@ -253,10 +349,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "array([[-2.166666, 1.499999, -0.8333326, 1.0],\n", - "\t [1.666666, -3.333331, 1.666666, -4.768516e-08],\n", - "\t [0.1666672, 2.166666, -0.8333327, -1.0],\n", - "\t [-0.1666666, -0.3333334, 4.96705e-08, 0.5]], dtype=float)\n", + "execution time: 294 us\n", "\n" ] } @@ -264,80 +357,18 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import linalg\n", - "\n", - "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", - "\n", - "print(linalg.inv(m))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Computation expenses\n", - "\n", - "Note that the cost of inverting a matrix is approximately twice as many floats (RAM), as the number of entries in the original matrix, and approximately as many operations, as the number of entries. Here are a couple of numbers: " - ] - }, - { - "cell_type": "code", - "execution_count": 552, - "metadata": { - "ExecuteTime": { - "end_time": "2019-10-20T07:10:39.190734Z", - "start_time": "2019-10-20T07:10:39.138872Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2 by 2 matrix:\n", - "execution time: 65 us\n", - "\n", - "4 by 4 matrix:\n", - "execution time: 105 us\n", - "\n", - "8 by 8 matrix:\n", - "execution time: 299 us\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -pyboard 1\n", - "\n", - "import ulab as np\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", "@timeit\n", - "def invert_matrix(m):\n", - " return linalg.inv(m)\n", - "\n", - "m = np.array([[1, 2,], [4, 5]])\n", - "print('2 by 2 matrix:')\n", - "invert_matrix(m)\n", - "\n", - "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", - "print('\\n4 by 4 matrix:')\n", - "invert_matrix(m)\n", + "def matrix_det(m):\n", + " return np.linalg.inv(m)\n", "\n", "m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], \n", " [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], \n", " [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], \n", " [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]])\n", - "print('\\n8 by 8 matrix:')\n", - "invert_matrix(m)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The above-mentioned scaling is not obeyed strictly. The reason for the discrepancy is that the function call is still the same for all three cases: the input must be inspected, the output array must be created, and so on. " + "\n", + "matrix_det(m)" ] }, { @@ -387,17 +418,16 @@ } ], "source": [ - "%%micropython -pyboard 1\n", + "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", "m = np.array([[1, 2, 3], [4, 5, 6], [7, 10, 9]], dtype=np.uint8)\n", - "n = linalg.inv(m)\n", + "n = np.linalg.inv(m)\n", "print(\"m:\\n\", m)\n", "print(\"\\nm^-1:\\n\", n)\n", "# this should be the unit matrix\n", - "print(\"\\nm*m^-1:\\n\", linalg.dot(m, n))" + "print(\"\\nm*m^-1:\\n\", np.linalg.dot(m, n))" ] }, { @@ -437,97 +467,13 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", "m = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=np.uint8)\n", "n = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype=np.uint8)\n", "print(m)\n", "print(n)\n", - "print(linalg.dot(m, n))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## det\n", - "\n", - "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.det.html\n", - "\n", - "The `det` function takes a square matrix as its single argument, and calculates the determinant. The calculation is based on successive elimination of the matrix elements, and the return value is a float, even if the input array was of integer type." - ] - }, - { - "cell_type": "code", - "execution_count": 495, - "metadata": { - "ExecuteTime": { - "end_time": "2019-10-19T13:27:24.246995Z", - "start_time": "2019-10-19T13:27:24.228698Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-2.0\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -pyboard 1\n", - "\n", - "import ulab as np\n", - "from ulab import linalg\n", - "\n", - "a = np.array([[1, 2], [3, 4]], dtype=np.uint8)\n", - "print(linalg.det(a))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Benchmark\n", - "\n", - "Since the routine for calculating the determinant is pretty much the same as for finding the [inverse of a matrix](#inv), the execution times are similar:" - ] - }, - { - "cell_type": "code", - "execution_count": 557, - "metadata": { - "ExecuteTime": { - "end_time": "2019-10-20T07:14:59.778987Z", - "start_time": "2019-10-20T07:14:59.740021Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "execution time: 294 us\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -pyboard 1\n", - "\n", - "@timeit\n", - "def matrix_det(m):\n", - " return linalg.inv(m)\n", - "\n", - "m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], \n", - " [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], \n", - " [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], \n", - " [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]])\n", - "\n", - "matrix_det(m)" + "print(np.linalg.dot(m, n))" ] }, { @@ -571,11 +517,10 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", "a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8)\n", - "x, y = linalg.eig(a)\n", + "x, y = np.linalg.eig(a)\n", "print('eigenvectors of a:\\n', y)\n", "print('\\neigenvalues of a:\\n', x)" ] @@ -660,12 +605,11 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", "@timeit\n", "def matrix_eig(a):\n", - " return linalg.eig(a)\n", + " return np.linalg.eig(a)\n", "\n", "a = np.array([[1, 2, 1, 4], [2, 5, 3, 5], [1, 3, 6, 1], [4, 5, 1, 7]], dtype=np.uint8)\n", "\n", @@ -676,20 +620,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Cholesky decomposition\n", + "## inv\n", "\n", - "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.cholesky.html\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.linalg.inv.html\n", "\n", - "`cholesky` takes a positive definite, symmetric square matrix as its single argument, and returns *square root matrix* in the lower triangular form. If the input argument does not fulfill the positivity or symmetry condition, a `ValueError` is raised." + "A square matrix, provided that it is not singular, can be inverted by calling the `inv` function that takes a single argument. The inversion is based on successive elimination of elements in the lower left triangle, and raises a `ValueError` exception, if the matrix turns out to be singular (i.e., one of the diagonal entries is zero)." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": { "ExecuteTime": { - "end_time": "2020-03-10T19:25:21.754166Z", - "start_time": "2020-03-10T19:25:21.740726Z" + "end_time": "2021-01-13T06:17:13.053816Z", + "start_time": "2021-01-13T06:17:13.038403Z" } }, "outputs": [ @@ -697,15 +641,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "a: array([[25.0, 15.0, -5.0],\n", - "\t [15.0, 18.0, 0.0],\n", - "\t [-5.0, 0.0, 11.0]], dtype=float)\n", - "\n", - "====================\n", - "Cholesky decomposition\n", - " array([[5.0, 0.0, 0.0],\n", - "\t [3.0, 3.0, 0.0],\n", - "\t [-1.0, 1.0, 3.0]], dtype=float)\n", + "array([[-2.166666666666667, 1.500000000000001, -0.8333333333333337, 1.0],\n", + " [1.666666666666667, -3.333333333333335, 1.666666666666668, -0.0],\n", + " [0.1666666666666666, 2.166666666666668, -0.8333333333333337, -1.0],\n", + " [-0.1666666666666667, -0.3333333333333333, 0.0, 0.5]], dtype=float64)\n", "\n", "\n" ] @@ -714,12 +653,78 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", - "a = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]])\n", - "print('a: ', a)\n", - "print('\\n' + '='*20 + '\\nCholesky decomposition\\n', linalg.cholesky(a))" + "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", + "\n", + "print(np.linalg.inv(m))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Computation expenses\n", + "\n", + "Note that the cost of inverting a matrix is approximately twice as many floats (RAM), as the number of entries in the original matrix, and approximately as many operations, as the number of entries. Here are a couple of numbers: " + ] + }, + { + "cell_type": "code", + "execution_count": 552, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-20T07:10:39.190734Z", + "start_time": "2019-10-20T07:10:39.138872Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 by 2 matrix:\n", + "execution time: 65 us\n", + "\n", + "4 by 4 matrix:\n", + "execution time: 105 us\n", + "\n", + "8 by 8 matrix:\n", + "execution time: 299 us\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def invert_matrix(m):\n", + " return np.linalg.inv(m)\n", + "\n", + "m = np.array([[1, 2,], [4, 5]])\n", + "print('2 by 2 matrix:')\n", + "invert_matrix(m)\n", + "\n", + "m = np.array([[1, 2, 3, 4], [4, 5, 6, 4], [7, 8.6, 9, 4], [3, 4, 5, 6]])\n", + "print('\\n4 by 4 matrix:')\n", + "invert_matrix(m)\n", + "\n", + "m = np.array([[1, 2, 3, 4, 5, 6, 7, 8], [0, 5, 6, 4, 5, 6, 4, 5], \n", + " [0, 0, 9, 7, 8, 9, 7, 8], [0, 0, 0, 10, 11, 12, 11, 12], \n", + " [0, 0, 0, 0, 4, 6, 7, 8], [0, 0, 0, 0, 0, 5, 6, 7], \n", + " [0, 0, 0, 0, 0, 0, 7, 6], [0, 0, 0, 0, 0, 0, 0, 2]])\n", + "print('\\n8 by 8 matrix:')\n", + "invert_matrix(m)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The above-mentioned scaling is not obeyed strictly. The reason for the discrepancy is that the function call is still the same for all three cases: the input must be inspected, the output array must be created, and so on. " ] }, { @@ -757,14 +762,13 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", - "a = ulab.array([1, 2, 3, 4, 5])\n", - "b = ulab.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", + "a = np.array([1, 2, 3, 4, 5])\n", + "b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", "\n", - "print('norm of a:', linalg.norm(a))\n", - "print('norm of b:', linalg.norm(b))" + "print('norm of a:', np.linalg.norm(a))\n", + "print('norm of b:', np.linalg.norm(b))" ] }, { @@ -808,17 +812,16 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab\n", - "from ulab import linalg\n", + "from ulab import numpy as np\n", "\n", - "a = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=ulab.int8)\n", + "a = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.int8)\n", "print('a: ', a)\n", - "print('\\ntrace of a: ', linalg.trace(a))\n", + "print('\\ntrace of a: ', np.linalg.trace(a))\n", "\n", - "b = ulab.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=ulab.float)\n", + "b = np.array([[25, 15, -5], [15, 18, 0], [-5, 0, 11]], dtype=np.float)\n", "\n", "print('='*20 + '\\nb: ', b)\n", - "print('\\ntrace of b: ', linalg.trace(b))" + "print('\\ntrace of b: ', np.linalg.trace(b))" ] } ], @@ -838,7 +841,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/ulab-vectorise.ipynb b/docs/numpy-universal.ipynb similarity index 82% rename from docs/ulab-vectorise.ipynb rename to docs/numpy-universal.ipynb index e09b568..70160b2 100644 --- a/docs/ulab-vectorise.ipynb +++ b/docs/numpy-universal.ipynb @@ -5,8 +5,8 @@ "execution_count": 1, "metadata": { "ExecuteTime": { - "end_time": "2020-05-01T09:27:13.438054Z", - "start_time": "2020-05-01T09:27:13.191491Z" + "end_time": "2021-01-13T18:54:58.722373Z", + "start_time": "2021-01-13T18:54:57.178438Z" } }, "outputs": [ @@ -34,8 +34,8 @@ "execution_count": 2, "metadata": { "ExecuteTime": { - "end_time": "2020-08-03T18:32:45.342280Z", - "start_time": "2020-08-03T18:32:45.338442Z" + "end_time": "2021-01-13T18:55:01.909310Z", + "start_time": "2021-01-13T18:55:01.903634Z" } }, "outputs": [], @@ -52,8 +52,8 @@ "execution_count": 3, "metadata": { "ExecuteTime": { - "end_time": "2020-07-23T20:31:25.296014Z", - "start_time": "2020-07-23T20:31:25.265937Z" + "end_time": "2021-01-13T18:55:02.434518Z", + "start_time": "2021-01-13T18:55:02.382296Z" } }, "outputs": [], @@ -225,22 +225,22 @@ "source": [ "# Universal functions\n", "\n", - "Standard mathematical functions are defined in the `vector` sub-module, and can be calculated on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). The functions execute faster with `ndarray` arguments than with iterables, because the values of the input vector can be extracted faster. \n", + "Standard mathematical functions can be calculated on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). The functions execute faster with `ndarray` arguments than with iterables, because the values of the input vector can be extracted faster. \n", "\n", "At present, the following functions are supported:\n", "\n", - "`acos`, `acosh`, `arctan2`, `around`, `asin`, `asinh`, `atan`, `arctan2`, `atanh`, `ceil`, `cos`, `degrees`, `erf`, `erfc`, `exp`, `expm1`, `floor`, `tgamma`, `lgamma`, `log`, `log10`, `log2`, `radians`, `sin`, `sinh`, `sqrt`, `tan`, `tanh`.\n", + "`acos`, `acosh`, `arctan2`, `around`, `asin`, `asinh`, `atan`, `arctan2`, `atanh`, `ceil`, `cos`, `degrees`, `exp`, `expm1`, `floor`, `log`, `log10`, `log2`, `radians`, `sin`, `sinh`, `sqrt`, `tan`, `tanh`.\n", "\n", - "These functions are applied element-wise to the arguments, thus, e.g., the exponential of a matrix cannot be calculated in this way. The functions can be invoked by importing the `vector` sub-module first. " + "These functions are applied element-wise to the arguments, thus, e.g., the exponential of a matrix cannot be calculated in this way." ] }, { "cell_type": "code", - "execution_count": 525, + "execution_count": 13, "metadata": { "ExecuteTime": { - "end_time": "2019-10-20T06:50:55.762508Z", - "start_time": "2019-10-20T06:50:55.655715Z" + "end_time": "2021-01-13T19:11:07.579601Z", + "start_time": "2021-01-13T19:11:07.554672Z" } }, "outputs": [ @@ -249,42 +249,48 @@ "output_type": "stream", "text": [ "a:\t range(0, 9)\n", - "exp(a):\t array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float)\n", + "exp(a):\t array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64)\n", "\n", - "b:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", - "exp(b):\t array([1.0, 2.718282, 7.389056, 20.08554, 54.59816, 148.4132, 403.4288, 1096.633, 2980.958], dtype=float)\n", + "=============\n", + "b:\n", + " array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "exp(b):\n", + " array([1.0, 2.718281828459045, 7.38905609893065, 20.08553692318767, 54.59815003314424, 148.4131591025766, 403.4287934927351, 1096.633158428459, 2980.957987041728], dtype=float64)\n", + "\n", + "=============\n", + "c:\n", + " array([[0.0, 1.0, 2.0],\n", + " [3.0, 4.0, 5.0],\n", + " [6.0, 7.0, 8.0]], dtype=float64)\n", + "exp(c):\n", + " array([[1.0, 2.718281828459045, 7.38905609893065],\n", + " [20.08553692318767, 54.59815003314424, 148.4131591025766],\n", + " [403.4287934927351, 1096.633158428459, 2980.957987041728]], dtype=float64)\n", "\n", - "c:\t array([[1.0, 2.0, 3.0],\n", - "\t [4.0, 5.0, 6.0],\n", - "\t [7.0, 8.0, 9.0]], dtype=float)\n", - "exp(c):\t array([[2.718282, 7.389056, 20.08554],\n", - "\t [54.59816, 148.4132, 403.4288],\n", - "\t [1096.633, 2980.958, 8103.084]], dtype=float)\n", "\n" ] } ], "source": [ - "%%micropython -pyboard 1\n", + "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "\n", "a = range(9)\n", "b = np.array(a)\n", "\n", "# works with ranges, lists, tuples etc.\n", "print('a:\\t', a)\n", - "print('exp(a):\\t', vector.exp(a))\n", + "print('exp(a):\\t', np.exp(a))\n", "\n", "# with 1D arrays\n", - "print('\\nb:\\t', b)\n", - "print('exp(b):\\t', vector.exp(b))\n", + "print('\\n=============\\nb:\\n', b)\n", + "print('exp(b):\\n', np.exp(b))\n", "\n", "# as well as with matrices\n", - "c = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])\n", - "print('\\nc:\\t', c)\n", - "print('exp(c):\\t', vector.exp(c))" + "c = np.array(range(9)).reshape((3, 3))\n", + "print('\\n=============\\nc:\\n', c)\n", + "print('exp(c):\\n', np.exp(c))" ] }, { @@ -327,8 +333,7 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "import math\n", "\n", "a = [0]*1000\n", @@ -336,7 +341,7 @@ "\n", "@timeit\n", "def timed_vector(iterable):\n", - " return vector.exp(iterable)\n", + " return np.exp(iterable)\n", "\n", "@timeit\n", "def timed_list(iterable):\n", @@ -352,6 +357,108 @@ "timed_list(a)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## arctan2\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html\n", + "\n", + "The two-argument inverse tangent function is also part of the `vector` sub-module. The function implements broadcasting as discussed in the section on `ndarray`s. Scalars (`micropython` integers or floats) are also allowed." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:15:08.215912Z", + "start_time": "2021-01-13T19:15:08.189806Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1.0, 2.2, 33.33, 444.444], dtype=float64)\n", + "\n", + "arctan2(a, 1.0)\n", + " array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float64)\n", + "\n", + "arctan2(1.0, a)\n", + " array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float64)\n", + "\n", + "arctan2(a, a): \n", + " array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2.2, 33.33, 444.444])\n", + "print('a:\\n', a)\n", + "print('\\narctan2(a, 1.0)\\n', np.arctan2(a, 1.0))\n", + "print('\\narctan2(1.0, a)\\n', np.arctan2(1.0, a))\n", + "print('\\narctan2(a, a): \\n', np.arctan2(a, a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## around\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html\n", + "\n", + "`numpy`'s `around` function can also be found in the `vector` sub-module. The function implements the `decimals` keyword argument with default value `0`. The first argument must be an `ndarray`. If this is not the case, the function raises a `TypeError` exception. Note that `numpy` accepts general iterables. The `out` keyword argument known from `numpy` is not accepted. The function always returns an ndarray of type `mp_float_t`." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:19:46.728823Z", + "start_time": "2021-01-13T19:19:46.703348Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float64)\n", + "\n", + "decimals = 0\t array([1.0, 2.0, 33.0, 444.0], dtype=float64)\n", + "\n", + "decimals = 1\t array([1.0, 2.2, 33.3, 444.4], dtype=float64)\n", + "\n", + "decimals = -1\t array([0.0, 0.0, 30.0, 440.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2.2, 33.33, 444.444])\n", + "print('a:\\t\\t', a)\n", + "print('\\ndecimals = 0\\t', np.around(a, decimals=0))\n", + "print('\\ndecimals = 1\\t', np.around(a, decimals=1))\n", + "print('\\ndecimals = -1\\t', np.around(a, decimals=-1))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -367,11 +474,11 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": { "ExecuteTime": { - "end_time": "2020-05-06T22:13:35.735953Z", - "start_time": "2020-05-06T22:13:35.720709Z" + "end_time": "2021-01-13T19:16:55.709617Z", + "start_time": "2021-01-13T19:16:55.688222Z" } }, "outputs": [ @@ -379,9 +486,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "f on a scalar: array([1936.0], dtype=float)\n", - "f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float)\n", - "f on a list: array([4.0, 9.0, 16.0], dtype=float)\n", + "f on a scalar: array([1936.0], dtype=float64)\n", + "f on an ndarray: array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", + "f on a list: array([4.0, 9.0, 16.0], dtype=float64)\n", "\n", "\n" ] @@ -390,13 +497,12 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "\n", "def f(x):\n", " return x*x\n", "\n", - "vf = vector.vectorize(f)\n", + "vf = np.vectorize(f)\n", "\n", "# calling with a scalar\n", "print('{:20}'.format('f on a scalar: '), vf(44.0))\n", @@ -418,11 +524,11 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 18, "metadata": { "ExecuteTime": { - "end_time": "2020-05-06T22:17:56.649769Z", - "start_time": "2020-05-06T22:17:56.639524Z" + "end_time": "2021-01-13T19:19:36.090837Z", + "start_time": "2021-01-13T19:19:36.069088Z" } }, "outputs": [ @@ -431,7 +537,7 @@ "output_type": "stream", "text": [ "output is uint8: array([1, 4, 9, 16], dtype=uint8)\n", - "output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float)\n", + "output is float: array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", "\n", "\n" ] @@ -440,15 +546,14 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "\n", "l = [1, 2, 3, 4]\n", "def f(x):\n", " return x*x\n", "\n", - "vf1 = vector.vectorize(f, otypes=np.uint8)\n", - "vf2 = vector.vectorize(f, otypes=np.float)\n", + "vf1 = np.vectorize(f, otypes=np.uint8)\n", + "vf2 = np.vectorize(f, otypes=np.float)\n", "\n", "print('{:20}'.format('output is uint8: '), vf1(l))\n", "print('{:20}'.format('output is float: '), vf2(l))" @@ -487,15 +592,14 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "\n", "int_list = [1, 2, 3, 4]\n", "float_list = [1.0, 2.0, 3.0, 4.0]\n", "def f(x):\n", " return x*x\n", "\n", - "vf = vector.vectorize(f, otypes=np.uint8)\n", + "vf = np.vectorize(f, otypes=np.uint8)\n", "\n", "print('{:20}'.format('integer list: '), vf(int_list))\n", "# this will raise a TypeError exception\n", @@ -545,13 +649,12 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab as np\n", - "from ulab import vector\n", + "from ulab import numpy as np\n", "\n", "def f(x):\n", " return x*x\n", "\n", - "vf = vector.vectorize(f)\n", + "vf = np.vectorize(f)\n", "\n", "@timeit\n", "def timed_vectorised_square(iterable):\n", @@ -603,106 +706,6 @@ "\n", "is perfectly valid code." ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## around\n", - "\n", - "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.around.html\n", - "\n", - "`numpy`'s `around` function can also be found in the `vector` sub-module. The function implements the `decimals` keyword argument with default value `0`. The first argument must be an `ndarray`. If this is not the case, the function raises a `TypeError` exception. Note that `numpy` accepts general iterables. The `out` keyword argument known from `numpy` is not accepted. The function always returns an ndarray of type `mp_float_t`." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "ExecuteTime": { - "end_time": "2020-03-12T16:24:36.435638Z", - "start_time": "2020-03-12T16:24:36.424468Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float)\n", - "\n", - "decimals = 0\t array([1.0, 2.0, 33.0, 444.0], dtype=float)\n", - "\n", - "decimals = 1\t array([1.0, 2.2, 33.3, 444.4], dtype=float)\n", - "\n", - "decimals = -1\t array([0.0, 0.0, 30.0, 440.0], dtype=float)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "from ulab import vector\n", - "\n", - "a = np.array([1, 2.2, 33.33, 444.444])\n", - "print('a:\\t\\t', a)\n", - "print('\\ndecimals = 0\\t', vector.around(a, decimals=0))\n", - "print('\\ndecimals = 1\\t', vector.around(a, decimals=1))\n", - "print('\\ndecimals = -1\\t', vector.around(a, decimals=-1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## arctan2\n", - "\n", - "`numpy`: https://docs.scipy.org/doc/numpy-1.17.0/reference/generated/numpy.arctan2.html\n", - "\n", - "The two-argument inverse tangent function is also part of the `vector` sub-module. The function implements broadcasting as discussed in the section on `ndarray`s. Scalars (`micropython` integers or floats) are also allowed." - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": { - "ExecuteTime": { - "end_time": "2020-03-16T18:34:22.795475Z", - "start_time": "2020-03-16T18:34:22.781447Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a:\t\t array([1.0, 2.2, 33.33, 444.444], dtype=float)\n", - "\n", - "arctan2(a, 1.0)\t array([0.7853981633974483, 1.14416883366802, 1.5408023243361, 1.568546328341769], dtype=float)\n", - "\n", - "arctan2(1.0, a)\t array([0.7853981633974483, 0.426627493126876, 0.02999400245879636, 0.002249998453127392], dtype=float)\n", - "\n", - "arctan2(a, a): \t array([0.7853981633974483, 0.7853981633974483, 0.7853981633974483, 0.7853981633974483], dtype=float)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "from ulab import vector\n", - "\n", - "a = np.array([1, 2.2, 33.33, 444.444])\n", - "print('a:\\t\\t', a)\n", - "print('\\narctan2(a, 1.0)\\t', vector.arctan2(a, 1.0))\n", - "print('\\narctan2(1.0, a)\\t', vector.arctan2(1.0, a))\n", - "print('\\narctan2(a, a): \\t', vector.arctan2(a, a))" - ] } ], "metadata": { @@ -721,7 +724,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/scipy-optimize.ipynb b/docs/scipy-optimize.ipynb new file mode 100644 index 0000000..2f884e2 --- /dev/null +++ b/docs/scipy-optimize.ipynb @@ -0,0 +1,515 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:51.417613Z", + "start_time": "2021-01-08T12:50:51.208257Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:52.581876Z", + "start_time": "2021-01-08T12:50:52.567901Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:50:53.516712Z", + "start_time": "2021-01-08T12:50:53.454984Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Optimize\n", + "\n", + "Functions in the `optimize` module can be called by prepending them by `scipy.optimize.`. The module defines the following three functions:\n", + "\n", + "1. [scipy.optimize.bisect](#bisect)\n", + "1. [scipy.optimize.fmin](#fmin)\n", + "1. [scipy.optimize.newton](#newton)\n", + "\n", + "Note that routines that work with user-defined functions still have to call the underlying `python` code, and therefore, gains in speed are not as significant as with other vectorised operations. As a rule of thumb, a factor of two can be expected, when compared to an optimised `python` implementation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## bisect \n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.bisect.html\n", + "\n", + "`bisect` finds the root of a function of one variable using a simple bisection routine. It takes three positional arguments, the function itself, and two starting points. The function must have opposite signs\n", + "at the starting points. Returned is the position of the root.\n", + "\n", + "Two keyword arguments, `xtol`, and `maxiter` can be supplied to control the accuracy, and the number of bisections, respectively." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:58:28.444300Z", + "start_time": "2021-01-08T12:58:28.421989Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9999997615814209\n", + "only 8 bisections: 0.984375\n", + "with 0.1 accuracy: 0.9375\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x - 1\n", + "\n", + "print(spy.optimize.bisect(f, 0, 4))\n", + "\n", + "print('only 8 bisections: ', spy.optimize.bisect(f, 0, 4, maxiter=8))\n", + "\n", + "print('with 0.1 accuracy: ', spy.optimize.bisect(f, 0, 4, xtol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Performance\n", + "\n", + "Since the `bisect` routine calls user-defined `python` functions, the speed gain is only about a factor of two, if compared to a purely `python` implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:08:24.750562Z", + "start_time": "2020-05-19T19:08:24.682959Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bisect running in python\r\n", + "execution time: 1270 us\r\n", + "bisect running in C\r\n", + "execution time: 642 us\r\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)*(x-1) - 2.0\n", + "\n", + "def bisect(f, a, b, xtol=2.4e-7, maxiter=100):\n", + " if f(a) * f(b) > 0:\n", + " raise ValueError\n", + "\n", + " rtb = a if f(a) < 0.0 else b\n", + " dx = b - a if f(a) < 0.0 else a - b\n", + " for i in range(maxiter):\n", + " dx *= 0.5\n", + " x_mid = rtb + dx\n", + " mid_value = f(x_mid)\n", + " if mid_value < 0:\n", + " rtb = x_mid\n", + " if abs(dx) < xtol:\n", + " break\n", + "\n", + " return rtb\n", + "\n", + "@timeit\n", + "def bisect_scipy(f, a, b):\n", + " return spy.optimize.bisect(f, a, b)\n", + "\n", + "@timeit\n", + "def bisect_timed(f, a, b):\n", + " return bisect(f, a, b)\n", + "\n", + "print('bisect running in python')\n", + "bisect_timed(f, 3, 2)\n", + "\n", + "print('bisect running in C')\n", + "bisect_scipy(f, 3, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## fmin\n", + "\n", + "`scipy`: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html\n", + "\n", + "The `fmin` function finds the position of the minimum of a user-defined function by using the downhill simplex method. Requires two positional arguments, the function, and the initial value. Three keyword arguments, `xatol`, `fatol`, and `maxiter` stipulate conditions for stopping." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T13:00:26.729947Z", + "start_time": "2021-01-08T13:00:26.702748Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.9996093749999952\n", + "1.199999999999996\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + "\n", + "def f(x):\n", + " return (x-1)**2 - 1\n", + "\n", + "print(spy.optimize.fmin(f, 3.0))\n", + "print(spy.optimize.fmin(f, 3.0, xatol=0.1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## newton\n", + "\n", + "`scipy`:https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html\n", + "\n", + "`newton` finds a zero of a real, user-defined function using the Newton-Raphson (or secant or Halley’s) method. The routine requires two positional arguments, the function, and the initial value. Three keyword\n", + "arguments can be supplied to control the iteration. These are the absolute and relative tolerances `tol`, and `rtol`, respectively, and the number of iterations before stopping, `maxiter`. The function retuns a single scalar, the position of the root." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-08T12:56:35.139958Z", + "start_time": "2021-01-08T12:56:35.119712Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1.260135727246117\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import scipy as spy\n", + " \n", + "def f(x):\n", + " return x*x*x - 2.0\n", + "\n", + "print(spy.optimize.newton(f, 3., tol=0.001, rtol=0.01))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/ulab-filter.ipynb b/docs/scipy-signal.ipynb similarity index 74% rename from docs/ulab-filter.ipynb rename to docs/scipy-signal.ipynb index 87c32a0..a8f0754 100644 --- a/docs/ulab-filter.ipynb +++ b/docs/scipy-signal.ipynb @@ -2,11 +2,11 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "ExecuteTime": { - "end_time": "2020-05-01T09:27:13.438054Z", - "start_time": "2020-05-01T09:27:13.191491Z" + "end_time": "2021-01-12T16:11:12.111639Z", + "start_time": "2021-01-12T16:11:11.914041Z" } }, "outputs": [ @@ -31,11 +31,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "ExecuteTime": { - "end_time": "2020-08-03T18:32:45.342280Z", - "start_time": "2020-08-03T18:32:45.338442Z" + "end_time": "2021-01-12T16:11:13.416714Z", + "start_time": "2021-01-12T16:11:13.404067Z" } }, "outputs": [], @@ -49,11 +49,11 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": { "ExecuteTime": { - "end_time": "2020-07-23T20:31:25.296014Z", - "start_time": "2020-07-23T20:31:25.265937Z" + "end_time": "2021-01-12T16:11:13.920842Z", + "start_time": "2021-01-12T16:11:13.863737Z" } }, "outputs": [], @@ -223,54 +223,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Filter routines\n", + "# Signal\n", "\n", - "Functions in the `filter` module can be called by importing the sub-module first." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## convolve\n", + "Functions in the `signal` module can be called by prepending them by `scipy.signal.`. The module defines the following two functions:\n", "\n", - "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html\n", - "\n", - "Returns the discrete, linear convolution of two one-dimensional sequences.\n", - "\n", - "Only the ``full`` mode is supported, and the ``mode`` named parameter is not accepted. Note that all other modes can be had by slicing a ``full`` result." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "ExecuteTime": { - "end_time": "2020-02-10T18:46:06.538207Z", - "start_time": "2020-02-10T18:46:06.525851Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "from ulab import filter\n", - "\n", - "x = np.array((1,2,3))\n", - "y = np.array((1,10,100,1000))\n", - "\n", - "print(filter.convolve(x, y))" + "1. [scipy.signal.sosfilt](#sosfilt)\n", + "1. [scipy.signal.spectrogram](#spectrogram)" ] }, { @@ -283,7 +241,7 @@ "\n", "Filter data along one dimension using cascaded second-order sections.\n", "\n", - "The function takes two positional arguments, `sos`, the filter segments of length 6, and the one-dimensional, uniformly sample data set to be filtered. Returns the filtered data, or the filtered data and the final filter delays, if the `zi` keyword arguments is supplied. The keyword argument be a float `ndarray` of shape `(n_sections, 2)`. If `zi` is not passed to the function, the initial values are assumed to be 0." + "The function takes two positional arguments, `sos`, the filter segments of length 6, and the one-dimensional, uniformly sampled data set to be filtered. Returns the filtered data, or the filtered data and the final filter delays, if the `zi` keyword arguments is supplied. The keyword argument must be a float `ndarray` of shape `(n_sections, 2)`. If `zi` is not passed to the function, the initial values are assumed to be 0." ] }, { @@ -309,12 +267,12 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab\n", - "from ulab import filter as filter\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", "\n", - "x = ulab.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", "sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]]\n", - "y = filter.sosfilt(sos, x)\n", + "y = spy.signal.sosfilt(sos, x)\n", "print('y: ', y)" ] }, @@ -345,18 +303,112 @@ "source": [ "%%micropython -unix 1\n", "\n", - "import ulab\n", - "from ulab import filter as filter\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", "\n", - "x = ulab.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", + "x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])\n", "sos = [[1, 2, 3, 1, 5, 6], [1, 2, 3, 1, 5, 6]]\n", "# initial conditions of the filter\n", - "zi = ulab.array([[1, 2], [3, 4]])\n", + "zi = np.array([[1, 2], [3, 4]])\n", "\n", - "y, zf = filter.sosfilt(sos, x, zi=zi)\n", + "y, zf = spy.signal.sosfilt(sos, x, zi=zi)\n", "print('y: ', y)\n", "print('\\n' + '='*40 + '\\nzf: ', zf)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## spectrogram\n", + "\n", + "In addition to the Fourier transform and its inverse, `ulab` also sports a function called `spectrogram`, which returns the absolute value of the Fourier transform. This could be used to find the dominant spectral component in a time series. The arguments are treated in the same way as in `fft`, and `ifft`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:12:06.573408Z", + "start_time": "2021-01-12T16:12:06.560558Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "original vector:\t array([0.0, 0.009775015390171337, 0.01954909674625918, ..., -0.5275140569487312, -0.5357931822978732, -0.5440211108893639], dtype=float64)\n", + "\n", + "spectrum:\t array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "a = spy.signal.spectrogram(y)\n", + "\n", + "print('original vector:\\t', y)\n", + "print('\\nspectrum:\\t', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As such, `spectrogram` is really just a shorthand for `np.sqrt(a*a + b*b)`:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:13:36.726662Z", + "start_time": "2021-01-12T16:13:36.705036Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "spectrum calculated the hard way:\t array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "spectrum calculated the lazy way:\t array([187.8635087634579, 315.3112063607119, 347.8814873399374, ..., 84.45888934298905, 347.8814873399374, 315.3112063607118], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "x = np.linspace(0, 10, num=1024)\n", + "y = np.sin(x)\n", + "\n", + "a, b = np.fft.fft(y)\n", + "\n", + "print('\\nspectrum calculated the hard way:\\t', np.sqrt(a*a + b*b))\n", + "\n", + "a = spy.signal.spectrogram(y)\n", + "\n", + "print('\\nspectrum calculated the lazy way:\\t', a)" + ] } ], "metadata": { @@ -375,7 +427,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/scipy-special.ipynb b/docs/scipy-special.ipynb new file mode 100644 index 0000000..a8830dc --- /dev/null +++ b/docs/scipy-special.ipynb @@ -0,0 +1,344 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:54:58.722373Z", + "start_time": "2021-01-13T18:54:57.178438Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:57:41.555892Z", + "start_time": "2021-01-13T18:57:41.551121Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T18:57:42.313231Z", + "start_time": "2021-01-13T18:57:42.288402Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Special functions\n", + "\n", + "`scipy`'s `special` module defines several functions that behave as do the standard mathematical functions of the `numpy`, i.e., they can be called on any scalar, scalar-valued iterable (ranges, lists, tuples containing numbers), and on `ndarray`s without having to change the call signature. In all cases the functions return a new `ndarray` of typecode `float` (since these functions usually generate float values, anyway). \n", + "\n", + "At present, `ulab`'s `special` module contains the following functions:\n", + "\n", + "`erf`, `erfc`, `gamma`, and `gammaln`, and they can be called by prepending them by `scipy.special.`." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T19:06:54.640444Z", + "start_time": "2021-01-13T19:06:54.623467Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: range(0, 9)\n", + "array([0.0, 0.8427007929497149, 0.9953222650189527, 0.9999779095030014, 0.9999999845827421, 1.0, 1.0, 1.0, 1.0], dtype=float64)\n", + "\n", + "b: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "array([1.0, 0.1572992070502851, 0.004677734981047265, 2.209049699858544e-05, 1.541725790028002e-08, 1.537459794428035e-12, 2.151973671249892e-17, 4.183825607779414e-23, 1.122429717298293e-29], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "from ulab import scipy as spy\n", + "\n", + "a = range(9)\n", + "b = np.array(a)\n", + "\n", + "print('a: ', a)\n", + "print(spy.special.erf(a))\n", + "\n", + "print('\\nb: ', b)\n", + "print(spy.special.erfc(b))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/ulab-approx.ipynb b/docs/ulab-approx.ipynb index fea3d23..52dc205 100644 --- a/docs/ulab-approx.ipynb +++ b/docs/ulab-approx.ipynb @@ -223,10 +223,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Interpolation, root finding, and function minimisation\n", + "# Approximation methods\n", "\n", - "The `approx` sub-module defines functions for interpolating numerical data, and finding the roots and the minimum of arbitrary functions defined in `python`. Note that routines that work with user-defined\n", - "functions still have to call the underlying `python` code, and therefore, gains in speed are not as significant as with other vectorised operations. As a rule of thumb, a factor of two can be expected, when compared to an optimised python implementation." + "`ulab` implements five functions that can be used for interpolating, root finding, and minimising arbitrary `python` functions in one dimension. Two of these functions, namely, `interp`, and `trapz` are defined in `numpy`, while the other three are parts of `scipy`'s `optimize` module. \n", + "\n", + "Note that routines that work with user-defined functions still have to call the underlying `python` code, and therefore, gains in speed are not as significant as with other vectorised operations. As a rule of thumb, a factor of two can be expected, when compared to an optimised `python` implementation." ] }, { @@ -238,57 +239,11 @@ "`numpy`: https://docs.scipy.org/doc/numpy/numpy.interp\n", "\n", "The `interp` function returns the linearly interpolated values of a one-dimensional numerical array. It requires three positional arguments,`x`, at which the interpolated values are evaluated, `xp`, the array\n", - "of the independent variables of the data, and `fp`, the array of the dependent values of the data. `xp` must be a monotonically increasing sequence of numbers.\n", + "of the independent data variable, and `fp`, the array of the dependent values of the data. `xp` must be a monotonically increasing sequence of numbers.\n", "\n", "Two keyword arguments, `left`, and `right` can also be supplied; these determine the return values, if `x < xp[0]`, and `x > xp[-1]`, respectively. If these arguments are not supplied, `left`, and `right` default to `fp[0]`, and `fp[-1]`, respectively." ] }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "ExecuteTime": { - "end_time": "2021-01-08T12:51:43.799937Z", - "start_time": "2021-01-08T12:51:43.778474Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "array([0.8, 1.8, 2.8, 3.8, 4.8], dtype=float64)\n", - "array([1.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", - "array([0.0, 1.8, 2.8, 4.6, 5.0], dtype=float64)\n", - "array([1.0, 1.8, 2.8, 4.6, 10.0], dtype=float64)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "from ulab import approx\n", - "\n", - "x = ulab.array([1, 2, 3, 4, 5]) - 0.2\n", - "xp = ulab.array([1, 2, 3, 4])\n", - "fp = ulab.array([1, 2, 3, 5])\n", - "\n", - "print(x)\n", - "print(approx.interp(x, xp, fp))\n", - "print(approx.interp(x, xp, fp, left=0.0))\n", - "print(approx.interp(x, xp, fp, right=10.0))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For `numpy`-compatible firmware: " - ] - }, { "cell_type": "code", "execution_count": 6, @@ -339,45 +294,6 @@ "arguments can be supplied to control the iteration. These are the absolute and relative tolerances `tol`, and `rtol`, respectively, and the number of iterations before stopping, `maxiter`. The function retuns a single scalar, the position of the root." ] }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": { - "ExecuteTime": { - "end_time": "2020-05-19T16:52:41.146966Z", - "start_time": "2020-05-19T16:52:41.132190Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1.260135727246117\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "from ulab import approx\n", - " \n", - "def f(x):\n", - " return x*x*x - 2.0\n", - "\n", - "print(approx.newton(f, 3., tol=0.001, rtol=0.01))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the firmware is `numpy`-compatible, we have to import `scipy`, and call the function via the `optimize` module:" - ] - }, { "cell_type": "code", "execution_count": 9, @@ -423,51 +339,6 @@ "Two keyword arguments, `xtol`, and `maxiter` can be supplied to control the accuracy, and the number of bisections, respectively." ] }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "ExecuteTime": { - "end_time": "2020-05-19T16:53:53.224741Z", - "start_time": "2020-05-19T16:53:53.212098Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.9999997615814209\n", - "only 8 bisections: 0.984375\n", - "with 0.1 accuracy: 0.9375\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "from ulab import approx\n", - " \n", - "def f(x):\n", - " return x*x - 1\n", - "\n", - "print(approx.bisect(f, 0, 4))\n", - "\n", - "print('only 8 bisections: ', approx.bisect(f, 0, 4, maxiter=8))\n", - "\n", - "print('with 0.1 accuracy: ', approx.bisect(f, 0, 4, xtol=0.1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the firmware is `numpy`-compatible, we have to import `scipy`, and call the function via the `optimize` module:" - ] - }, { "cell_type": "code", "execution_count": 12, @@ -539,8 +410,7 @@ "source": [ "%%micropython -pyboard 1\n", "\n", - "import ulab\n", - "from ulab import approx\n", + "from ulab import scipy as spy\n", "\n", "def f(x):\n", " return (x-1)*(x-1) - 2.0\n", @@ -563,8 +433,8 @@ " return rtb\n", "\n", "@timeit\n", - "def bisect_approx(f, a, b):\n", - " return approx.bisect(f, a, b)\n", + "def bisect_scipy(f, a, b):\n", + " return spy.optimize.bisect(f, a, b)\n", "\n", "@timeit\n", "def bisect_timed(f, a, b):\n", @@ -574,7 +444,7 @@ "bisect_timed(f, 3, 2)\n", "\n", "print('bisect running in C')\n", - "bisect_approx(f, 3, 2)" + "bisect_scipy(f, 3, 2)" ] }, { @@ -588,47 +458,6 @@ "The `fmin` function finds the position of the minimum of a user-defined function by using the downhill simplex method. Requires two positional arguments, the function, and the initial value. Three keyword arguments, `xatol`, `fatol`, and `maxiter` stipulate conditions for stopping." ] }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": { - "ExecuteTime": { - "end_time": "2020-05-19T16:56:40.365826Z", - "start_time": "2020-05-19T16:56:40.352936Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.9996093749999952\n", - "1.199999999999996\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "from ulab import approx\n", - "\n", - "def f(x):\n", - " return (x-1)**2 - 1\n", - "\n", - "print(approx.fmin(f, 3.0))\n", - "print(approx.fmin(f, 3.0, xatol=0.1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If the firmware is `numpy`-compatible, we have to import `scipy`, and call the function via the `optimize` module:" - ] - }, { "cell_type": "code", "execution_count": 14, @@ -673,54 +502,6 @@ "The function takes one or two one-dimensional `ndarray`s, and integrates the dependent values (`y`) using the trapezoidal rule. If the independent variable (`x`) is given, that is taken as the sample points corresponding to `y`." ] }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "ExecuteTime": { - "end_time": "2020-07-23T20:46:11.084208Z", - "start_time": "2020-07-23T20:46:11.071068Z" - }, - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0], dtype=float)\n", - "y: array([0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0], dtype=float)\n", - "============================\n", - "integral of y: 244.5\n", - "integral of y at x: 244.5\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "from ulab import approx\n", - "\n", - "x = ulab.linspace(0, 9, num=10)\n", - "y = x*x\n", - "\n", - "print('x: ', x)\n", - "print('y: ', y)\n", - "print('============================')\n", - "print('integral of y: ', approx.trapz(y))\n", - "print('integral of y at x: ', approx.trapz(y, x=x))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For `numpy`-compatible firmware" - ] - }, { "cell_type": "code", "execution_count": 15, diff --git a/docs/ulab-change-log.md b/docs/ulab-change-log.md index cf8bb8e..c15b85d 100644 --- a/docs/ulab-change-log.md +++ b/docs/ulab-change-log.md @@ -1,3 +1,9 @@ +Thu, 14 Jan 2021 + +version 2.1.1 + + fixed bad error in diff + Thu, 26 Nov 2020 version 2.1.0 diff --git a/docs/ulab-compare.ipynb b/docs/ulab-compare.ipynb index a379598..69fa762 100644 --- a/docs/ulab-compare.ipynb +++ b/docs/ulab-compare.ipynb @@ -223,9 +223,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Comparison of arrays\n", - "\n", - "If the firmware is `circuitpython`-compatible, functions in the `compare` module can be called by importing the sub-module first. Otherwise, they are bound at the top level in `numpy`." + "# Comparison of arrays" ] }, { @@ -243,94 +241,6 @@ "These two functions take two `ndarray`s, or scalars as their arguments. No keyword arguments are implemented." ] }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "ExecuteTime": { - "end_time": "2021-01-08T14:44:03.644188Z", - "start_time": "2021-01-08T14:44:03.628522Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Traceback (most recent call last):\n", - " File \"/dev/shm/micropython.py\", line 5, in \n", - "AttributeError: 'module' object has no attribute 'zeros'\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "\n", - "a = np.array(range(9))\n", - "b = np.zeros(9)\n", - "\n", - "print('a: ', a)\n", - "print('b: ', b)\n", - "print('\\na == b: ', np.compare.equal(a, b))\n", - "print('a != b: ', np.compare.not_equal(a, b))\n", - "\n", - "# comparison with scalars\n", - "print('a == 2: ', np.compare.equal(a, 2))" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": { - "ExecuteTime": { - "end_time": "2020-05-03T08:53:02.668348Z", - "start_time": "2020-05-03T08:53:02.656130Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", - "b: array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], dtype=float)\n", - "\n", - "a == b: [True, False, False, False, False, False, False, False, False]\n", - "a != b: [False, True, True, True, True, True, True, True, True]\n", - "a == 2: [False, False, True, False, False, False, False, False, False]\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab as np\n", - "\n", - "a = np.array(range(9))\n", - "b = np.zeros(9)\n", - "\n", - "print('a: ', a)\n", - "print('b: ', b)\n", - "print('\\na == b: ', np.compare.equal(a, b))\n", - "print('a != b: ', np.compare.not_equal(a, b))\n", - "\n", - "# comparison with scalars\n", - "print('a == 2: ', np.compare.equal(a, 2))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "On the other hand, for `numpy`-compatible firmware" - ] - }, { "cell_type": "code", "execution_count": 13, @@ -381,57 +291,13 @@ "\n", "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.minimum.html\n", "\n", - "Returns the minimum of two arrays, or two scalars, or an array, and a scalar. Partial broadcasting is implemented. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented.\n", + "Returns the minimum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented.\n", "\n", "## maximum\n", "\n", "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.maximum.html\n", "\n", - "Returns the maximum of two arrays, or two scalars, or an array, and a scalar. Partial broadcasting is implemented. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": { - "ExecuteTime": { - "end_time": "2020-04-21T20:31:42.178279Z", - "start_time": "2020-04-21T20:31:42.163823Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "minimum of a, and b:\n", - "array([1.0, 2.0, 3.0, 2.0, 1.0], dtype=float)\n", - "\n", - "maximum of a, and b:\n", - "array([5.0, 4.0, 3.0, 4.0, 5.0], dtype=float)\n", - "\n", - "maximum of 1, and 5.5:\n", - "5.5\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "\n", - "a = ulab.array([1, 2, 3, 4, 5], dtype=ulab.uint8)\n", - "b = ulab.array([5, 4, 3, 2, 1], dtype=ulab.float)\n", - "print('minimum of a, and b:')\n", - "print(ulab.compare.minimum(a, b))\n", - "\n", - "print('\\nmaximum of a, and b:')\n", - "print(ulab.compare.maximum(a, b))\n", - "\n", - "print('\\nmaximum of 1, and 5.5:')\n", - "print(ulab.compare.maximum(1, 5.5))" + "Returns the maximum of two arrays, or two scalars, or an array, and a scalar. If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators). If both inputs are scalars, a scalar is returned. Only positional arguments are implemented." ] }, { @@ -486,48 +352,7 @@ "\n", "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.clip.html\n", "\n", - "Clips an array, i.e., values that are outside of an interval are clipped to the interval edges. The function is equivalent to `maximum(a_min, minimum(a, a_max))`. or two scalars, hence partial broadcasting takes place exactly as in [minimum](#minimum). If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators)." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "ExecuteTime": { - "end_time": "2020-04-21T20:33:29.679569Z", - "start_time": "2020-04-21T20:33:29.663150Z" - }, - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", - "clipped:\t array([3, 3, 3, 3, 4, 5, 6, 7, 7], dtype=uint8)\n", - "\n", - "a:\t\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", - "b:\t\t array([3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0], dtype=float)\n", - "clipped:\t array([3.0, 3.0, 3.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.0], dtype=float)\n", - "\n", - "\n" - ] - } - ], - "source": [ - "%%micropython -unix 1\n", - "\n", - "import ulab\n", - "\n", - "a = ulab.array(range(9), dtype=ulab.uint8)\n", - "print('a:\\t\\t', a)\n", - "print('clipped:\\t', ulab.compare.clip(a, 3, 7))\n", - "\n", - "b = 3 * ulab.ones(len(a), dtype=ulab.float)\n", - "print('\\na:\\t\\t', a)\n", - "print('b:\\t\\t', b)\n", - "print('clipped:\\t', ulab.compare.clip(a, b, 7))" + "Clips an array, i.e., values that are outside of an interval are clipped to the interval edges. The function is equivalent to `maximum(a_min, minimum(a, a_max))` broadcasting takes place exactly as in [minimum](#minimum). If the arrays are of different `dtype`, the output is upcast as in [Binary operators](#Binary-operators)." ] }, { diff --git a/docs/ulab-convert.ipynb b/docs/ulab-convert.ipynb index 777d330..42da0b2 100644 --- a/docs/ulab-convert.ipynb +++ b/docs/ulab-convert.ipynb @@ -17,8 +17,8 @@ "execution_count": 7, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T16:08:07.609100Z", - "start_time": "2020-11-18T16:08:07.591303Z" + "end_time": "2021-01-14T17:57:42.304478Z", + "start_time": "2021-01-14T17:57:42.205232Z" } }, "outputs": [ @@ -57,11 +57,11 @@ "# -- Project information -----------------------------------------------------\n", "\n", "project = 'The ulab book'\n", - "copyright = '2019-2020, Zoltán Vörös and contributors'\n", + "copyright = '2019-2021, Zoltán Vörös and contributors'\n", "author = 'Zoltán Vörös'\n", "\n", "# The full version, including alpha/beta/rc tags\n", - "release = '1.4.0'\n", + "release = '2.1.1'\n", "\n", "\n", "# -- General configuration ---------------------------------------------------\n", @@ -132,19 +132,6 @@ "'Zoltán Vörös', 'manual'),\n", "]\n", "\n", - "# sphinx-autoapi\n", - "extensions.append('autoapi.extension')\n", - "autoapi_type = 'python'\n", - "autoapi_keep_files = True\n", - "autoapi_dirs = [\"ulab\"]\n", - "autoapi_add_toctree_entry = False\n", - "autoapi_options = ['members', 'undoc-members', 'private-members', 'show-inheritance', 'special-members']\n", - "autoapi_template_dir = '../autoapi/templates'\n", - "autoapi_python_class_content = \"both\"\n", - "autoapi_python_use_implicit_namespaces = True\n", - "autoapi_root = \".\"\n", - "\n", - "\n", "# Read the docs theme\n", "on_rtd = os.environ.get('READTHEDOCS', None) == 'True'\n", "if not on_rtd:\n", @@ -156,49 +143,16 @@ " html_theme = 'default'\n", " html_theme_path = ['.']\n", "else:\n", - " html_theme_path = ['.']\n", - "\n", - "\n", - "class UlabTransform(SphinxTransform):\n", - " default_priority = 870\n", - "\n", - " def _convert_first_paragraph_into_title(self):\n", - " title = self.document.next_node(nodes.title)\n", - " paragraph = self.document.next_node(nodes.paragraph)\n", - " if not title or not paragraph:\n", - " return\n", - " if isinstance(paragraph[0], nodes.paragraph):\n", - " paragraph = paragraph[0]\n", - " if all(isinstance(child, nodes.Text) for child in paragraph.children):\n", - " for child in paragraph.children:\n", - " title.append(nodes.Text(\" \\u2013 \"))\n", - " title.append(child)\n", - " paragraph.parent.remove(paragraph)\n", - "\n", - " def _enable_linking_to_nonclass_targets(self):\n", - " for desc in self.document.traverse(addnodes.desc):\n", - " for xref in desc.traverse(addnodes.pending_xref):\n", - " if xref.attributes.get(\"reftype\") == \"class\":\n", - " xref.attributes.pop(\"refspecific\", None)\n", - "\n", - " def apply(self, **kwargs):\n", - " docname = self.env.docname\n", - " if docname.startswith(\"ulab/\"):\n", - " self._convert_first_paragraph_into_title()\n", - " self._enable_linking_to_nonclass_targets()\n", - "\n", - "\n", - "def setup(app):\n", - " app.add_transform(UlabTransform)" + " html_theme_path = ['.']" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T16:03:54.103540Z", - "start_time": "2020-11-18T16:03:54.089945Z" + "end_time": "2021-01-14T16:17:06.823520Z", + "start_time": "2021-01-14T16:17:06.759683Z" } }, "outputs": [ @@ -228,24 +182,17 @@ " ulab-intro\n", "\n", ".. toctree::\n", - " :maxdepth: 2\n", - " :caption: API Reference\n", - "\n", - " ulab/index.rst\n", - "\n", - ".. toctree::\n", " :maxdepth: 3\n", " :caption: User's guide:\n", "\n", " ulab-ndarray\n", - " ulab-approx\n", - " ulab-compare\n", - " ulab-fft\n", - " ulab-filter\n", - " ulab-linalg\n", - " ulab-numerical\n", - " ulab-poly\n", - " ulab-vectorise\n", + " numpy-functions\n", + " numpy-universal\n", + " numpy-fft\n", + " numpy-linalg\n", + " scipy-optimize\n", + " scipy-signal\n", + " scipy-special\n", " ulab-programming\n", "\n", "Indices and tables\n", @@ -265,11 +212,11 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T16:05:30.108446Z", - "start_time": "2020-11-18T16:05:27.788180Z" + "end_time": "2021-01-14T16:19:01.938959Z", + "start_time": "2021-01-14T16:18:59.040730Z" } }, "outputs": [], @@ -303,25 +250,24 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "ExecuteTime": { - "end_time": "2020-11-18T16:05:38.058388Z", - "start_time": "2020-11-18T16:05:30.135525Z" + "end_time": "2021-01-14T16:19:15.242085Z", + "start_time": "2021-01-14T16:19:01.955650Z" } }, "outputs": [], "source": [ "files = ['ulab-intro',\n", " 'ulab-ndarray',\n", - " 'ulab-approx', \n", - " 'ulab-compare',\n", - " 'ulab-fft',\n", - " 'ulab-filter',\n", - " 'ulab-linalg',\n", - " 'ulab-numerical',\n", - " 'ulab-poly',\n", - " 'ulab-vectorise',\n", + " 'numpy-functions', \n", + " 'numpy-universal',\n", + " 'numpy-fft',\n", + " 'numpy-linalg',\n", + " 'scipy-optimize',\n", + " 'scipy-signal',\n", + " 'scipy-special',\n", " 'ulab-programming']\n", "\n", "for file in files:\n", @@ -494,7 +440,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/ulab-intro.ipynb b/docs/ulab-intro.ipynb index 594d655..64b2aec 100644 --- a/docs/ulab-intro.ipynb +++ b/docs/ulab-intro.ipynb @@ -297,7 +297,7 @@ "\n", "`ulab` has originally been developed for `micropython`, but has since been integrated into a number of its flavours. Most of these flavours are simply forks of `micropython` itself, with some additional functionality. One of the notable exceptions is `circuitpython`, which has slightly diverged at the core level, and this has some minor consequences. Some of these concern the C implementation details only, which all have been sorted out with the generous and enthusiastic support of Jeff Epler from [Adafruit Industries](http://www.adafruit.com).\n", "\n", - "There are, however, a couple of instances, where the usage in the two environments is different at the python level. These are how the packages can be imported, and how the class properties can be accessed. We will point out the differences and possible workarounds at the relevant places in this document." + "There are, however, a couple of instances, where the usage in the two environments is different at the python level. These are how the class properties can be accessed. We will point out the differences and possible workarounds at the relevant places in this document." ] }, { @@ -312,9 +312,6 @@ "All `ulab` options are listed in a single header file, [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h), which contains pre-processor flags for each feature that can be fine-tuned. The first couple of lines of the file look like this\n", "\n", "```c\n", - "#ifndef __ULAB__\n", - "#define __ULAB__\n", - "\n", "// The pre-processor constants in this file determine how ulab behaves:\n", "//\n", "// - how many dimensions ulab can handle\n", @@ -326,49 +323,57 @@ "// A considerable amount of flash space can be saved by removing (setting\n", "// the corresponding constants to 0) the unnecessary functions and features.\n", "\n", - "// Setting this variable to 1 produces numpy-compatible firmware,\n", - "// i.e., functions can be called at the top level,\n", - "// without having to import the sub-modules (linalg and fft are exceptions,\n", - "// since those must be imported even in numpy)\n", - "#define ULAB_NUMPY_COMPATIBILITY (1)\n", + "// Determines, whether scipy is defined in ulab. The sub-modules and functions\n", + "// of scipy have to be defined separately\n", + "#define ULAB_HAS_SCIPY (1)\n", "\n", "// The maximum number of dimensions the firmware should be able to support\n", "// Possible values lie between 1, and 4, inclusive\n", - "#define ULAB_MAX_DIMS 2\n", + "#define ULAB_MAX_DIMS 2\n", "\n", "// By setting this constant to 1, iteration over array dimensions will be implemented\n", "// as a function (ndarray_rewind_array), instead of writing out the loops in macros\n", "// This reduces firmware size at the expense of speed\n", - "#define ULAB_HAS_FUNCTION_ITERATOR (0)\n", + "#define ULAB_HAS_FUNCTION_ITERATOR (0)\n", "\n", "// If NDARRAY_IS_ITERABLE is 1, the ndarray object defines its own iterator function\n", "// This option saves approx. 250 bytes of flash space\n", - "#define NDARRAY_IS_ITERABLE (1)\n", + "#define NDARRAY_IS_ITERABLE (1)\n", "\n", "// Slicing can be switched off by setting this variable to 0\n", - "#define NDARRAY_IS_SLICEABLE (1)\n", + "#define NDARRAY_IS_SLICEABLE (1)\n", "\n", "// The default threshold for pretty printing. These variables can be overwritten\n", "// at run-time via the set_printoptions() function\n", - "#define ULAB_HAS_PRINTOPTIONS (1)\n", - "#define NDARRAY_PRINT_THRESHOLD 10\n", - "#define NDARRAY_PRINT_EDGEITEMS 3\n", + "#define ULAB_HAS_PRINTOPTIONS (1)\n", + "#define NDARRAY_PRINT_THRESHOLD 10\n", + "#define NDARRAY_PRINT_EDGEITEMS 3\n", "\n", - "// determines, whether pi, and e are defined in ulab itself\n", - "#define ULAB_HAS_MATH_CONSTANTS (1)\n", - "\n", - "// determines, whether the ndinfo function is available\n", - "#define ULAB_HAS_NDINFO (1)\n", + "// determines, whether the dtype is an object, or simply a character\n", + "// the object implementation is numpythonic, but requires more space\n", + "#define ULAB_HAS_DTYPE_OBJECT (0)\n", "\n", "// the ndarray binary operators\n", "#define NDARRAY_HAS_BINARY_OPS (1)\n", + "\n", + "// Firmware size can be reduced at the expense of speed by using function\n", + "// pointers in iterations. For each operator, he function pointer saves around\n", + "// 2 kB in the two-dimensional case, and around 4 kB in the four-dimensional case.\n", + "\n", + "#define NDARRAY_BINARY_USES_FUN_POINTER (0)\n", + "\n", "#define NDARRAY_HAS_BINARY_OP_ADD (1)\n", "#define NDARRAY_HAS_BINARY_OP_EQUAL (1)\n", "#define NDARRAY_HAS_BINARY_OP_LESS (1)\n", "#define NDARRAY_HAS_BINARY_OP_LESS_EQUAL (1)\n", "#define NDARRAY_HAS_BINARY_OP_MORE (1)\n", "#define NDARRAY_HAS_BINARY_OP_MORE_EQUAL (1)\n", - "...\n", + "#define NDARRAY_HAS_BINARY_OP_MULTIPLY (1)\n", + "#define NDARRAY_HAS_BINARY_OP_NOT_EQUAL (1)\n", + "#define NDARRAY_HAS_BINARY_OP_POWER (1)\n", + "#define NDARRAY_HAS_BINARY_OP_SUBTRACT (1)\n", + "#define NDARRAY_HAS_BINARY_OP_TRUE_DIVIDE (1)\n", + "... \n", "```\n", "\n", "The meaning of flags with names `_HAS_` should be obvious, so we will just explain the other options. \n", @@ -377,37 +382,7 @@ "\n", "## Compatibility with numpy\n", "\n", - "### Working with sub-modules\n", - "\n", - "The functions implemented in `ulab` are organised in sub-modules at the C level. This modularity is elevated to `python`, if \n", - "\n", - "```c\n", - "#define ULAB_NUMPY_COMPATIBILITY (0)\n", - "```\n", - "\n", - "meaning that if you want to access a particular function, you would have to import the corresponding sub-module first. E.g., you would evaluate a polynomial as \n", - "\n", - "```python\n", - "import ulab\n", - "from ulab import poly\n", - "\n", - "x = ulab.array([4, 5, 6])\n", - "p = ulab.array([1, 2, 3])\n", - "poly.polyval(p, x)\n", - "```\n", - "\n", - "The idea of such grouping of functions and methods at the `python` level is to provide a means for granularity. At first, having to import everything in this way might appear to be overly complicated, but there is a very good reason behind all this: you can find out at the time of importing, whether a function or sub-module is part of your `ulab` firmware, or not. The alternative, namely, that you do not have to import anything beyond `ulab`, could prove catastrophic: you would learn only at run time (at the moment of calling the function in your code) that a particular function is not in the firmware, and that is most probably too late.\n", - "\n", - "### Generating numpy-compatible firmware\n", - "\n", - "`circuitpython` follows the approach above, setting the `ULAB_NUMPY_COMPATIBILITY` flag to 0. On the other hand, if you want to generate truly `numpy`-compatible firmware, you can set \n", - "\n", - "\n", - "```c\n", - "#define ULAB_NUMPY_COMPATIBILITY (1)\n", - "```\n", - "\n", - "If `ULAB_NUMPY_COMPATIBILITY` equals 1, functions will be bound at the top level, meaning that the example above now would look like \n", + "The functions implemented in `ulab` are organised in three sub-modules at the C level, namely, `numpy`, `scipy`, and `user`. This modularity is elevated to `python`, meaning that in order to use functions that are part of `numpy`, you have to import `numpy` as\n", "\n", "```python\n", "from ulab import numpy as np\n", @@ -417,7 +392,7 @@ "np.polyval(p, x)\n", "```\n", "\n", - "There are two exceptions to this rule, namely `fft`, and `linalg`, which are sub-modules even in `numpy`, thus you have to write them out as \n", + "There are a couple of exceptions to this rule, namely `fft`, and `linalg`, which are sub-modules even in `numpy`, thus you have to write them out as \n", "\n", "```python\n", "from ulab import numpy as np\n", @@ -426,9 +401,7 @@ "np.linalg.trace(A)\n", "```\n", "\n", - "It should also be noted that the `numpy`-compatible firmware is a couple of hundred bytes smaller than the one with sub-modules, because defining the sub-modules requires some space.\n", - "\n", - "Some of the functions in `ulab` are re-implementations of `scipy` functions, and they are two be imported as \n", + "Some of the functions in `ulab` are re-implementations of `scipy` functions, and they are to be imported as \n", "\n", "```python\n", "from ulab import numpy as np\n", @@ -439,7 +412,7 @@ "spy.special.erf(x)\n", "```\n", "\n", - "The `numpy`-compatible firmware has one huge advantage over sub-modules: namely, by `try`ing to `import`, we can guarantee that the same, unmodified code runs in `CPython`, as in `micropython`. The following snippet is platform-independent, thus, the `python` code can be tested and debugged on a computer before loading it onto the microcontroller.\n", + "`numpy`-compatibility has an enormous benefit : namely, by `try`ing to `import`, we can guarantee that the same, unmodified code runs in `CPython`, as in `micropython`. The following snippet is platform-independent, thus, the `python` code can be tested and debugged on a computer before loading it onto the microcontroller.\n", "\n", "```python\n", "\n", @@ -463,8 +436,7 @@ "\n", "### Reducing the number of dimensions\n", "\n", - "`ulab` supports tensors of rank four, but this is expensive in terms of flash: with all available functions and options, the library adds around 100 kB to the flash. However, if such high dimensions are not required, significant reductions in size can be gotten by changing the value of \n", - "\n", + "`ulab` supports tensors of rank four, but this is expensive in terms of flash: with all available functions and options, the library adds around 100 kB to the firmware. However, if such high dimensions are not required, significant reductions in size can be gotten by changing the value of \n", "\n", "```c\n", "#define ULAB_MAX_DIMS 2\n", @@ -555,11 +527,11 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 19, "metadata": { "ExecuteTime": { - "end_time": "2021-01-08T12:41:48.295139Z", - "start_time": "2021-01-08T12:41:48.279502Z" + "end_time": "2021-01-12T06:25:27.328061Z", + "start_time": "2021-01-12T06:25:27.308199Z" } }, "outputs": [ @@ -567,7 +539,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "you are running ulab version 2.1.0-2D-numpy\n", + "you are running ulab version 2.1.0-2D\n", "\n", "\n" ] @@ -587,18 +559,61 @@ "source": [ "The first three numbers indicate the major, minor, and sub-minor versions of `ulab` (defined by the `ULAB_VERSION` constant in [ulab.c](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.c)). We usually change the minor version, whenever a new function is added to the code, and the sub-minor version will be incremented, if a bug fix is implemented. \n", "\n", - "`2D` tells us that the particular firmware supports tensors of rank 2 (defined by `ULAB_MAX_DIMS` in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h)), and the string `numpy` means that the firmware is `numpy`-compatible in the sense explained above. Otherwise, you would find `cpy`, i.e., firmware that conforms to `circuitpython`'s conventions. \n", + "`2D` tells us that the particular firmware supports tensors of rank 2 (defined by `ULAB_MAX_DIMS` in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h)). \n", "\n", "If you find a bug, please, include the version string in your report!" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Should you need the numerical value of `ULAB_MAX_DIMS`, you can get it from the version string in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:00:00.616473Z", + "start_time": "2021-01-13T06:00:00.602787Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "version string: 2.1.0-2D\n", + "version dimensions: 2D\n", + "numerical value of dimensions: 2\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab\n", + "\n", + "version = ulab.__version__\n", + "version_dims = version.split('-')[1]\n", + "version_num = int(version_dims.replace('D', ''))\n", + "\n", + "print('version string: ', version)\n", + "print('version dimensions: ', version_dims)\n", + "print('numerical value of dimensions: ', version_num)" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Finding out what your firmware supports\n", "\n", - "`ulab` implements a number of array operators and functions, but this doesn't mean that all of these functions and methods are actually compiled into the firmware. You can fine-tune your firmware by setting/unsetting any of the `_HAS_` constants in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h). \n", + "`ulab` implements a number of array operators and functions, but this does not mean that all of these functions and methods are actually compiled into the firmware. You can fine-tune your firmware by setting/unsetting any of the `_HAS_` constants in [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h). \n", "\n", "### Functions included in the firmware\n", "\n", @@ -709,7 +724,7 @@ "source": [ "### Operators included in the firmware\n", "\n", - "A list of operators cannot be generated as shown above. If you need to find out, whether, e.g., the `**` operator is supported by the firmware, you have to `try` it:" + "A list of operators cannot be generated as shown above. If you really need to find out, whether, e.g., the `**` operator is supported by the firmware, you have to `try` it:" ] }, { @@ -745,6 +760,26 @@ "except Exception as e:\n", " print('operator is not supported: ', e)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The exception above would be raised, if the firmware was compiled with the \n", + "\n", + "```c\n", + "#define NDARRAY_HAS_BINARY_OP_POWER (0)\n", + "```\n", + "\n", + "definition." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/docs/ulab-ndarray.ipynb b/docs/ulab-ndarray.ipynb new file mode 100644 index 0000000..b8348ca --- /dev/null +++ b/docs/ulab-ndarray.ipynb @@ -0,0 +1,3412 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:20:20.064769Z", + "start_time": "2021-01-12T16:20:19.787429Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Notebook magic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:20:21.904391Z", + "start_time": "2021-01-12T16:20:21.897782Z" + } + }, + "outputs": [], + "source": [ + "from IPython.core.magic import Magics, magics_class, line_cell_magic\n", + "from IPython.core.magic import cell_magic, register_cell_magic, register_line_magic\n", + "from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring\n", + "import subprocess\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:20:24.866729Z", + "start_time": "2021-01-12T16:20:24.812364Z" + } + }, + "outputs": [], + "source": [ + "@magics_class\n", + "class PyboardMagic(Magics):\n", + " @cell_magic\n", + " @magic_arguments()\n", + " @argument('-skip')\n", + " @argument('-unix')\n", + " @argument('-pyboard')\n", + " @argument('-file')\n", + " @argument('-data')\n", + " @argument('-time')\n", + " @argument('-memory')\n", + " def micropython(self, line='', cell=None):\n", + " args = parse_argstring(self.micropython, line)\n", + " if args.skip: # doesn't care about the cell's content\n", + " print('skipped execution')\n", + " return None # do not parse the rest\n", + " if args.unix: # tests the code on the unix port. Note that this works on unix only\n", + " with open('/dev/shm/micropython.py', 'w') as fout:\n", + " fout.write(cell)\n", + " proc = subprocess.Popen([\"../../micropython/ports/unix/micropython\", \"/dev/shm/micropython.py\"], \n", + " stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n", + " print(proc.stdout.read().decode(\"utf-8\"))\n", + " print(proc.stderr.read().decode(\"utf-8\"))\n", + " return None\n", + " if args.file: # can be used to copy the cell content onto the pyboard's flash\n", + " spaces = \" \"\n", + " try:\n", + " with open(args.file, 'w') as fout:\n", + " fout.write(cell.replace('\\t', spaces))\n", + " printf('written cell to {}'.format(args.file))\n", + " except:\n", + " print('Failed to write to disc!')\n", + " return None # do not parse the rest\n", + " if args.data: # can be used to load data from the pyboard directly into kernel space\n", + " message = pyb.exec(cell)\n", + " if len(message) == 0:\n", + " print('pyboard >>>')\n", + " else:\n", + " print(message.decode('utf-8'))\n", + " # register new variable in user namespace\n", + " self.shell.user_ns[args.data] = string_to_matrix(message.decode(\"utf-8\"))\n", + " \n", + " if args.time: # measures the time of executions\n", + " pyb.exec('import utime')\n", + " message = pyb.exec('t = utime.ticks_us()\\n' + cell + '\\ndelta = utime.ticks_diff(utime.ticks_us(), t)' + \n", + " \"\\nprint('execution time: {:d} us'.format(delta))\")\n", + " print(message.decode('utf-8'))\n", + " \n", + " if args.memory: # prints out memory information \n", + " message = pyb.exec('from micropython import mem_info\\nprint(mem_info())\\n')\n", + " print(\"memory before execution:\\n========================\\n\", message.decode('utf-8'))\n", + " message = pyb.exec(cell)\n", + " print(\">>> \", message.decode('utf-8'))\n", + " message = pyb.exec('print(mem_info())')\n", + " print(\"memory after execution:\\n========================\\n\", message.decode('utf-8'))\n", + "\n", + " if args.pyboard:\n", + " message = pyb.exec(cell)\n", + " print(message.decode('utf-8'))\n", + "\n", + "ip = get_ipython()\n", + "ip.register_magics(PyboardMagic)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pyboard" + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:35.126401Z", + "start_time": "2020-05-07T07:35:35.105824Z" + } + }, + "outputs": [], + "source": [ + "import pyboard\n", + "pyb = pyboard.Pyboard('/dev/ttyACM0')\n", + "pyb.enter_raw_repl()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-19T19:11:18.145548Z", + "start_time": "2020-05-19T19:11:18.137468Z" + } + }, + "outputs": [], + "source": [ + "pyb.exit_raw_repl()\n", + "pyb.close()" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T07:35:38.725924Z", + "start_time": "2020-05-07T07:35:38.645488Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "import ulab as np\n", + "\n", + "def timeit(n=1000):\n", + " def wrapper(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " run_times = np.zeros(n, dtype=np.uint16)\n", + " for i in range(n):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " run_times[i] = utime.ticks_diff(utime.ticks_us(), t)\n", + " print('{}() execution times based on {} cycles'.format(func_name, n, (delta2-delta1)/n))\n", + " print('\\tbest: %d us'%np.min(run_times))\n", + " print('\\tworst: %d us'%np.max(run_times))\n", + " print('\\taverage: %d us'%np.mean(run_times))\n", + " print('\\tdeviation: +/-%.3f us'%np.std(run_times)) \n", + " return result\n", + " return new_func\n", + " return wrapper\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "__END_OF_DEFS__" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ndarray, the basic container\n", + "\n", + "The `ndarray` is the underlying container of numerical data. It can be thought of as micropython's own `array` object, but has a great number of extra features starting with how it can be initialised, which operations can be done on it, and which functions can accept it as an argument. One important property of an `ndarray` is that it is also a proper `micropython` iterable.\n", + "\n", + "The `ndarray` consists of a short header, and a pointer that holds the data. The pointer always points to a contiguous segment in memory (`numpy` is more flexible in this regard), and the header tells the interpreter, how the data from this segment is to be read out, and what the bytes mean. Some operations, e.g., `reshape`, are fast, because they do not operate on the data, they work on the header, and therefore, only a couple of bytes are manipulated, even if there are a million data entries. A more detailed exposition of how operators are implemented can be found in the section titled [Programming ulab](#Programming_ula).\n", + "\n", + "Since the `ndarray` is a binary container, it is also compact, meaning that it takes only a couple of bytes of extra RAM in addition to what is required for storing the numbers themselves. `ndarray`s are also type-aware, i.e., one can save RAM by specifying a data type, and using the smallest reasonable one. Five such types are defined, namely `uint8`, `int8`, which occupy a single byte of memory per datum, `uint16`, and `int16`, which occupy two bytes per datum, and `float`, which occupies four or eight bytes per datum. The precision/size of the `float` type depends on the definition of `mp_float_t`. Some platforms, e.g., the PYBD, implement `double`s, but some, e.g., the pyboard.v.11, do not. You can find out, what type of float your particular platform implements by looking at the output of the [.itemsize](#.itemsize) class property, or looking at the exact `dtype`, when you print out an array.\n", + "\n", + "In addition to the five above-mentioned numerical types, it is also possible to define Boolean arrays, which can be used in the indexing of data. However, Boolean arrays are really nothing but arrays of type `uint8` with an extra flag. \n", + "\n", + "On the following pages, we will see how one can work with `ndarray`s. Those familiar with `numpy` should find that the nomenclature and naming conventions of `numpy` are adhered to as closely as possible. We will point out the few differences, where necessary.\n", + "\n", + "For the sake of comparison, in addition to the `ulab` code snippets, sometimes the equivalent `numpy` code is also presented. You can find out, where the snippet is supposed to run by looking at its first line, the header of the code block." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The ndinfo function\n", + "\n", + "A concise summary of a couple of the properties of an `ndarray` can be printed out by calling the `ndinfo` \n", + "function. In addition to finding out what the *shape* and *strides* of the array array, we also get the `itemsize`, as well as the type. An interesting piece of information is the *data pointer*, which tells us, what the address of the data segment of the `ndarray` is. We will see the significance of this in the section [Slicing and indexing](#Slicing-and-indexing).\n", + "\n", + "Note that this function simply prints some information, but does not return anything. If you need to get a handle of the data contained in the printout, you should call the dedicated `shape`, `strides`, or `itemsize` functions directly." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:08.710325Z", + "start_time": "2021-01-12T16:24:08.699287Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "class: ndarray\n", + "shape: (5,)\n", + "strides: (8,)\n", + "itemsize: 8\n", + "data pointer: 0x7f8f6fa2e240\n", + "type: float\n", + "\n", + "\n", + "class: ndarray\n", + "shape: (5, 5)\n", + "strides: (5, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7f8f6fa2e2e0\n", + "type: uint8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.float)\n", + "b = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "np.ndinfo(a)\n", + "print('\\n')\n", + "np.ndinfo(b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initialising an array\n", + "\n", + "A new array can be created by passing either a standard micropython iterable, or another `ndarray` into the constructor." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialising by passing iterables\n", + "\n", + "If the iterable is one-dimensional, i.e., one whose elements are numbers, then a row vector will be created and returned. If the iterable is two-dimensional, i.e., one whose elements are again iterables, a matrix will be created. If the lengths of the iterables are not consistent, a `ValueError` will be raised. Iterables of different types can be mixed in the initialisation function. \n", + "\n", + "If the `dtype` keyword with the possible `uint8/int8/uint16/int16/float` values is supplied, the new `ndarray` will have that type, otherwise, it assumes `float` as default. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:21.952689Z", + "start_time": "2021-01-12T16:24:21.938231Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "c:\t array([[0, 1, 2, 3, 4],\n", + " [20, 21, 22, 23, 24],\n", + " [44, 55, 66, 77, 88]], dtype=uint8)\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 15, in \n", + "ValueError: iterables are not of the same length\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b = np.array(a)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"b:\\t\", b)\n", + "\n", + "# a two-dimensional array with mixed-type initialisers\n", + "c = np.array([range(5), range(20, 25, 1), [44, 55, 66, 77, 88]], dtype=np.uint8)\n", + "print(\"\\nc:\\t\", c)\n", + "\n", + "# and now we throw an exception\n", + "d = np.array([range(5), range(10), [44, 55, 66, 77, 88]], dtype=np.uint8)\n", + "print(\"\\nd:\\t\", d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initialising by passing arrays\n", + "\n", + "An `ndarray` can be initialised by supplying another array. This statement is almost trivial, since `ndarray`s are iterables themselves, though it should be pointed out that initialising through arrays is a bit faster. This statement is especially true, if the `dtype`s of the source and output arrays are the same, because then the contents can simply be copied without further ado. While type conversion is also possible, it will always be slower than straight copying." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:33.050654Z", + "start_time": "2021-01-12T16:24:33.039754Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t [1, 2, 3, 4, 5, 6, 7, 8]\n", + "\n", + "b:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "c:\t array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float64)\n", + "\n", + "d:\t array([1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = [1, 2, 3, 4, 5, 6, 7, 8]\n", + "b = np.array(a)\n", + "c = np.array(b)\n", + "d = np.array(b, dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"\\nc:\\t\", c)\n", + "print(\"\\nd:\\t\", d)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the default type of the `ndarray` is `float`. Hence, if the array is initialised from another array, type conversion will always take place, except, when the output type is specifically supplied. I.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:24:39.722844Z", + "start_time": "2021-01-12T16:24:39.709963Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "b:\t array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.uint8)\n", + "b = np.array(a)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "will iterate over the elements in `a`, since in the assignment `b = np.array(a)`, no output type was given, therefore, `float` was assumed. On the other hand, " + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:25:06.597051Z", + "start_time": "2021-01-12T16:25:06.585511Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "b:\t array([0, 1, 2, 3, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(5), dtype=np.uint8)\n", + "b = np.array(a, dtype=np.uint8)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\nb:\\t\", b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "will simply copy the content of `a` into `b` without any iteration, and will, therefore, be faster. Keep this in mind, whenever the output type, or performance is important." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Array initialisation functions\n", + "\n", + "There are seven functions that can be used for initialising an array." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### arange\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.arange.html\n", + "\n", + "The function returns a one-dimensional array with evenly spaced values. Takes 3 positional arguments (two are optional), and the `dtype` keyword argument. " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:03.795728Z", + "start_time": "2021-01-12T16:26:03.782352Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=int16)\n", + "array([2, 3, 4, 5, 6, 7, 8, 9], dtype=int16)\n", + "array([2, 5, 8], dtype=int16)\n", + "array([2.0, 5.0, 8.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.arange(10))\n", + "print(np.arange(2, 10))\n", + "print(np.arange(2, 10, 3))\n", + "print(np.arange(2, 10, 3, dtype=np.float))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### concatenate\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.concatenate.html\n", + "\n", + "The function joins a sequence of arrays, if they are compatible in shape, i.e., if all shapes except the one along the joining axis are equal. " + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:37.145965Z", + "start_time": "2021-01-12T16:26:37.134350Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24],\n", + " [0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "b = np.array(range(15), dtype=np.uint8).reshape((3, 5))\n", + "\n", + "c = np.concatenate((a, b), axis=0)\n", + "print(c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING**: `numpy` accepts arbitrary `dtype`s in the sequence of arrays, in `ulab` the `dtype`s must be identical. If you want to concatenate different types, you have to convert all arrays to the same type first. Here `b` is of `float` type, so it cannot directly be concatenated to `a`. However, if we cast the `dtype` of `b`, the concatenation works:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:26:56.120820Z", + "start_time": "2021-01-12T16:26:56.102365Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[0, 1, 2, 3, 4],\n", + " [5, 6, 7, 8, 9],\n", + " [10, 11, 12, 13, 14],\n", + " [15, 16, 17, 18, 19],\n", + " [20, 21, 22, 23, 24]], dtype=uint8)\n", + "====================\n", + "d: array([[1, 2, 3],\n", + " [4, 5, 6],\n", + " [7, 8, 9],\n", + " [10, 11, 12],\n", + " [13, 14, 15]], dtype=uint8)\n", + "====================\n", + "c: array([[1, 2, 3, 0, 1, 2, 3, 4],\n", + " [4, 5, 6, 5, 6, 7, 8, 9],\n", + " [7, 8, 9, 10, 11, 12, 13, 14],\n", + " [10, 11, 12, 15, 16, 17, 18, 19],\n", + " [13, 14, 15, 20, 21, 22, 23, 24]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(25), dtype=np.uint8).reshape((5, 5))\n", + "b = np.array(range(15), dtype=np.float).reshape((5, 3))\n", + "d = np.array(b+1, dtype=np.uint8)\n", + "print('a: ', a)\n", + "print('='*20 + '\\nd: ', d)\n", + "c = np.concatenate((d, a), axis=1)\n", + "print('='*20 + '\\nc: ', c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### eye\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.eye.html\n", + "\n", + "Another special array method is the `eye` function, whose call signature is \n", + "\n", + "```python\n", + "eye(N, M, k=0, dtype=float)\n", + "```\n", + "where `N` (`M`) specify the dimensions of the matrix (if only `N` is supplied, then we get a square matrix, otherwise one with `M` rows, and `N` columns), and `k` is the shift of the ones (the main diagonal corresponds to `k=0`). Here are a couple of examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### With a single argument" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:08.533394Z", + "start_time": "2021-01-12T16:27:08.518940Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1.0, 0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 1.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 1.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 1.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0, 1.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Specifying the dimensions of the matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:34.075468Z", + "start_time": "2021-01-12T16:27:34.064137Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[0, 0, 0, 0, 0, 0],\n", + " [1, 0, 0, 0, 0, 0],\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 0, 0, 0]], dtype=int16)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(4, M=6, k=-1, dtype=np.int16))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:27:42.492135Z", + "start_time": "2021-01-12T16:27:42.477684Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[1, 0, 0, 0, 0, 0],\n", + " [0, 1, 0, 0, 0, 0],\n", + " [0, 0, 1, 0, 0, 0],\n", + " [0, 0, 0, 1, 0, 0]], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.eye(4, M=6, dtype=np.int8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### full\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.full.html\n", + "\n", + "The function returns an array of arbitrary dimension, whose elements are all equal to the second positional argument. The first argument is a tuple describing the shape of the tensor. The `dtype` keyword argument with a default value of `float` can also be supplied." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:29:11.931011Z", + "start_time": "2021-01-12T16:29:11.915195Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([[3.0, 3.0, 3.0, 3.0],\n", + " [3.0, 3.0, 3.0, 3.0]], dtype=float64)\n", + "\n", + "====================\n", + "\n", + "array([[3, 3, 3, 3],\n", + " [3, 3, 3, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# create an array with the default type\n", + "print(np.full((2, 4), 3))\n", + "\n", + "print('\\n' + '='*20 + '\\n')\n", + "# the array type is uint8 now\n", + "print(np.full((2, 4), 3, dtype=np.uint8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### linspace\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html\n", + "\n", + "This function returns an array, whose elements are uniformly spaced between the `start`, and `stop` points. The number of intervals is determined by the `num` keyword argument, whose default value is 50. With the `endpoint` keyword argument (defaults to `True`) one can include `stop` in the sequence. In addition, the `dtype` keyword can be supplied to force type conversion of the output. The default is `float`. Note that, when `dtype` is of integer type, the sequence is not necessarily evenly spaced. This is not an error, rather a consequence of rounding. (This is also the `numpy` behaviour.)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:29:45.897927Z", + "start_time": "2021-01-12T16:29:45.876325Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default sequence:\t array([0.0, 0.2040816326530612, 0.4081632653061225, ..., 9.591836734693871, 9.795918367346932, 9.999999999999993], dtype=float64)\n", + "num=5:\t\t\t array([0.0, 2.5, 5.0, 7.5, 10.0], dtype=float64)\n", + "num=5:\t\t\t array([0.0, 2.0, 4.0, 6.0, 8.0], dtype=float64)\n", + "num=5:\t\t\t array([0, 0, 1, 2, 2, 3, 4], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# generate a sequence with defaults\n", + "print('default sequence:\\t', np.linspace(0, 10))\n", + "\n", + "# num=5\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 10, num=5))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 10, num=5, endpoint=False))\n", + "\n", + "# num=5, endpoint=False, dtype=uint8\n", + "print('num=5:\\t\\t\\t', np.linspace(0, 5, num=7, endpoint=False, dtype=np.uint8))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### logspace\n", + "\n", + "`linspace`' equivalent for logarithmically spaced data is `logspace`. This function produces a sequence of numbers, in which the quotient of consecutive numbers is constant. This is a geometric sequence.\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.logspace.html\n", + "\n", + "This function returns an array, whose elements are uniformly spaced between the `start`, and `stop` points. The number of intervals is determined by the `num` keyword argument, whose default value is 50. With the `endpoint` keyword argument (defaults to `True`) one can include `stop` in the sequence. In addition, the `dtype` keyword can be supplied to force type conversion of the output. The default is `float`. Note that, exactly as in `linspace`, when `dtype` is of integer type, the sequence is not necessarily evenly spaced in log space.\n", + "\n", + "In addition to the keyword arguments found in `linspace`, `logspace` also accepts the `base` argument. The default value is 10. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:30:44.483893Z", + "start_time": "2021-01-12T16:30:44.466705Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "default sequence:\t array([1.0, 1.151395399326447, 1.325711365590109, ..., 754.3120063354646, 868.5113737513561, 1000.000000000004], dtype=float64)\n", + "num=5:\t\t\t array([10.0, 1778.279410038923, 316227.766016838, 56234132.5190349, 10000000000.0], dtype=float64)\n", + "num=5:\t\t\t array([10.0, 630.9573444801933, 39810.71705534974, 2511886.431509581, 158489319.2461114], dtype=float64)\n", + "num=5:\t\t\t array([2.0, 6.964404506368993, 24.25146506416637, 84.44850628946524, 294.066778879241], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "# generate a sequence with defaults\n", + "print('default sequence:\\t', np.logspace(0, 3))\n", + "\n", + "# num=5\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5, endpoint=False))\n", + "\n", + "# num=5, endpoint=False\n", + "print('num=5:\\t\\t\\t', np.logspace(1, 10, num=5, endpoint=False, base=2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ones, zeros\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.zeros.html\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ones.html\n", + "\n", + "A couple of special arrays and matrices can easily be initialised by calling one of the `ones`, or `zeros` functions. `ones` and `zeros` follow the same pattern, and have the call signature\n", + "\n", + "```python\n", + "ones(shape, dtype=float)\n", + "zeros(shape, dtype=float)\n", + "```\n", + "where shape is either an integer, or a tuple specifying the shape." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-12T16:32:05.422109Z", + "start_time": "2021-01-12T16:32:05.407921Z" + }, + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([1, 1, 1, 1, 1, 1], dtype=uint8)\n", + "array([[0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0]], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "print(np.ones(6, dtype=np.uint8))\n", + "\n", + "print(np.zeros((6, 4)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " When specifying the shape, make sure that the length of the tuple is not larger than the maximum dimension of your firmware." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:01:44.960353Z", + "start_time": "2021-01-13T06:01:44.944935Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "maximum number of dimensions: 2.1.0-2D\n", + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 7, in \n", + "TypeError: too many dimensions\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "import ulab\n", + "\n", + "print('maximum number of dimensions: ', ulab.__version__)\n", + "\n", + "print(np.zeros((2, 2, 2)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Customising array printouts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`ndarray`s are pretty-printed, i.e., if the number of entries along the last axis is larger than 10 (default value), then only the first and last three entries will be printed. Also note that, as opposed to `numpy`, the printout always contains the `dtype`." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:20.162127Z", + "start_time": "2021-01-13T06:02:20.146219Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0.0, 1.0, 2.0, ..., 197.0, 198.0, 199.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(200))\n", + "print(\"a:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### set_printoptions\n", + "\n", + "The default values can be overwritten by means of the `set_printoptions` function [numpy.set_printoptions](https://numpy.org/doc/1.18/reference/generated/numpy.set_printoptions.html), which accepts two keywords arguments, the `threshold`, and the `edgeitems`. The first of these arguments determines the length of the longest array that will be printed in full, while the second is the number of items that will be printed on the left and right hand side of the ellipsis, if the array is longer than `threshold`." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:42.073823Z", + "start_time": "2021-01-13T06:02:42.057424Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a printed with defaults:\t array([0.0, 1.0, 2.0, ..., 17.0, 18.0, 19.0], dtype=float64)\n", + "\n", + "a printed in full:\t\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0], dtype=float64)\n", + "\n", + "a truncated with 2 edgeitems:\t array([0.0, 1.0, ..., 18.0, 19.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(20))\n", + "print(\"a printed with defaults:\\t\", a)\n", + "\n", + "np.set_printoptions(threshold=200)\n", + "print(\"\\na printed in full:\\t\\t\", a)\n", + "\n", + "np.set_printoptions(threshold=10, edgeitems=2)\n", + "print(\"\\na truncated with 2 edgeitems:\\t\", a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### get_printoptions\n", + "\n", + "The set value of the `threshold` and `edgeitems` can be retrieved by calling the `get_printoptions` function with no arguments. The function returns a *dictionary* with two keys." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:51.383653Z", + "start_time": "2021-01-13T06:02:51.372551Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'threshold': 100, 'edgeitems': 20}\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "np.set_printoptions(threshold=100, edgeitems=20)\n", + "print(np.get_printoptions())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Methods and properties of ndarrays\n", + "\n", + "Arrays have several *properties* that can queried, and some methods that can be called. With the exception of the flatten and transpose operators, properties return an object that describe some feature of the array, while the methods return a new array-like object. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .copy\n", + "\n", + "The `.copy` method creates a new *deep copy* of an array, i.e., the entries of the source array are *copied* into the target array." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:02:58.898485Z", + "start_time": "2021-01-13T06:02:58.878864Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "====================\n", + "b: array([1, 2, 3, 4], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = a.copy()\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .dtype\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.dtype.htm\n", + "\n", + "The `.dtype` property returns the `dtype` of an array. This can then be used for initialising another array with the matching type. `ulab` implements two versions of `dtype`; one that is `numpy`-like, i.e., one, which returns a `dtype` object, and one that is significantly cheaper in terms of flash space, but does not define a the `dtype` object, and returns a single character (number) instead. \n", + "\n", + "**WARNING**: in `circuitpython`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-02T17:16:12.818777Z", + "start_time": "2020-11-02T17:16:12.807147Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "dtype of a: dtype('int8')\n", + "\n", + "b: array([5, 6, 7], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = np.array([5, 6, 7], dtype=a.dtype)\n", + "print('a: ', a)\n", + "print('dtype of a: ', a.dtype)\n", + "print('\\nb: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** in `micropython`:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:05:06.183009Z", + "start_time": "2021-01-13T06:05:06.167855Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "dtype of a: dtype('int8')\n", + "\n", + "b: array([5, 6, 7], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = np.array([5, 6, 7], dtype=a.dtype())\n", + "print('a: ', a)\n", + "print('dtype of a: ', a.dtype())\n", + "print('\\nb: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If the `ulab.h` header file sets the pre-processor constant `ULAB_HAS_DTYPE_OBJECT` to 0 as\n", + "\n", + "```c\n", + "#define ULAB_HAS_DTYPE_OBJECT (0)\n", + "```\n", + "then the output of the previous snippet will be" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "ExecuteTime": { + "end_time": "2020-11-02T20:36:23.099166Z", + "start_time": "2020-11-02T20:36:23.088586Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([1, 2, 3, 4], dtype=int8)\n", + "dtype of a: 98\n", + "\n", + "b: array([5, 6, 7], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "b = np.array([5, 6, 7], dtype=a.dtype())\n", + "print('a: ', a)\n", + "print('dtype of a: ', a.dtype())\n", + "print('\\nb: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here 98 is nothing but the ASCII value of the character `b`, which is the type code for signed 8-bit integers. The object definition adds around 600 bytes to the firmware." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .flatten\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.htm\n", + "\n", + "`.flatten` returns the flattened array. The array can be flattened in `C` style (i.e., moving along the last axis in the tensor), or in `fortran` style (i.e., moving along the first axis in the tensor)." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:07:16.735771Z", + "start_time": "2021-01-13T06:07:16.723514Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: \t\t array([1, 2, 3, 4], dtype=int8)\n", + "a flattened: \t array([1, 2, 3, 4], dtype=int8)\n", + "\n", + "b: array([[1, 2, 3],\n", + " [4, 5, 6]], dtype=int8)\n", + "b flattened (C): \t array([1, 2, 3, 4, 5, 6], dtype=int8)\n", + "b flattened (F): \t array([1, 4, 2, 5, 3, 6], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a: \\t\\t\", a)\n", + "print(\"a flattened: \\t\", a.flatten())\n", + "\n", + "b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int8)\n", + "print(\"\\nb:\", b)\n", + "\n", + "print(\"b flattened (C): \\t\", b.flatten())\n", + "print(\"b flattened (F): \\t\", b.flatten(order='F'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .itemsize\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.itemsize.html\n", + "\n", + "The `.itemsize` method (property) returns an integer with the size of elements in the array.\n", + "\n", + "**WARNING:** In `circuitpython`:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:07:49.080817Z", + "start_time": "2021-01-13T06:07:49.065749Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "itemsize of a: 1\n", + "\n", + "b:\n", + " array([[1.0, 2.0],\n", + " [3.0, 4.0]], dtype=float64)\n", + "itemsize of b: 8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"itemsize of a:\", a.itemsize\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.float)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"itemsize of b:\", b.itemsize" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** In `micropython`:" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:01.630382Z", + "start_time": "2021-01-13T06:08:01.619318Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "itemsize of a: >\n", + "\n", + "b:\n", + " array([[1.0, 2.0],\n", + " [3.0, 4.0]], dtype=float64)\n", + "itemsize of b: 8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"itemsize of a:\", a.itemsize)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.float)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"itemsize of b:\", b.itemsize())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .reshape\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html\n", + "\n", + "`reshape` re-writes the shape properties of an `ndarray`, but the array will not be modified in any other way. The function takes a single 2-tuple with two integers as its argument. The 2-tuple should specify the desired number of rows and columns. If the new shape is not consistent with the old, a `ValueError` exception will be raised." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:12.234490Z", + "start_time": "2021-01-13T06:08:12.217652Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a (4 by 4): array([[1, 2, 3, 4],\n", + " [5, 6, 7, 8],\n", + " [9, 10, 11, 12],\n", + " [13, 14, 15, 16]], dtype=uint8)\n", + "a (2 by 8): array([[1, 2, 3, 4, 5, 6, 7, 8],\n", + " [9, 10, 11, 12, 13, 14, 15, 16]], dtype=uint8)\n", + "a (1 by 16): array([[1, 2, 3, ..., 14, 15, 16]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], dtype=np.uint8)\n", + "print('a (4 by 4):', a)\n", + "print('a (2 by 8):', a.reshape((2, 8)))\n", + "print('a (1 by 16):', a.reshape((1, 16)))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .shape\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.shape.html\n", + "\n", + "The `.shape` method (property) returns a tuple with the length of the array in along each dimension. \n", + "\n", + "**WARNING:** In `circuitpython`, you can call the method as a property, i.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:50.479850Z", + "start_time": "2021-01-13T06:08:50.464741Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3, 4], dtype=int8)\n", + "shape of a: (4,)\n", + "\n", + "b:\n", + " array([[1, 2],\n", + " [3, 4]], dtype=int8)\n", + "shape of b: (2, 2)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"shape of a:\", a.shape)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"shape of b:\", b.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** On the other hand, since properties are not implemented in `micropython`, there you would call the method as a function, i.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:08:36.531124Z", + "start_time": "2021-01-13T06:08:36.515040Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3, 4], dtype=int8)\n", + "shape of a: (4,)\n", + "\n", + "b:\n", + " array([[1, 2],\n", + " [3, 4]], dtype=int8)\n", + "shape of b: (2, 2)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"shape of a:\", a.shape())\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"shape of b:\", b.shape())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .size\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.size.html\n", + "\n", + "The `.size` method (property) returns an integer with the number of elements in the array. \n", + "\n", + "**WARNING:** In `circuitpython`, the `numpy` nomenclature applies, i.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "ExecuteTime": { + "end_time": "2020-02-11T06:32:22.721112Z", + "start_time": "2020-02-11T06:32:22.713111Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "size of a: 3\n", + "\n", + "b:\n", + " array([[1, 2],\n", + "\t [3, 4]], dtype=int8)\n", + "size of b: 4\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"size of a:\", a.size)\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"size of b:\", b.size)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** In `micropython`, `size` is a method, i.e., " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:09:20.062380Z", + "start_time": "2021-01-13T06:09:20.045307Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([1, 2, 3], dtype=int8)\n", + "size of a: 3\n", + "\n", + "b:\n", + " array([[1, 2],\n", + " [3, 4]], dtype=int8)\n", + "size of b: 4\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3], dtype=np.int8)\n", + "print(\"a:\\n\", a)\n", + "print(\"size of a:\", a.size())\n", + "\n", + "b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n", + "print(\"\\nb:\\n\", b)\n", + "print(\"size of b:\", b.size())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .tobytes\n", + "\n", + "`numpy`: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tobytes.html\n", + "\n", + "The `.tobytes` method can be used for acquiring a handle of the underlying data pointer of an array, and it returns a new `bytearray` that can be fed into any method that can accep a `bytearray`, e.g., ADC data can be buffered into this `bytearray`, or the `bytearray` can be fed into a DAC. Since the `bytearray` is really nothing but the bare data container of the array, any manipulation on the `bytearray` automatically modifies the array itself.\n", + "\n", + "Note that the method raises a `ValueError` exception, if the array is not dense (i.e., it has already been sliced)." + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:09:57.262071Z", + "start_time": "2021-01-13T06:09:57.250519Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\n", + "b: bytearray(b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07')\n", + "====================\n", + "b: bytearray(b'\\r\\x01\\x02\\x03\\x04\\x05\\x06\\x07')\n", + "a: array([13, 1, 2, 3, 4, 5, 6, 7], dtype=uint8)\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", + "print('a: ', a)\n", + "b = a.tobytes()\n", + "print('b: ', b)\n", + "\n", + "# modify b\n", + "b[0] = 13\n", + "\n", + "print('='*20)\n", + "print('b: ', b)\n", + "print('a: ', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .transpose\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.transpose.html\n", + "\n", + "Returns the transposed array. Only defined, if the number of maximum dimensions is larger than 1." + ] + }, + { + "cell_type": "code", + "execution_count": 384, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T08:39:11.844987Z", + "start_time": "2019-10-19T08:39:11.828099Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1, 2, 3],\n", + "\t [4, 5, 6],\n", + "\t [7, 8, 9],\n", + "\t [10, 11, 12]], dtype=uint8)\n", + "shape of a: (4, 3)\n", + "\n", + "transpose of a:\n", + " array([[1, 4, 7, 10],\n", + "\t [2, 5, 8, 11],\n", + "\t [3, 6, 9, 12]], dtype=uint8)\n", + "shape of a: (3, 4)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]], dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "print('shape of a:', a.shape())\n", + "a.transpose()\n", + "print('\\ntranspose of a:\\n', a)\n", + "print('shape of a:', a.shape())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### .sort\n", + "\n", + "`numpy`: https://docs.scipy.org/doc/numpy/reference/generated/numpy.sort.html\n", + "\n", + "In-place sorting of an `ndarray`. For a more detailed exposition, see [sort](#sort)." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:11:20.989109Z", + "start_time": "2021-01-13T06:11:20.972842Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "a:\n", + " array([[1, 12, 3, 0],\n", + " [5, 3, 4, 1],\n", + " [9, 11, 1, 8],\n", + " [7, 10, 0, 1]], dtype=uint8)\n", + "\n", + "a sorted along vertical axis:\n", + " array([[1, 3, 0, 0],\n", + " [5, 10, 1, 1],\n", + " [7, 11, 3, 1],\n", + " [9, 12, 4, 8]], dtype=uint8)\n", + "\n", + "a sorted along horizontal axis:\n", + " array([[0, 1, 3, 12],\n", + " [1, 3, 4, 5],\n", + " [1, 8, 9, 11],\n", + " [0, 1, 7, 10]], dtype=uint8)\n", + "\n", + "flattened a sorted:\n", + " array([0, 0, 1, ..., 10, 11, 12], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "print('\\na:\\n', a)\n", + "a.sort(axis=0)\n", + "print('\\na sorted along vertical axis:\\n', a)\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "a.sort(axis=1)\n", + "print('\\na sorted along horizontal axis:\\n', a)\n", + "\n", + "a = np.array([[1, 12, 3, 0], [5, 3, 4, 1], [9, 11, 1, 8], [7, 10, 0, 1]], dtype=np.uint8)\n", + "a.sort(axis=None)\n", + "print('\\nflattened a sorted:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unary operators\n", + "\n", + "With the exception of `len`, which returns a single number, all unary operators manipulate the underlying data element-wise. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### len\n", + "\n", + "This operator takes a single argument, the array, and returns either the length of the first axis." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:11:49.266192Z", + "start_time": "2021-01-13T06:11:49.255493Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4, 5], dtype=uint8)\n", + "length of a: 5\n", + "shape of a: (5,)\n", + "\n", + "b:\t array([[0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4],\n", + " [0, 1, 2, 3, 4]], dtype=uint8)\n", + "length of b: 2\n", + "shape of b: (4, 5)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([range(5), range(5), range(5), range(5)], dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "print(\"length of a: \", len(a))\n", + "print(\"shape of a: \", a.shape())\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"length of b: \", len(b))\n", + "print(\"shape of b: \", b.shape())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " The number returned by `len` is also the length of the iterations, when the array supplies the elements for an iteration (see later)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### invert\n", + "\n", + "The function is defined for integer data types (`uint8`, `int8`, `uint16`, and `int16`) only, takes a single argument, and returns the element-by-element, bit-wise inverse of the array. If a `float` is supplied, the function raises a `ValueError` exception.\n", + "\n", + "With signed integers (`int8`, and `int16`), the results might be unexpected, as in the example below:" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:16:16.754210Z", + "start_time": "2019-10-11T13:16:16.735618Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([0, -1, -100], dtype=int8)\n", + "inverse of a:\t array([-1, 0, 99], dtype=int8)\n", + "\n", + "a:\t\t array([0, 1, 254, 255], dtype=uint8)\n", + "inverse of a:\t array([255, 254, 1, 0], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, -1, -100], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"inverse of a:\\t\", ~a)\n", + "\n", + "a = np.array([0, 1, 254, 255], dtype=np.uint8)\n", + "print(\"\\na:\\t\\t\", a)\n", + "print(\"inverse of a:\\t\", ~a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### abs\n", + "\n", + "This function takes a single argument, and returns the element-by-element absolute value of the array. When the data type is unsigned (`uint8`, or `uint16`), a copy of the array will be returned immediately, and no calculation takes place." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:05:43.926821Z", + "start_time": "2019-10-11T13:05:43.912629Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t\t array([0, -1, -100], dtype=int8)\n", + "absolute value of a:\t array([0, 1, 100], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([0, -1, -100], dtype=np.int8)\n", + "print(\"a:\\t\\t\\t \", a)\n", + "print(\"absolute value of a:\\t \", abs(a))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### neg\n", + "\n", + "This operator takes a single argument, and changes the sign of each element in the array. Unsigned values are wrapped. " + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:17:00.946009Z", + "start_time": "2019-10-11T13:17:00.927264Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([10, -1, 1], dtype=int8)\n", + "negative of a:\t array([-10, 1, -1], dtype=int8)\n", + "\n", + "b:\t\t array([0, 100, 200], dtype=uint8)\n", + "negative of b:\t array([0, 156, 56], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([10, -1, 1], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"negative of a:\\t\", -a)\n", + "\n", + "b = np.array([0, 100, 200], dtype=np.uint8)\n", + "print(\"\\nb:\\t\\t\", b)\n", + "print(\"negative of b:\\t\", -b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### pos\n", + "\n", + "This function takes a single argument, and simply returns a copy of the array." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-11T13:09:15.965662Z", + "start_time": "2019-10-11T13:09:15.945461Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t\t array([10, -1, 1], dtype=int8)\n", + "positive of a:\t array([10, -1, 1], dtype=int8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([10, -1, 1], dtype=np.int8)\n", + "print(\"a:\\t\\t\", a)\n", + "print(\"positive of a:\\t\", +a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary operators\n", + "\n", + "`ulab` implements the `+`, `-`, `*`, `/`, `**`, `<`, `>`, `<=`, `>=`, `==`, `!=`, `+=`, `-=`, `*=`, `/=`, `**=` binary operators that work element-wise. Broadcasting is available, meaning that the two operands do not even have to have the same shape. If the lengths along the respective axes are equal, or one of them is 1, or the axis is missing, the element-wise operation can still be carried out. \n", + "A thorough explanation of broadcasting can be found under https://numpy.org/doc/stable/user/basics.broadcasting.html. \n", + "\n", + "**WARNING**: note that relational operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) should have the `ndarray` on their left hand side, when compared to scalars. This means that the following works" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:12:30.802935Z", + "start_time": "2021-01-13T06:12:30.786069Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([False, False, True], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print(a > 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "while the equivalent statement, `2 < a`, will raise a `TypeError` exception:" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:12:51.262197Z", + "start_time": "2021-01-13T06:12:51.244206Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 5, in \n", + "TypeError: unsupported types for __lt__: 'int', 'ndarray'\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3])\n", + "print(2 < a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING:** `circuitpython` users should use the `equal`, and `not_equal` operators instead of `==`, and `!=`. See the section on [array comparison](#Comparison-of-arrays) for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Upcasting\n", + "\n", + "Binary operations require special attention, because two arrays with different typecodes can be the operands of an operation, in which case it is not trivial, what the typecode of the result is. This decision on the result's typecode is called upcasting. Since the number of typecodes in `ulab` is significantly smaller than in `numpy`, we have to define new upcasting rules. Where possible, I followed `numpy`'s conventions. \n", + "\n", + "`ulab` observes the following upcasting rules:\n", + "\n", + "1. Operations on two `ndarray`s of the same `dtype` preserve their `dtype`, even when the results overflow.\n", + "\n", + "2. if either of the operands is a float, the result is automatically a float\n", + "\n", + "3. When one of the operands is a scalar, it will internally be turned into a single-element `ndarray` with the *smallest* possible `dtype`. Thus, e.g., if the scalar is 123, it will be converted into an array of `dtype` `uint8`, while -1000 will be converted into `int16`. An `mp_obj_float`, will always be promoted to `dtype` `float`. Other micropython types (e.g., lists, tuples, etc.) raise a `TypeError` exception. \n", + "\n", + "4. \n", + " \n", + "| left hand side | right hand side | ulab result | numpy result |\n", + "|----------------|-----------------|-------------|--------------|\n", + "|`uint8` |`int8` |`int16` |`int16` |\n", + "|`uint8` |`int16` |`int16` |`int16` |\n", + "|`uint8` |`uint16` |`uint16` |`uint16` |\n", + "|`int8` |`int16` |`int16` |`int16` | \n", + "|`int8` |`uint16` |`uint16` |`int32` |\n", + "|`uint16` |`int16` |`float` |`int32` |\n", + " \n", + "Note that the last two operations are promoted to `int32` in `numpy`.\n", + " \n", + "**WARNING:** Due to the lower number of available data types, the upcasting rules of `ulab` are slightly different to those of `numpy`. Watch out for this, when porting code!\n", + "\n", + "Upcasting can be seen in action in the following snippet:" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:13:23.026904Z", + "start_time": "2021-01-13T06:13:23.009315Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4], dtype=uint8)\n", + "b:\t array([1, 2, 3, 4], dtype=int8)\n", + "a+b:\t array([2, 4, 6, 8], dtype=int16)\n", + "\n", + "a:\t array([1, 2, 3, 4], dtype=uint8)\n", + "c:\t array([1.0, 2.0, 3.0, 4.0], dtype=float64)\n", + "a*c:\t array([1.0, 4.0, 9.0, 16.0], dtype=float64)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4], dtype=np.uint8)\n", + "b = np.array([1, 2, 3, 4], dtype=np.int8)\n", + "print(\"a:\\t\", a)\n", + "print(\"b:\\t\", b)\n", + "print(\"a+b:\\t\", a+b)\n", + "\n", + "c = np.array([1, 2, 3, 4], dtype=np.float)\n", + "print(\"\\na:\\t\", a)\n", + "print(\"c:\\t\", c)\n", + "print(\"a*c:\\t\", a*c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmarks\n", + "\n", + "The following snippet compares the performance of binary operations to a possible implementation in python. For the time measurement, we will take the following snippet from the micropython manual:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "ExecuteTime": { + "end_time": "2020-05-07T06:39:52.225256Z", + "start_time": "2020-05-07T06:39:52.194691Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "import utime\n", + "\n", + "def timeit(f, *args, **kwargs):\n", + " func_name = str(f).split(' ')[1]\n", + " def new_func(*args, **kwargs):\n", + " t = utime.ticks_us()\n", + " result = f(*args, **kwargs)\n", + " print('execution time: ', utime.ticks_diff(utime.ticks_us(), t), ' us')\n", + " return result\n", + " return new_func" + ] + }, + { + "cell_type": "code", + "execution_count": 490, + "metadata": { + "ExecuteTime": { + "end_time": "2019-10-19T13:23:45.432395Z", + "start_time": "2019-10-19T13:23:45.344021Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python add:\n", + "execution time: 10051 us\n", + "\n", + "python multiply:\n", + "execution time: 14175 us\n", + "\n", + "ulab add:\n", + "execution time: 222 us\n", + "\n", + "ulab multiply:\n", + "execution time: 213 us\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -pyboard 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "@timeit\n", + "def py_add(a, b):\n", + " return [a[i]+b[i] for i in range(1000)]\n", + "\n", + "@timeit\n", + "def py_multiply(a, b):\n", + " return [a[i]*b[i] for i in range(1000)]\n", + "\n", + "@timeit\n", + "def ulab_add(a, b):\n", + " return a + b\n", + "\n", + "@timeit\n", + "def ulab_multiply(a, b):\n", + " return a * b\n", + "\n", + "a = [0.0]*1000\n", + "b = range(1000)\n", + "\n", + "print('python add:')\n", + "py_add(a, b)\n", + "\n", + "print('\\npython multiply:')\n", + "py_multiply(a, b)\n", + "\n", + "a = np.linspace(0, 10, num=1000)\n", + "b = np.ones(1000)\n", + "\n", + "print('\\nulab add:')\n", + "ulab_add(a, b)\n", + "\n", + "print('\\nulab multiply:')\n", + "ulab_multiply(a, b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The python implementation above is not perfect, and certainly, there is much room for improvement. However, the factor of 50 difference in execution time is very spectacular. This is nothing but a consequence of the fact that the `ulab` functions run `C` code, with very little python overhead. The factor of 50 appears to be quite universal: the FFT routine obeys similar scaling (see [Speed of FFTs](#Speed-of-FFTs)), and this number came up with font rendering, too: [fast font rendering on graphical displays](https://forum.micropython.org/viewtopic.php?f=15&t=5815&p=33362&hilit=ufont#p33383)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparison operators\n", + "\n", + "The smaller than, greater than, smaller or equal, and greater or equal operators return a vector of Booleans indicating the positions (`True`), where the condition is satisfied. " + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T15:08:38.673585Z", + "start_time": "2020-10-17T15:08:38.659225Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([True, True, True, True, False, False, False, False], dtype=bool)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8)\n", + "print(a < 5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**WARNING**: at the moment, due to `micropython`'s implementation details, the `ndarray` must be on the left hand side of the relational operators.\n", + "\n", + "That is, while `a < 5` and `5 > a` have the same meaning, the following code will not work:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Traceback (most recent call last):\n", + " File \"/dev/shm/micropython.py\", line 5, in \n", + "TypeError: unsupported types for __gt__: 'int', 'ndarray'\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "import ulab as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint8)\n", + "print(5 > a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Iterating over arrays\n", + "\n", + "`ndarray`s are iterable, which means that their elements can also be accessed as can the elements of a list, tuple, etc. If the array is one-dimensional, the iterator returns scalars, otherwise a new reduced-dimensional *view* is created and returned." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "metadata": { + "ExecuteTime": { + "end_time": "2021-01-13T06:14:11.756254Z", + "start_time": "2021-01-13T06:14:11.742246Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([1, 2, 3, 4, 5], dtype=uint8)\n", + "element 0 in a: 1\n", + "element 1 in a: 2\n", + "element 2 in a: 3\n", + "element 3 in a: 4\n", + "element 4 in a: 5\n", + "\n", + "b:\t array([[0, 1, 2, 3, 4],\n", + " [10, 11, 12, 13, 14],\n", + " [20, 21, 22, 23, 24],\n", + " [30, 31, 32, 33, 34]], dtype=uint8)\n", + "element 0 in b: array([0, 1, 2, 3, 4], dtype=uint8)\n", + "element 1 in b: array([10, 11, 12, 13, 14], dtype=uint8)\n", + "element 2 in b: array([20, 21, 22, 23, 24], dtype=uint8)\n", + "element 3 in b: array([30, 31, 32, 33, 34], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([1, 2, 3, 4, 5], dtype=np.uint8)\n", + "b = np.array([range(5), range(10, 15, 1), range(20, 25, 1), range(30, 35, 1)], dtype=np.uint8)\n", + "\n", + "print(\"a:\\t\", a)\n", + "\n", + "for i, _a in enumerate(a):\n", + " print(\"element %d in a:\"%i, _a)\n", + " \n", + "print(\"\\nb:\\t\", b)\n", + "\n", + "for i, _b in enumerate(b):\n", + " print(\"element %d in b:\"%i, _b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Slicing and indexing\n", + "\n", + "\n", + "### Views vs. copies\n", + "\n", + "`numpy` has a very important concept called *views*, which is a powerful extension of `python`'s own notion of slicing. Slices are special python objects of the form\n", + "\n", + "```python\n", + "slice = start:end:stop\n", + "```\n", + "\n", + "where `start`, `end`, and `stop` are (not necessarily non-negative) integers. Not all of these three numbers must be specified in an index, in fact, all three of them can be missing. The interpreter takes care of filling in the missing values. (Note that slices cannot be defined in this way, only there, where an index is expected.) For a good explanation on how slices work in python, you can read the stackoverflow question https://stackoverflow.com/questions/509211/understanding-slice-notation.\n", + "\n", + "In order to see what slicing does, let us take the string `a = '012345679'`! We can extract every second character by creating the slice `::2`, which is equivalent to `0:len(a):2`, i.e., increments the character pointer by 2 starting from 0, and traversing the string up to the very end." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T05:26:17.758735Z", + "start_time": "2020-10-12T05:26:17.748487Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'02468'" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "string = '0123456789'\n", + "string[::2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can do the same with numerical arrays." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T05:25:49.352435Z", + "start_time": "2020-10-12T05:25:49.339452Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)\n", + "a[::2]:\t array([0, 2, 4, 6, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print('a:\\t', a)\n", + "\n", + "print('a[::2]:\\t', a[::2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This looks similar to `string` above, but there is a very important difference that is not so obvious. Namely, `string[::2]` produces a partial copy of `string`, while `a[::2]` only produces a *view* of `a`. What this means is that `a`, and `a[::2]` share their data, and the only difference between the two is, how the data are read out. In other words, internally, `a[::2]` has the same data pointer as `a`. We can easily convince ourselves that this is indeed the case by calling the [ndinfo](#The_ndinfo_function) function: the *data pointer* entry is the same in the two printouts." + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-16T18:43:07.480791Z", + "start_time": "2020-10-16T18:43:07.471473Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8) \n", + "\n", + "class: ndarray\n", + "shape: (10,)\n", + "strides: (1,)\n", + "itemsize: 1\n", + "data pointer: 0x7ff6c6193220\n", + "type: uint8\n", + "\n", + "====================\n", + "a[::2]: array([0, 2, 4, 6, 8], dtype=uint8) \n", + "\n", + "class: ndarray\n", + "shape: (5,)\n", + "strides: (2,)\n", + "itemsize: 1\n", + "data pointer: 0x7ff6c6193220\n", + "type: uint8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print('a: ', a, '\\n')\n", + "np.ndinfo(a)\n", + "print('\\n' + '='*20)\n", + "print('a[::2]: ', a[::2], '\\n')\n", + "np.ndinfo(a[::2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you are still a bit confused about the meaning of *views*, the section [Slicing and assigning to slices](#Slicing-and-assigning-to-slices) should clarify the issue." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Indexing\n", + "\n", + "The simplest form of indexing is specifying a single integer between the square brackets as in " + ] + }, + { + "cell_type": "code", + "execution_count": 64, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:31:45.485584Z", + "start_time": "2020-10-12T18:31:45.464551Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)\n", + "the first, and last element of a:\n", + " 0 9\n", + "the second, and last but one element of a:\n", + " 1 8\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(10), dtype=np.uint8)\n", + "print(\"a: \", a)\n", + "print(\"the first, and last element of a:\\n\", a[0], a[-1])\n", + "print(\"the second, and last but one element of a:\\n\", a[1], a[-2])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indexing can be applied to higher-dimensional tensors, too. When the length of the indexing sequences is smaller than the number of dimensions, a new *view* is returned, otherwise, we get a single number." + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:26:12.783883Z", + "start_time": "2020-10-12T18:26:12.770180Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 1, 2],\n", + "\t[3, 4, 5],\n", + "\t[6, 7, 8]], dtype=uint8)\n", + "a[0]:\n", + " array([[0, 1, 2]], dtype=uint8)\n", + "a[1,1]: 4\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8).reshape((3, 3))\n", + "print(\"a:\\n\", a)\n", + "print(\"a[0]:\\n\", a[0])\n", + "print(\"a[1,1]: \", a[1,1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indices can also be a list of Booleans. By using a Boolean list, we can select those elements of an array that satisfy a specific condition. At the moment, such indexing is defined for row vectors only; when the rank of the tensor is higher than 1, the function raises a `NotImplementedError` exception, though this will be rectified in a future version of `ulab`." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:34:34.105614Z", + "start_time": "2020-10-12T17:34:34.094017Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)\n", + "a < 5:\t array([0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.float)\n", + "print(\"a:\\t\", a)\n", + "print(\"a < 5:\\t\", a[a < 5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Indexing with Boolean arrays can take more complicated expressions. This is a very concise way of comparing two vectors, e.g.:" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T18:03:38.846377Z", + "start_time": "2020-10-12T18:03:38.826689Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\t array([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "a**2:\t array([0, 1, 4, 9, 16, 25, 36, 49, 64], dtype=uint16)\n", + "\n", + "b:\t array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=uint8)\n", + "\n", + "100*sin(b):\t array([-75.68024953079282, -75.68024953079282, -75.68024953079282, 14.11200080598672, 14.11200080598672, 14.11200080598672, 42.01670368266409, 42.01670368266409, 42.01670368266409], dtype=float)\n", + "\n", + "a[a*a > np.sin(b)*100.0]:\t array([0, 1, 2, 4, 5, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array([4, 4, 4, 3, 3, 3, 13, 13, 13], dtype=np.uint8)\n", + "print(\"a:\\t\", a)\n", + "print(\"\\na**2:\\t\", a*a)\n", + "print(\"\\nb:\\t\", b)\n", + "print(\"\\n100*sin(b):\\t\", np.sin(b)*100.0)\n", + "print(\"\\na[a*a > np.sin(b)*100.0]:\\t\", a[a*a > np.sin(b)*100.0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Boolean indices can also be used in assignments, if the array is one-dimensional. The following example replaces the data in an array, wherever some condition is fulfilled." + ] + }, + { + "cell_type": "code", + "execution_count": 72, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-13T16:14:21.055356Z", + "start_time": "2020-10-13T16:14:21.035329Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2], dtype=uint8)\n", + "array([123, 123, 123, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array(range(9)) + 12\n", + "\n", + "print(a[b < 15])\n", + "\n", + "a[b < 15] = 123\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On the right hand side of the assignment we can even have another array." + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-13T16:14:10.054210Z", + "start_time": "2020-10-13T16:14:10.039523Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "array([0, 1, 2], dtype=uint8) array([12.0, 13.0, 14.0], dtype=float)\n", + "array([12, 13, 14, 3, 4, 5, 6, 7, 8], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array(range(9), dtype=np.uint8)\n", + "b = np.array(range(9)) + 12\n", + "\n", + "print(a[b < 15], b[b < 15])\n", + "\n", + "a[b < 15] = b[b < 15]\n", + "print(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Slicing and assigning to slices\n", + "\n", + "You can also generate sub-arrays by specifying slices as the index of an array. Slices are special python objects of the form " + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:38:15.975404Z", + "start_time": "2020-10-12T17:38:15.955070Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[1, 2, 3],\n", + "\t[4, 5, 6],\n", + "\t[7, 8, 9]], dtype=uint8)\n", + "\n", + "a[0]:\n", + " array([[1, 2, 3]], dtype=uint8)\n", + "\n", + "a[0,:2]:\n", + " array([[1, 2]], dtype=uint8)\n", + "\n", + "a[:,0]:\n", + " array([[1],\n", + "\t[4],\n", + "\t[7]], dtype=uint8)\n", + "\n", + "a[-1]:\n", + " array([[7, 8, 9]], dtype=uint8)\n", + "\n", + "a[-1:-3:-1]:\n", + " array([[7, 8, 9],\n", + "\t[4, 5, 6]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "\n", + "# the first row\n", + "print('\\na[0]:\\n', a[0])\n", + "\n", + "# the first two elements of the first row\n", + "print('\\na[0,:2]:\\n', a[0,:2])\n", + "\n", + "# the zeroth element in each row (also known as the zeroth column)\n", + "print('\\na[:,0]:\\n', a[:,0])\n", + "\n", + "# the last row\n", + "print('\\na[-1]:\\n', a[-1])\n", + "\n", + "# the last two rows backwards\n", + "print('\\na[-1:-3:-1]:\\n', a[-1:-3:-1])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assignment to slices can be done for the whole slice, per row, and per column. A couple of examples should make these statements clearer:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:40:24.031254Z", + "start_time": "2020-10-12T17:40:24.011816Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a:\n", + " array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "a[0] = 1\n", + " array([[1, 1, 1],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "a[:,0]:\n", + " array([[0, 0, 3],\n", + "\t[0, 0, 3],\n", + "\t[0, 0, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "print('a:\\n', a)\n", + "\n", + "# assigning to the whole row\n", + "a[0] = 1\n", + "print('\\na[0] = 1\\n', a)\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "\n", + "# assigning to a column\n", + "a[:,2] = 3.0\n", + "print('\\na[:,0]:\\n', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, you should notice that we re-set the array `a` after the first assignment. Do you care to see what happens, if we do not do that? Well, here are the results:" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-12T17:44:09.180623Z", + "start_time": "2020-10-12T17:44:09.161578Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "a: array([[1, 1, 3],\n", + "\t[0, 0, 3],\n", + "\t[0, 0, 3]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a[:,:]\n", + "# assign 1 to the first row\n", + "b[0] = 1\n", + "\n", + "# assigning to the last column\n", + "b[:,2] = 3\n", + "print('a: ', a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that both assignments involved `b`, and not `a`, yet, when we print out `a`, its entries are updated. This proves our earlier statement about the behaviour of *views*: in the statement `b = a[:,:]` we simply created a *view* of `a`, and not a *deep* copy of it, meaning that whenever we modify `b`, we actually modify `a`, because the underlying data container of `a` and `b` are shared between the two object. Having a single data container for two seemingly different objects provides an extremely powerful way of manipulating sub-sets of numerical data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you want to work on a *copy* of your data, you can use the `.copy` method of the `ndarray`. The following snippet should drive the point home:" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T13:06:20.223171Z", + "start_time": "2020-10-17T13:06:20.206422Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "class: ndarray\n", + "shape: (3, 3)\n", + "strides: (3, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7ff737ea3220\n", + "type: uint8\n", + "\n", + "class: ndarray\n", + "shape: (3, 3)\n", + "strides: (3, 1)\n", + "itemsize: 1\n", + "data pointer: 0x7ff737ea3340\n", + "type: uint8\n", + "\n", + "a: array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "====================\n", + "b: array([[1, 1, 1],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a.copy()\n", + "\n", + "# get the address of the underlying data pointer\n", + "\n", + "np.ndinfo(a)\n", + "print()\n", + "np.ndinfo(b)\n", + "\n", + "# assign 1 to the first row of b, and do not touch a\n", + "b[0] = 1\n", + "\n", + "print()\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `.copy` method can also be applied to views: below, `a[0]` is a *view* of `a`, out of which we create a *deep copy* called `b`. This is a row vector now. We can then do whatever we want to with `b`, and that leaves `a` unchanged." + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": { + "ExecuteTime": { + "end_time": "2020-10-17T13:00:06.217232Z", + "start_time": "2020-10-17T13:00:06.207417Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "b: array([0, 0, 0], dtype=uint8)\n", + "====================\n", + "a: array([[0, 0, 0],\n", + "\t[0, 0, 0],\n", + "\t[0, 0, 0]], dtype=uint8)\n", + "====================\n", + "b: array([1, 0, 0], dtype=uint8)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "%%micropython -unix 1\n", + "\n", + "from ulab import numpy as np\n", + "\n", + "a = np.zeros((3, 3), dtype=np.uint8)\n", + "b = a[0].copy()\n", + "print('b: ', b)\n", + "print('='*20)\n", + "# assign 1 to the first entry of b, and do not touch a\n", + "b[0] = 1\n", + "print('a: ', a)\n", + "print('='*20)\n", + "print('b: ', b)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The fact that the underlying data of a view is the same as that of the original array has another important consequence, namely, that the creation of a view is cheap. Both in terms of RAM, and execution time. A view is really nothing but a short header with a data array that already exists, and is filled up. Hence, creating the view requires only the creation of its header. This operation is fast, and uses virtually no RAM." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": { + "height": "calc(100% - 180px)", + "left": "10px", + "top": "150px", + "width": "382.797px" + }, + "toc_section_display": true, + "toc_window_display": true + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/ulab-numerical.ipynb b/docs/ulab-numerical.ipynb index d3341f2..66e5b05 100644 --- a/docs/ulab-numerical.ipynb +++ b/docs/ulab-numerical.ipynb @@ -1105,7 +1105,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/ulab-poly.ipynb b/docs/ulab-poly.ipynb index 4251bb8..9cd7223 100644 --- a/docs/ulab-poly.ipynb +++ b/docs/ulab-poly.ipynb @@ -399,7 +399,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1, diff --git a/docs/ulab-programming.ipynb b/docs/ulab-programming.ipynb index 7b3a24c..249cdf6 100644 --- a/docs/ulab-programming.ipynb +++ b/docs/ulab-programming.ipynb @@ -35,12 +35,12 @@ "source": [ "# Programming ulab\n", "\n", - "Earlier we have seen, how `ulab`'s functions and methods can be accessed in `micropython`. This last section of the book explains, how these functions are implemented. By the end of this chapter, not only would you be able to extend `ulab`, and write your own `numpy`-compatible functions, but through a deeper understanding of the inner workings of the functions, you would be able to see what the trade-offs are at the `python` level.\n", + "Earlier we have seen, how `ulab`'s functions and methods can be accessed in `micropython`. This last section of the book explains, how these functions are implemented. By the end of this chapter, not only would you be able to extend `ulab`, and write your own `numpy`-compatible functions, but through a deeper understanding of the inner workings of the functions, you would also be able to see what the trade-offs are at the `python` level.\n", "\n", "\n", "## Code organisation\n", "\n", - "As mentioned earlier, the `python` functions are organised into sub-modules at the C level. Functions in module `x` always begin with the `x_` prefix, so it is relatively easy to navigate the code. Sub-modules are all in their respective folder. E.g., the `filter` sub-module is in `./ulab/code/filter/`, with two files, `./ulab/code/filter/filter.h`, and `./ulab/code/filter/filter.c`. `filter.c` contains two functions, `filter_convolve`, and `filter_sosfilt`, which are bound to the name space either in `ulab_filter_globals_table[]`, or, if `numpy`-compatibility is required, at the top level, in `ulab.c`." + "As mentioned earlier, the `python` functions are organised into sub-modules at the C level. The C sub-modules can be found in `./ulab/code/`." ] }, { @@ -56,14 +56,13 @@ "source": [ "### General comments\n", "\n", - "`ndarrays` are efficient containers of numerical data of the same type (i.e., signed/unsigned chars, signed/unsigned integers or `mp_float_t`s, which, depending on the platform, are either C `float`s, or C `double`s). Beyond storing the actual data in the void pointer `*array`, the type definition has eight additional members (on top of the `base` type). Namely, `dense`, which tells us, whether the array is dense or sparse (more on this later), the `dtype`, which tells us, how the bytes are to be interpreted. Moreover, the `itemsize`, which stores the size of a single entry in the array, `boolean`, an unsigned integer, which determines, whether the arrays is to be treated as a set of Booleans, or as numerical data, `ndim`, the number of dimensions (`uint8_t`), `len`, the length of the array, the shape (`*size_t`), the strides (`*int32_t`). The length is simply the product of the numbers in `shape`.\n", + "`ndarrays` are efficient containers of numerical data of the same type (i.e., signed/unsigned chars, signed/unsigned integers or `mp_float_t`s, which, depending on the platform, are either C `float`s, or C `double`s). Beyond storing the actual data in the void pointer `*array`, the type definition has eight additional members (on top of the `base` type). Namely, the `dtype`, which tells us, how the bytes are to be interpreted. Moreover, the `itemsize`, which stores the size of a single entry in the array, `boolean`, an unsigned integer, which determines, whether the arrays is to be treated as a set of Booleans, or as numerical data, `ndim`, the number of dimensions (`uint8_t`), `len`, the length of the array (the number of entries), the shape (`*size_t`), the strides (`*int32_t`). The length is simply the product of the numbers in `shape`.\n", "\n", "The type definition is as follows:\n", "\n", "```c\n", "typedef struct _ndarray_obj_t {\n", " mp_obj_base_t base;\n", - " uint8_t dense;\n", " uint8_t dtype;\n", " uint8_t itemsize;\n", " uint8_t boolean;\n", @@ -109,7 +108,7 @@ "\n", "### Iterating using the unwrapped loops\n", "\n", - "The following macro definition is taken from [vectorise.h](https://github.com/v923z/micropython-ulab/blob/master/code/vector/vectorise.h), and demonstrates, how we can iterate over a single array in four dimensions. \n", + "The following macro definition is taken from [vector.h](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/vector/vector.h), and demonstrates, how we can iterate over a single array in four dimensions. \n", "\n", "```c\n", "#define ITERATE_VECTOR(type, array, source, sarray) do {\n", @@ -273,7 +272,7 @@ "}\n", "```\n", "\n", - "A good example of how the function would be called can be found in [vectorise.c](https://github.com/v923z/micropython-ulab/blob/master/code/vector/vectorise.c), in the `vectorise_arctan2` function:\n", + "A good example of how the function would be called can be found in [vector.c](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/vector/vector.c), in the `vector_arctan2` function:\n", "\n", "```c\n", "mp_obj_t vectorise_arctan2(mp_obj_t y, mp_obj_t x) {\n", @@ -331,7 +330,7 @@ "}\n", "```\n", "\n", - "Once the reduced `strides` and `shape` are known, we place the axis in question in the innermost loop, and wrap it with the loops, whose coordinates are in the `strides`, and `shape` arrays. The `RUN_STD` macro from [numerical.h](https://github.com/v923z/micropython-ulab/blob/master/code/numerical/numerical.h) is a good example. The macro is expanded in the `numerical_sum_mean_std_ndarray` function. \n", + "Once the reduced `strides` and `shape` are known, we place the axis in question in the innermost loop, and wrap it with the loops, whose coordinates are in the `strides`, and `shape` arrays. The `RUN_STD` macro from [numerical.h](https://github.com/v923z/micropython-ulab/blob/master/code/numpy/numerical/numerical.h) is a good example. The macro is expanded in the `numerical_sum_mean_std_ndarray` function. \n", "\n", "\n", "```c\n", @@ -629,13 +628,13 @@ "constant has been set to 1. After compilation, you can call a particular `user` function in `python` by importing the module first, i.e., \n", "\n", "```python\n", - "import ulab\n", + "from ulab import numpy as np\n", "from ulab import user\n", "\n", "user.some_function(...)\n", "```\n", "\n", - "This separation of user-defined functions from the rest of the code ensures that the integrity of the main module and all its functions are always preserved. Even in case of a catastrophic failure, you can easily clone `ulab` anew, and start over.\n", + "This separation of user-defined functions from the rest of the code ensures that the integrity of the main module and all its functions are always preserved. Even in case of a catastrophic failure, you can exclude the `user` module, and start over.\n", "\n", "And now the function:\n", "\n", @@ -756,7 +755,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.6" + "version": "3.8.5" }, "toc": { "base_numbering": 1,