updated docs, removed circuitpython stuff, and fixed diff code

This commit is contained in:
Zoltán Vörös 2021-01-14 19:40:18 +01:00
parent 9f9e006955
commit 06bb8348a0
89 changed files with 8921 additions and 4907 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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_

View file

@ -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 <math.h>
#include "py/runtime.h"

View file

@ -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 );

View file

@ -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"""
//|

View file

@ -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_ */

View file

@ -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
*/

View file

@ -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;\

View file

@ -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);

View file

@ -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_

View file

@ -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 <math.h>

View file

@ -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_

View file

@ -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
*/

View file

@ -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_

View file

@ -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

View file

@ -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_

View file

@ -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 <math.h>
@ -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;
}

View file

@ -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

View file

@ -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
*/

View file

@ -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 {\

View file

@ -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
};

View file

@ -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
*
*/

View file

@ -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

View file

@ -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_

View file

@ -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 */

View file

@ -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_ */

View file

@ -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);

View file

@ -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
*
*/

View file

@ -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
};

View file

@ -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
*
*/

View file

@ -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
*/

View file

@ -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
*
*/

View file

@ -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) },

View file

@ -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
*
*/

View file

@ -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 <math.h>
@ -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

View file

@ -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

View file

@ -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
*/

View file

@ -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_

View file

@ -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

View file

@ -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_

View file

@ -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 <math.h>

View file

@ -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

View file

@ -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)

View file

@ -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 %}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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
-----------------------------

File diff suppressed because it is too large Load diff

View file

@ -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::

View file

@ -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)

View file

@ -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 Halleys) 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

View file

@ -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)

View file

@ -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)

View file

@ -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 Halleys) 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

View file

@ -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)

View file

@ -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)

View file

@ -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 <http://www.adafruit.com>`__.
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 <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
.. 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 <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.
`ulab.h <https://github.com/v923z/micropython-ulab/blob/master/code/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
doesnt 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 <https://github.com/v923z/micropython-ulab/blob/master/code/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

View file

@ -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, dont. 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 <module>
@ -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 <module>
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: <bound_method 7fdc008692c0 array([1, 2, 3], dtype=int8).<function>>
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 <module>
File "/dev/shm/micropython.py", line 5, in <module>
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()

View file

@ -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 rows 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)

View file

@ -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
fits 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

View file

@ -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 <https://github.com/v923z/micropython-ulab/blob/master/code/vector/vectorise.h>`__,
`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.
@ -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 <https://github.com/v923z/micropython-ulab/blob/master/code/vector/vectorise.c>`__,
in the ``vectorise_arctan2`` function:
`vector.c <https://github.com/v923z/micropython-ulab/blob/master/code/numpy/vector/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 <https://github.com/v923z/micropython-ulab/blob/master/code/numerical/numerical.h>`__
`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.
@ -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:

View file

@ -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.

View file

@ -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

View file

@ -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``.

View file

@ -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')``.

View file

@ -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 self<value.
.. method:: __le__(self, other: Union[array, _float])
Return self<=value.
.. method:: __gt__(self, other: Union[array, _float])
Return self>value.
.. 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.

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -1,11 +0,0 @@
:mod:`ulab.user`
================
.. py:module:: ulab.user
.. autoapi-nested-parse::
This module should hold arbitrary user-defined functions.

View file

@ -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.

View file

@ -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,

1600
docs/numpy-functions.ipynb Normal file

File diff suppressed because it is too large Load diff

View file

@ -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,

View file

@ -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,

515
docs/scipy-optimize.ipynb Normal file
View file

@ -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 Halleys) 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
}

View file

@ -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,

344
docs/scipy-special.ipynb Normal file
View file

@ -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
}

View file

@ -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,

View file

@ -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

View file

@ -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 <module>\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)."
]
},
{

View file

@ -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,

View file

@ -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": {

3412
docs/ulab-ndarray.ipynb Normal file

File diff suppressed because it is too large Load diff

View file

@ -1105,7 +1105,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.8.5"
},
"toc": {
"base_numbering": 1,

View file

@ -399,7 +399,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.6"
"version": "3.8.5"
},
"toc": {
"base_numbering": 1,

View file

@ -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,