Compare commits

...

80 commits

Author SHA1 Message Date
Jeff Epler
fbf98c16db
Merge pull request #11 from jepler/merge-upstream-4
Merge upstream 4
2020-02-27 14:09:18 -06:00
Jeff Epler
42d831e1e6 Merge remote-tracking branch 'upstream/master' into merge-upstream-4 2020-02-27 14:08:15 -06:00
Jeff Epler
66b89de8c7 Always include creation functions 2020-02-27 14:07:04 -06:00
Jeff Epler
844b85018b
Merge pull request #49 from jepler/gitignore
ignore files created by ./build.sh
2020-02-27 13:58:25 -06:00
Jeff Epler
1c1a693a2b
Merge pull request #48 from v923z/create
created new create sub-module for ndarray initialisation functions
2020-02-27 13:58:14 -06:00
Jeff Epler
f81e950513 ignore files created by ./build.sh 2020-02-27 13:56:09 -06:00
Jeff Epler
ffff7606c8 fix tests after 'eye' was moved 2020-02-27 13:53:29 -06:00
Zoltán Vörös
bee25781b9 added new source file... 2020-02-27 20:46:52 +01:00
Zoltán Vörös
47bf2ec9a7 created new create sub-module for ndarray initialisation functions 2020-02-27 20:39:13 +01:00
Jeff Epler
6860448096
Merge pull request #10 from jepler/merge-upstream-3
Merge from upstream
2020-02-27 10:16:25 -06:00
Jeff Epler
6b9ea24b2e remove unused and unneeded file 2020-02-27 10:14:53 -06:00
Jeff Epler
a5ac422f33 Merge remote-tracking branch 'upstream/master' into merge-upstream-3 2020-02-27 10:14:40 -06:00
Jeff Epler
badeee48df
Merge pull request #47 from jepler/local-build-script
a script to build and run tests locally
2020-02-27 10:12:03 -06:00
Jeff Epler
e370e56a15 a script to build and run tests locally 2020-02-27 10:11:34 -06:00
Jeff Epler
db5f1f85bb
Merge pull request #46 from jepler/ndarray-properties-sort
Ndarray properties sort
2020-02-27 10:11:17 -06:00
Jeff Epler
2bddc94df5
Merge pull request #45 from jepler/move-ones-zeros
Move zeros(), ones() to base ulab module
2020-02-27 10:08:29 -06:00
Jeff Epler
aa5ef4afb9 Enable sort method in circuitpython 2020-02-27 10:06:33 -06:00
Jeff Epler
d99d834d87 Enable properties in circuitpython
I verified that these work for us as coded.
2020-02-27 10:06:27 -06:00
Jeff Epler
83479f115b Move zeros(), ones() to base ulab module 2020-02-27 10:05:50 -06:00
924dc7012a
Merge pull request #44 from jepler/circuitpy-fixes
Circuitpy fixes
2020-02-27 09:29:43 -06:00
Jeff Epler
daaacac16f Remove CIRCUITPY special cases 2020-02-27 08:56:07 -06:00
Jeff Epler
aa4d53e292 Use circuitpy-compat for none 2020-02-27 08:56:04 -06:00
3febd79aa0
Merge pull request #41 from v923z/2dim
Split ulab into multiple modules
2020-02-26 11:29:17 -06:00
Zoltán Vörös
7e2be88dff Merge branch '2dim' of github.com:v923z/micropython-ulab into 2dim
added circuitpython-related stuff to code and manual
2020-02-26 18:06:19 +01:00
Zoltán Vörös
e0e840f6d5 added circuitpython-related stuff to the manual 2020-02-26 18:05:49 +01:00
102ba5032e add missing expected-file 2020-02-18 21:40:31 -06:00
b83ed3e2ca add more tests 2020-02-18 21:36:49 -06:00
1e5ebe739d numerical: add __name__ 2020-02-18 21:27:05 -06:00
70af1c8a77 fix test 2020-02-18 21:25:03 -06:00
a2962f0fe5 fix printing failure info when tests fail 2020-02-18 21:10:33 -06:00
cf057c4df9 roll back constness corrections 2020-02-18 21:00:30 -06:00
de6b7772e4 fix ci build 2020-02-18 20:50:01 -06:00
43551c9a7a Merge remote-tracking branch 'upstream/master' into 2dim 2020-02-18 20:46:43 -06:00
23adf6e4a7 Mark modules as 'extern const' 2020-02-18 20:32:33 -06:00
f6d123beb8 Don't export modules if CIRCUITPY
.. We are using the "shared bindings" method of circuitpython
2020-02-18 20:32:28 -06:00
27996c9003 Add MP_DELCARE_CONST_FUN declarations to headers 2020-02-18 20:32:22 -06:00
Zoltán Vörös
bec24fe4a4 Merge branch 'master' of github.com:v923z/micropython-ulab 2020-02-17 19:58:27 +01:00
Zoltán Vörös
e71f667114 temporary fix for issue #40 2020-02-17 19:57:55 +01:00
Zoltán Vörös
8300de7f11 backup commit, absolutely nothing essential 2020-02-16 19:53:30 +01:00
Zoltán Vörös
017c1c2c46 added extras.h/extras.c 2020-02-16 19:49:28 +01:00
Zoltán Vörös
6fe015f134 properties are now defined in ndarray_properties.h 2020-02-16 19:49:01 +01:00
Zoltán Vörös
3727c38182
Merge pull request #39 from jepler/github-actions
Perform continuous integration using github actions
2020-02-16 16:22:14 +01:00
a75903efe5 Add github actions
This will build micropython's master branch with ulab support, and
then run the tests in tests/

At this time, there's only one test and it's not very useful.
2020-02-15 19:57:50 -06:00
Zoltán Vörös
d8bfe46bd8 added __name__ to all submodules 2020-02-14 19:51:28 +01:00
Zoltán Vörös
2e3a0b4483 separated sub-modules into proper python sub-modules 2020-02-13 21:49:09 +01:00
Jeff Epler
cf0180e05e
Merge pull request #9 from jepler/merge-upstream-2
Merge upstream
2020-02-13 13:31:11 -06:00
4e5947afe1 Merge remote-tracking branch 'upstream/master' 2020-02-13 13:27:54 -06:00
Zoltán Vörös
49db707a9f
Merge pull request #37 from jepler/portability
Make portable to CircuitPython within same codebase
2020-02-12 18:27:16 +01:00
Zoltán Vörös
f2aaab84cc
Merge pull request #36 from jepler/convolve-optimize
filter_convolve: fix build error
2020-02-12 18:24:26 +01:00
Jeff Epler
fc80a25685 Increase CircuitPython compatibility
- Adapt to signature of mp_make_new_fun_t for mpy and cpy by ifdef
 - Use MP_OBJ_IS_TYPE instead of mp_obj_is_type
 - Ditto MP_OBJ_IS_INT
 - Use mp_const_none instead of MP_ROM_NONE
 - Ditto mp_const_true, mp_const_false
2020-02-12 10:15:47 -06:00
Jeff Epler
f47abf90ac filter_convolve: fix build error
My earlier change introduced a build error.  I'm not sure why
I didn't find this before making the pull request.
2020-02-12 09:50:17 -06:00
Zoltán Vörös
5bf6e89a0a
Merge pull request #35 from jepler/convolve-optimize
convolve: Optimize and special-case floats
2020-02-12 08:16:18 +01:00
Jeff Epler
7846b0c469 convolve: Optimize and special-case floats
Special casing floats decreases runtime to about 50% (applying a 117-tap
filter to 512 points of data goes from 70ms to 32ms)

The top_n/bot_n calculations already meant that the a/c indices were
never out of range.  This decreases runtime further to about 15% of
original (11ms)

Timings done on an Adafruit Clue (nrf52840 at 64MHz)

It does of course increase code size somewhat.
2020-02-11 17:57:54 -06:00
Zoltán Vörös
9153fd8f8a added a short section to the manual on how to customise ulab 2020-02-11 21:36:12 +01:00
Zoltán Vörös
57cf52838c
Merge pull request #33 from jepler/fix-undef-errors-mpy
Fix some define-guards
2020-02-11 20:19:27 +01:00
Zoltán Vörös
c14eee1bd4 trying to fix ulab.h definitions 2020-02-11 20:15:45 +01:00
Zoltán Vörös
2c71467ced implemented ndarray properties 2020-02-11 20:08:37 +01:00
02d74a4d3e Fix some define-guards
These problems were found building in circuitpython:
../../extmod/ulab/code/numerical.c:671:5: error: "ULAB_NUMERICAL_ARGSORT" is not defined, evaluates to 0 [-Werror=undef]
  671 | #if ULAB_NUMERICAL_ARGSORT
      |     ^~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
../../extmod/ulab/code/ulab.c:150:9: error: "ULAB_VECTORISE_" is not defined, evaluates to 0 [-Werror=undef]
  150 |     #if ULAB_VECTORISE_
      |         ^~~~~~~~~~~~~~~
../../extmod/ulab/code/ulab.c:159:9: error: "ULAB_VECTORISE_TAHN" is not defined, evaluates to 0 [-Werror=undef]
  159 |     #if ULAB_VECTORISE_TAHN
      |         ^~~~~~~~~~~~~~~~~~~
../../extmod/ulab/code/ulab.c:198:9: error: "ULAB_NUMERICAL_ARGSORT" is not defined, evaluates to 0 [-Werror=undef]
  198 |     #if ULAB_NUMERICAL_ARGSORT
      |         ^~~~~~~~~~~~~~~~~~~~~~
2020-02-11 11:08:25 -06:00
Jeff Epler
72025550d1
Merge pull request #8 from jepler/fix-undef-errors
Fix some define-guards
2020-02-11 10:44:05 -06:00
1beddec7bb Fix some define-guards
These problems were found building in circuitpython:
../../extmod/ulab/code/numerical.c:671:5: error: "ULAB_NUMERICAL_ARGSORT" is not defined, evaluates to 0 [-Werror=undef]
  671 | #if ULAB_NUMERICAL_ARGSORT
      |     ^~~~~~~~~~~~~~~~~~~~~~
cc1: all warnings being treated as errors
../../extmod/ulab/code/ulab.c:150:9: error: "ULAB_VECTORISE_" is not defined, evaluates to 0 [-Werror=undef]
  150 |     #if ULAB_VECTORISE_
      |         ^~~~~~~~~~~~~~~
../../extmod/ulab/code/ulab.c:159:9: error: "ULAB_VECTORISE_TAHN" is not defined, evaluates to 0 [-Werror=undef]
  159 |     #if ULAB_VECTORISE_TAHN
      |         ^~~~~~~~~~~~~~~~~~~
../../extmod/ulab/code/ulab.c:198:9: error: "ULAB_NUMERICAL_ARGSORT" is not defined, evaluates to 0 [-Werror=undef]
  198 |     #if ULAB_NUMERICAL_ARGSORT
      |         ^~~~~~~~~~~~~~~~~~~~~~
2020-02-11 10:42:58 -06:00
5e81f75661 Trim README to just the basics 2020-02-11 10:08:47 -06:00
Jeff Epler
21b5a130c8 Port to CircuitPython 2020-02-11 10:08:47 -06:00
Zoltán Vörös
4a0677fd14 removed extra ndarray_get_buffer 2020-02-10 19:54:49 +01:00
Zoltán Vörös
800bb3b872 Merge branch 'master' of github.com:v923z/micropython-ulab 2020-02-10 19:51:22 +01:00
Zoltán Vörös
89170a13a6 fixed error in filter.c, removed asbytearray, and added buffer protocol to ndarray.c 2020-02-10 19:50:49 +01:00
Zoltán Vörös
ca23263655
Merge pull request #31 from jepler/memoryview
ndarray: let memoryview(arr) work
2020-02-10 18:38:11 +01:00
Zoltán Vörös
936bb3bae5 corrected slicing error in issue #32 2020-02-09 19:56:32 +01:00
a90d18caf1 ndarray: let memoryview(arr) work
.. this makes ndarray.rawbytes redundant.  Circuitpython will remove it.
2020-02-08 10:29:45 -06:00
Zoltán Vörös
918075daaf fixed a couple of small issues in filter.c 2020-02-08 10:45:13 +01:00
Zoltán Vörös
fac3eb4099 fixed a couple of small issues in filter.c 2020-02-08 10:44:24 +01:00
Zoltán Vörös
76ccd1a118 the master branch is configurable now 2020-02-07 21:23:24 +01:00
Zoltán Vörös
0315408e05
Merge pull request #29 from jepler/convolve
convolve: implement and document
2020-02-06 19:53:51 +01:00
Jeff Epler
722401c2e4 Clean up and polish based on review comments
no functional change
2020-02-06 12:43:39 -06:00
Jeff Epler
efa4c148c9 convolve: implement and document 2020-02-05 16:33:48 -06:00
Zoltán Vörös
dcd65feb21 fixed indexing error in linalg.dot 2020-01-29 19:27:28 +01:00
Zoltán Vörös
b2c4749962 replaced MP_ROM_PTR macro, wherever it made sense 2020-01-20 19:55:38 +01:00
Zoltán Vörös
941ffceb1b replaced MP_ROM_PTR macro, wherever it made sense 2020-01-20 19:49:57 +01:00
Zoltán Vörös
ac1111c251 fixed glitch in std/mean functions 2020-01-07 21:53:23 +01:00
Zoltán Vörös
992e48b84e switched to version string 2020-01-07 21:02:20 +01:00
Zoltán Vörös
68e38bc6b8 backup commit 2020-01-06 07:43:27 +01:00
37 changed files with 2307 additions and 9702 deletions

58
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,58 @@
name: Build CI
on:
push:
pull_request:
release:
types: [published]
check_suite:
types: [rerequested]
jobs:
test:
runs-on: ubuntu-16.04
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Set up Python 3.5
uses: actions/setup-python@v1
with:
python-version: 3.5
- name: Versions
run: |
gcc --version
python3 --version
- name: Checkout ulab
uses: actions/checkout@v1
- name: Checkout micropython repo
uses: actions/checkout@v2
with:
repository: micropython/micropython
path: micropython
- name: Checkout micropython submodules
run: (cd micropython && git submodule update --init)
- name: Build mpy-cross
run: make -C micropython/mpy-cross -j2
- name: Build micropython unix port
run: |
make -C micropython/ports/unix -j2 deplibs
make -C micropython/ports/unix -j2 USER_C_MODULES=$(readlink -f .)
- name: Run tests
run: env MICROPYTHON_CPYTHON3=python3.5 MICROPY_MICROPYTHON=micropython/ports/unix/micropython micropython/tests/run-tests -d tests
- name: Print failure info
run: |
for exp in *.exp;
do testbase=$(basename $exp .exp);
echo -e "\nFAILURE $testbase";
diff -u $testbase.exp $testbase.out;
done
if: failure()

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/micropython
/*.exp
/*.out

View file

@ -1,62 +1,12 @@
# micropython-ulab
# circuitpython-ulab
ulab is a numpy-like array manipulation library for micropython.
The module is written in C, defines compact containers for numerical
data, and is fast.
data, and is fast.
Documentation can be found under https://micropython-ulab.readthedocs.io/en/latest/
The source for the manual is in https://github.com/v923z/micropython-ulab/blob/master/docs/ulab-manual.ipynb,
while developer help is in https://github.com/v923z/micropython-ulab/blob/master/docs/ulab.ipynb.
ulab will be incorporated in builds of most CircuitPython supported
devices, so there's usually no need to use the files here directly.
If you've encountered a problem with circuitpython-ulab, please
file an issue [in the circuitpython issue tracker](https://github.com/adafruit/circuitpython).
# Firmware
Firmware for pyboard.v.1.1, and PYBD_SF6 is updated once in a while, and can be downloaded
from https://github.com/v923z/micropython-ulab/releases.
## Compiling
If you want to try the latest version of `ulab`, or your hardware is
different to pyboard.v.1.1, or PYBD_SF6, the firmware can be compiled
from the source by following these steps:
First, you have to clone the micropython repository by running
```
git clone https://github.com/micropython/micropython.git
```
on the command line. This will create a new repository with the name `micropython`. Staying there, clone the `ulab` repository with
```
git clone https://github.com/v923z/micropython-ulab.git ulab
```
Then you have to include `ulab` in the compilation process by editing `mpconfigport.h` of the directory of the port for which you want to compile, so, still on the command line, navigate to `micropython/ports/unix`, or `micropython/ports/stm32`, or whichever port is your favourite, and edit the `mpconfigport.h` file there. All you have to do is add a single line at the end:
```
#define MODULE_ULAB_ENABLED (1)
```
This line will inform the compiler that you want `ulab` in the resulting firmware. If you don't have the cross-compiler installed, your might want to do that now, for instance on Linux by executing
```
sudo apt-get install gcc-arm-none-eabi
```
If that was successful, you can try to run the make command in the port's directory as
```
make BOARD=PYBV11 USER_C_MODULES=../../../ulab all
```
which will prepare the firmware for pyboard.v.11. Similarly,
```
make BOARD=PYBD_SF6 USER_C_MODULES=../../../ulab all
```
will compile for the SF6 member of the PYBD series. Provided that you managed to compile the firmware, you would upload that by running
either
```
dfu-util --alt 0 -D firmware.dfu
```
or
```
python pydfu.py -u firmware.dfu
```
In case you got stuck somewhere in the process, a bit more detailed instructions can be found under https://github.com/micropython/micropython/wiki/Getting-Started, and https://github.com/micropython/micropython/wiki/Pyboard-Firmware-Update.
circuitpython-ulab is based on [micropython-ulab](https://github.com/v923z/micropython-ulab).

17
build.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/sh
set -e
HERE="$(dirname -- "$(readlink -f -- "${0}")" )"
[ -e micropython/py/py.mk ] || git clone https://github.com/micropython/micropython
[ -e micropython/lib/libffi/autogen.sh ] || (cd micropython && git submodule update --init lib/libffi )
#git clone https://github.com/micropython/micropython
make -C micropython/mpy-cross -j$(nproc)
make -C micropython/ports/unix -j$(nproc) deplibs
make -C micropython/ports/unix -j$(nproc) USER_C_MODULES="${HERE}"
if ! env MICROPY_MICROPYTHON=micropython/ports/unix/micropython micropython/tests/run-tests -d tests; then
for exp in *.exp; do
testbase=$(basename $exp .exp);
echo -e "\nFAILURE $testbase";
diff -u $testbase.exp $testbase.out;
done
fi

155
code/create.c Normal file
View file

@ -0,0 +1,155 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
* 2019-2020 Zoltán Vörös
*/
#include "py/obj.h"
#include "py/runtime.h"
#include "create.h"
mp_obj_t create_zeros_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t kind) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} } ,
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
uint8_t dtype = args[1].u_int;
if(!MP_OBJ_IS_INT(args[0].u_obj) && !MP_OBJ_IS_TYPE(args[0].u_obj, &mp_type_tuple)) {
mp_raise_TypeError(translate("input argument must be an integer or a 2-tuple"));
}
ndarray_obj_t *ndarray = NULL;
if(MP_OBJ_IS_INT(args[0].u_obj)) {
size_t n = mp_obj_get_int(args[0].u_obj);
ndarray = create_new_ndarray(1, n, dtype);
} else if(MP_OBJ_IS_TYPE(args[0].u_obj, &mp_type_tuple)) {
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(args[0].u_obj);
if(tuple->len != 2) {
mp_raise_TypeError(translate("input argument must be an integer or a 2-tuple"));
}
ndarray = create_new_ndarray(mp_obj_get_int(tuple->items[0]),
mp_obj_get_int(tuple->items[1]), dtype);
}
if(kind == 1) {
mp_obj_t one = mp_obj_new_int(1);
for(size_t i=0; i < ndarray->array->len; i++) {
mp_binary_set_val_array(dtype, ndarray->array->items, i, one);
}
}
return MP_OBJ_FROM_PTR(ndarray);
}
mp_obj_t create_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return create_zeros_ones(n_args, pos_args, kw_args, 0);
}
MP_DEFINE_CONST_FUN_OBJ_KW(create_zeros_obj, 0, create_zeros);
mp_obj_t create_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return create_zeros_ones(n_args, pos_args, kw_args, 1);
}
MP_DEFINE_CONST_FUN_OBJ_KW(create_ones_obj, 0, create_ones);
mp_obj_t create_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_M, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
size_t n = args[0].u_int, m;
int16_t k = args[2].u_int;
uint8_t dtype = args[3].u_int;
if(args[1].u_rom_obj == mp_const_none) {
m = n;
} else {
m = mp_obj_get_int(args[1].u_rom_obj);
}
ndarray_obj_t *ndarray = create_new_ndarray(m, n, dtype);
mp_obj_t one = mp_obj_new_int(1);
size_t i = 0;
if((k >= 0) && (k < n)) {
while(k < n) {
mp_binary_set_val_array(dtype, ndarray->array->items, i*n+k, one);
k++;
i++;
}
} else if((k < 0) && (-k < m)) {
k = -k;
i = 0;
while(k < m) {
mp_binary_set_val_array(dtype, ndarray->array->items, k*n+i, one);
k++;
i++;
}
}
return MP_OBJ_FROM_PTR(ndarray);
}
MP_DEFINE_CONST_FUN_OBJ_KW(create_eye_obj, 0, create_eye);
mp_obj_t create_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_num, MP_ARG_INT, {.u_int = 50} },
{ MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_true} },
{ MP_QSTR_retstep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_false} },
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(2, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
uint16_t len = args[2].u_int;
if(len < 2) {
mp_raise_ValueError(translate("number of points must be at least 2"));
}
mp_float_t value, step;
value = mp_obj_get_float(args[0].u_obj);
uint8_t typecode = args[5].u_int;
if(args[3].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj)-value)/(len-1);
else step = (mp_obj_get_float(args[1].u_obj)-value)/len;
ndarray_obj_t *ndarray = create_new_ndarray(1, len, typecode);
if(typecode == NDARRAY_UINT8) {
uint8_t *array = (uint8_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (uint8_t)value;
} else if(typecode == NDARRAY_INT8) {
int8_t *array = (int8_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (int8_t)value;
} else if(typecode == NDARRAY_UINT16) {
uint16_t *array = (uint16_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (uint16_t)value;
} else if(typecode == NDARRAY_INT16) {
int16_t *array = (int16_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (int16_t)value;
} else {
mp_float_t *array = (mp_float_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = value;
}
if(args[4].u_obj == mp_const_false) {
return MP_OBJ_FROM_PTR(ndarray);
} else {
mp_obj_t tuple[2];
tuple[0] = ndarray;
tuple[1] = mp_obj_new_float(step);
return mp_obj_new_tuple(2, tuple);
}
}
MP_DEFINE_CONST_FUN_OBJ_KW(create_linspace_obj, 2, create_linspace);

29
code/create.h Normal file
View file

@ -0,0 +1,29 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
* 2019-2020 Zoltán Vörös
*/
#ifndef _CREATE_
#define _CREATE_
#include "ulab.h"
#include "ndarray.h"
/*
mp_obj_t create_zeros(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t create_ones(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t create_eye(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t create_linspace(size_t , const mp_obj_t *, mp_map_t *);
*/
MP_DECLARE_CONST_FUN_OBJ_KW(create_ones_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(create_zeros_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(create_eye_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(create_linspace_obj);
#endif

33
code/extras.c Normal file
View file

@ -0,0 +1,33 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Zoltán Vörös
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "py/misc.h"
#include "extras.h"
#if ULAB_EXTRAS_MODULE
STATIC const mp_rom_map_elem_t ulab_filter_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_extras) },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_extras_globals, ulab_extras_globals_table);
mp_obj_module_t ulab_filter_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_extras_globals,
};
#endif

23
code/extras.h Normal file
View file

@ -0,0 +1,23 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Zoltán Vörös
*/
#ifndef _EXTRA_
#define _EXTRA_
#include "ulab.h"
#include "ndarray.h"
#if ULAB_EXTRAS_MODULE
mp_obj_module_t ulab_extras_module;
#endif
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,20 +6,23 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "py/runtime.h"
#include "py/builtin.h"
#include "py/binary.h"
#include "py/obj.h"
#include "py/objarray.h"
#include "ndarray.h"
#include "fft.h"
#if ULAB_FFT_MODULE
enum FFT_TYPE {
FFT_FFT,
FFT_IFFT,
@ -76,18 +80,18 @@ void fft_kernel(mp_float_t *real, mp_float_t *imag, int n, int isign) {
mp_obj_t fft_fft_ifft_spectrum(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im, uint8_t type) {
if(!MP_OBJ_IS_TYPE(arg_re, &ulab_ndarray_type)) {
mp_raise_NotImplementedError("FFT is defined for ndarrays only");
mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only"));
}
if(n_args == 2) {
if(!MP_OBJ_IS_TYPE(arg_im, &ulab_ndarray_type)) {
mp_raise_NotImplementedError("FFT is defined for ndarrays only");
mp_raise_NotImplementedError(translate("FFT is defined for ndarrays only"));
}
}
// Check if input is of length of power of 2
ndarray_obj_t *re = MP_OBJ_TO_PTR(arg_re);
uint16_t len = re->array->len;
if((len & (len-1)) != 0) {
mp_raise_ValueError("input array length must be power of 2");
mp_raise_ValueError(translate("input array length must be power of 2"));
}
ndarray_obj_t *out_re = create_new_ndarray(1, len, NDARRAY_FLOAT);
@ -99,8 +103,9 @@ mp_obj_t fft_fft_ifft_spectrum(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im,
memcpy((mp_float_t *)out_re->array->items, (mp_float_t *)re->array->items, re->bytes);
} else {
for(size_t i=0; i < len; i++) {
data_re[i] = ndarray_get_float_value(re->array->items, re->array->typecode, i);
*data_re++ = ndarray_get_float_value(re->array->items, re->array->typecode, i);
}
data_re -= len;
}
ndarray_obj_t *out_im = create_new_ndarray(1, len, NDARRAY_FLOAT);
mp_float_t *data_im = (mp_float_t *)out_im->array->items;
@ -108,29 +113,33 @@ mp_obj_t fft_fft_ifft_spectrum(size_t n_args, mp_obj_t arg_re, mp_obj_t arg_im,
if(n_args == 2) {
ndarray_obj_t *im = MP_OBJ_TO_PTR(arg_im);
if (re->array->len != im->array->len) {
mp_raise_ValueError("real and imaginary parts must be of equal length");
mp_raise_ValueError(translate("real and imaginary parts must be of equal length"));
}
if(im->array->typecode == NDARRAY_FLOAT) {
memcpy((mp_float_t *)out_im->array->items, (mp_float_t *)im->array->items, im->bytes);
} else {
for(size_t i=0; i < len; i++) {
data_im[i] = ndarray_get_float_value(im->array->items, im->array->typecode, i);
*data_im++ = ndarray_get_float_value(im->array->items, im->array->typecode, i);
}
data_im -= len;
}
}
if((type == FFT_FFT) || (type == FFT_SPECTRUM)) {
fft_kernel(data_re, data_im, len, 1);
if(type == FFT_SPECTRUM) {
for(size_t i=0; i < len; i++) {
data_re[i] = MICROPY_FLOAT_C_FUN(sqrt)(data_re[i]*data_re[i] + data_im[i]*data_im[i]);
*data_re = MICROPY_FLOAT_C_FUN(sqrt)(*data_re * *data_re + *data_im * *data_im);
data_re++;
data_im++;
}
}
} else { // inverse transform
fft_kernel(data_re, data_im, len, -1);
// TODO: numpy accepts the norm keyword argument
for(size_t i=0; i < len; i++) {
data_re[i] /= len;
data_im[i] /= len;
*data_re++ /= len;
*data_im++ /= len;
}
}
if(type == FFT_SPECTRUM) {
@ -151,6 +160,8 @@ mp_obj_t fft_fft(size_t n_args, const mp_obj_t *args) {
}
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft);
mp_obj_t fft_ifft(size_t n_args, const mp_obj_t *args) {
if(n_args == 2) {
return fft_fft_ifft_spectrum(n_args, args[0], args[1], FFT_IFFT);
@ -159,6 +170,8 @@ 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);
mp_obj_t fft_spectrum(size_t n_args, const mp_obj_t *args) {
if(n_args == 2) {
return fft_fft_ifft_spectrum(n_args, args[0], args[1], FFT_SPECTRUM);
@ -166,3 +179,21 @@ mp_obj_t fft_spectrum(size_t n_args, const mp_obj_t *args) {
return fft_fft_ifft_spectrum(n_args, args[0], mp_const_none, FFT_SPECTRUM);
}
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_spectrum_obj, 1, 2, fft_spectrum);
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_spectrum), (mp_obj_t)&fft_spectrum_obj },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_fft_globals, ulab_fft_globals_table);
mp_obj_module_t ulab_fft_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_fft_globals,
};
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,11 +6,12 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _FFT_
#define _FFT_
#include "ulab.h"
#ifndef MP_PI
#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846)
@ -17,7 +19,13 @@
#define SWAP(t, a, b) { t tmp = a; a = b; b = tmp; }
mp_obj_t fft_fft(size_t , const mp_obj_t *);
mp_obj_t fft_ifft(size_t , const mp_obj_t *);
mp_obj_t fft_spectrum(size_t , const mp_obj_t *);
#if ULAB_FFT_MODULE
extern mp_obj_module_t ulab_fft_module;
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj);
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj);
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(fft_spectrum_obj);
#endif
#endif

99
code/filter.c Normal file
View file

@ -0,0 +1,99 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "py/obj.h"
#include "py/runtime.h"
#include "py/misc.h"
#include "filter.h"
#if ULAB_FILTER_MODULE
mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_a, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_v, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(2, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if(!MP_OBJ_IS_TYPE(args[0].u_obj, &ulab_ndarray_type) || !MP_OBJ_IS_TYPE(args[1].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("convolve arguments must be ndarrays"));
}
ndarray_obj_t *a = MP_OBJ_TO_PTR(args[0].u_obj);
ndarray_obj_t *c = MP_OBJ_TO_PTR(args[1].u_obj);
int len_a = a->array->len;
int len_c = c->array->len;
// deal with linear arrays only
if(a->m*a->n != len_a || c->m*c->n != len_c) {
mp_raise_TypeError(translate("convolve arguments must be linear arrays"));
}
if(len_a == 0 || len_c == 0) {
mp_raise_TypeError(translate("convolve arguments must not be empty"));
}
int len = len_a + len_c - 1; // convolve mode "full"
ndarray_obj_t *out = create_new_ndarray(1, len, NDARRAY_FLOAT);
mp_float_t *outptr = out->array->items;
int off = len_c-1;
if(a->array->typecode == NDARRAY_FLOAT && c->array->typecode == NDARRAY_FLOAT) {
mp_float_t* a_items = (mp_float_t*)a->array->items;
mp_float_t* c_items = (mp_float_t*)c->array->items;
for(int k=-off; k<len-off; k++) {
mp_float_t accum = (mp_float_t)0;
int top_n = MIN(len_c, len_a - k);
int bot_n = MAX(-k, 0);
mp_float_t* a_ptr = a_items + bot_n + k;
mp_float_t* a_end = a_ptr + (top_n - bot_n);
mp_float_t* c_ptr = c_items + len_c - bot_n - 1;
for(; a_ptr != a_end;) {
accum += *a_ptr++ * *c_ptr--;
}
*outptr++ = accum;
}
} else {
for(int k=-off; k<len-off; k++) {
mp_float_t accum = (mp_float_t)0;
int top_n = MIN(len_c, len_a - k);
int bot_n = MAX(-k, 0);
for(int n=bot_n; n<top_n; n++) {
int idx_c = len_c - n - 1;
int idx_a = n+k;
mp_float_t ai = ndarray_get_float_value(a->array->items, a->array->typecode, idx_a);
mp_float_t ci = ndarray_get_float_value(c->array->items, c->array->typecode, idx_c);
accum += ai * ci;
}
*outptr++ = accum;
}
}
return out;
}
MP_DEFINE_CONST_FUN_OBJ_KW(filter_convolve_obj, 2, filter_convolve);
STATIC const mp_rom_map_elem_t ulab_filter_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_filter) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_convolve), (mp_obj_t)&filter_convolve_obj },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_filter_globals, ulab_filter_globals_table);
mp_obj_module_t ulab_filter_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_filter_globals,
};
#endif

25
code/filter.h Normal file
View file

@ -0,0 +1,25 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
*/
#ifndef _FILTER_
#define _FILTER_
#include "ulab.h"
#include "ndarray.h"
#if ULAB_FILTER_MODULE
extern mp_obj_module_t ulab_filter_module;
MP_DECLARE_CONST_FUN_OBJ_KW(filter_convolve_obj);
#endif
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
@ -16,76 +17,24 @@
#include "py/misc.h"
#include "linalg.h"
mp_obj_t linalg_transpose(mp_obj_t self_in) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
// the size of a single item in the array
uint8_t _sizeof = mp_binary_get_size('@', self->array->typecode, NULL);
// NOTE:
// if the matrices are square, we can simply swap items, but
// generic matrices can't be transposed in place, so we have to
// declare a temporary variable
// NOTE:
// In the old matrix, the coordinate (m, n) is m*self->n + n
// We have to assign this to the coordinate (n, m) in the new
// matrix, i.e., to n*self->m + m (since the new matrix has self->m columns)
// one-dimensional arrays can be transposed by simply swapping the dimensions
if((self->m != 1) && (self->n != 1)) {
uint8_t *c = (uint8_t *)self->array->items;
// self->bytes is the size of the bytearray, irrespective of the typecode
uint8_t *tmp = m_new(uint8_t, self->bytes);
for(size_t m=0; m < self->m; m++) {
for(size_t n=0; n < self->n; n++) {
memcpy(tmp+_sizeof*(n*self->m + m), c+_sizeof*(m*self->n + n), _sizeof);
}
}
memcpy(self->array->items, tmp, self->bytes);
m_del(uint8_t, tmp, self->bytes);
}
SWAP(size_t, self->m, self->n);
return mp_const_none;
}
mp_obj_t linalg_reshape(mp_obj_t self_in, mp_obj_t shape) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
if(!MP_OBJ_IS_TYPE(shape, &mp_type_tuple) || (MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(shape)) != 2)) {
mp_raise_ValueError("shape must be a 2-tuple");
}
mp_obj_iter_buf_t iter_buf;
mp_obj_t item, iterable = mp_getiter(shape, &iter_buf);
uint16_t m, n;
item = mp_iternext(iterable);
m = mp_obj_get_int(item);
item = mp_iternext(iterable);
n = mp_obj_get_int(item);
if(m*n != self->m*self->n) {
// TODO: the proper error message would be "cannot reshape array of size %d into shape (%d, %d)"
mp_raise_ValueError("cannot reshape array (incompatible input/output shape)");
}
self->m = m;
self->n = n;
return MP_OBJ_FROM_PTR(self);
}
#if ULAB_LINALG_MODULE
mp_obj_t linalg_size(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(1, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError("size is defined for ndarrays only");
if(!MP_OBJ_IS_TYPE(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("size is defined for ndarrays only"));
} else {
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj);
if(args[1].u_obj == mp_const_none) {
return mp_obj_new_int(ndarray->array->len);
} else if(mp_obj_is_int(args[1].u_obj)) {
} else if(MP_OBJ_IS_INT(args[1].u_obj)) {
uint8_t ax = mp_obj_get_int(args[1].u_obj);
if(ax == 0) {
if(ndarray->m == 1) {
@ -95,19 +44,21 @@ mp_obj_t linalg_size(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
}
} else if(ax == 1) {
if(ndarray->m == 1) {
mp_raise_ValueError("tuple index out of range");
mp_raise_ValueError(translate("tuple index out of range"));
} else {
return mp_obj_new_int(ndarray->n);
}
} else {
mp_raise_ValueError("tuple index out of range");
mp_raise_ValueError(translate("tuple index out of range"));
}
} else {
mp_raise_TypeError("wrong argument type");
mp_raise_TypeError(translate("wrong argument type"));
}
}
}
MP_DEFINE_CONST_FUN_OBJ_KW(linalg_size_obj, 1, linalg_size);
bool linalg_invert_matrix(mp_float_t *data, size_t N) {
// returns true, of the inversion was successful,
// false, if the matrix is singular
@ -153,14 +104,14 @@ bool linalg_invert_matrix(mp_float_t *data, size_t N) {
mp_obj_t linalg_inv(mp_obj_t o_in) {
// since inv is not a class method, we have to inspect the input argument first
if(!MP_OBJ_IS_TYPE(o_in, &ulab_ndarray_type)) {
mp_raise_TypeError("only ndarrays can be inverted");
mp_raise_TypeError(translate("only ndarrays can be inverted"));
}
ndarray_obj_t *o = MP_OBJ_TO_PTR(o_in);
if(!MP_OBJ_IS_TYPE(o_in, &ulab_ndarray_type)) {
mp_raise_TypeError("only ndarray objects can be inverted");
mp_raise_TypeError(translate("only ndarray objects can be inverted"));
}
if(o->m != o->n) {
mp_raise_ValueError("only square matrices can be inverted");
mp_raise_ValueError(translate("only square matrices can be inverted"));
}
ndarray_obj_t *inverted = create_new_ndarray(o->m, o->n, NDARRAY_FLOAT);
mp_float_t *data = (mp_float_t *)inverted->array->items;
@ -178,17 +129,22 @@ mp_obj_t linalg_inv(mp_obj_t o_in) {
// TODO: I am not sure this is needed here. Otherwise,
// how should we free up the unused RAM of inverted?
m_del(mp_float_t, inverted->array->items, o->n*o->n);
mp_raise_ValueError("input matrix is singular");
mp_raise_ValueError(translate("input matrix is singular"));
}
return MP_OBJ_FROM_PTR(inverted);
}
MP_DEFINE_CONST_FUN_OBJ_1(linalg_inv_obj, linalg_inv);
mp_obj_t linalg_dot(mp_obj_t _m1, mp_obj_t _m2) {
// TODO: should the results be upcast?
if(!MP_OBJ_IS_TYPE(_m1, &ulab_ndarray_type) || !MP_OBJ_IS_TYPE(_m2, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("arguments must be ndarrays"));
}
ndarray_obj_t *m1 = MP_OBJ_TO_PTR(_m1);
ndarray_obj_t *m2 = MP_OBJ_TO_PTR(_m2);
if(m1->n != m2->m) {
mp_raise_ValueError("matrix dimensions do not match");
mp_raise_ValueError(translate("matrix dimensions do not match"));
}
// TODO: numpy uses upcasting here
ndarray_obj_t *out = create_new_ndarray(m1->m, m2->n, NDARRAY_FLOAT);
@ -203,102 +159,21 @@ mp_obj_t linalg_dot(mp_obj_t _m1, mp_obj_t _m2) {
v2 = ndarray_get_float_value(m2->array->items, m2->array->typecode, k*m2->n+j);
sum += v1 * v2;
}
outdata[i*m1->m+j] = sum;
outdata[j*m1->m+i] = sum;
}
}
return MP_OBJ_FROM_PTR(out);
}
mp_obj_t linalg_zeros_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t kind) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} } ,
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
uint8_t dtype = args[1].u_int;
if(!mp_obj_is_int(args[0].u_obj) && !mp_obj_is_type(args[0].u_obj, &mp_type_tuple)) {
mp_raise_TypeError("input argument must be an integer or a 2-tuple");
}
ndarray_obj_t *ndarray = NULL;
if(mp_obj_is_int(args[0].u_obj)) {
size_t n = mp_obj_get_int(args[0].u_obj);
ndarray = create_new_ndarray(1, n, dtype);
} else if(mp_obj_is_type(args[0].u_obj, &mp_type_tuple)) {
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(args[0].u_obj);
if(tuple->len != 2) {
mp_raise_TypeError("input argument must be an integer or a 2-tuple");
}
ndarray = create_new_ndarray(mp_obj_get_int(tuple->items[0]),
mp_obj_get_int(tuple->items[1]), dtype);
}
if(kind == 1) {
mp_obj_t one = mp_obj_new_int(1);
for(size_t i=0; i < ndarray->array->len; i++) {
mp_binary_set_val_array(dtype, ndarray->array->items, i, one);
}
}
return MP_OBJ_FROM_PTR(ndarray);
}
mp_obj_t linalg_zeros(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return linalg_zeros_ones(n_args, pos_args, kw_args, 0);
}
mp_obj_t linalg_ones(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return linalg_zeros_ones(n_args, pos_args, kw_args, 1);
}
mp_obj_t linalg_eye(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_M, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_k, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
size_t n = args[0].u_int, m;
int16_t k = args[2].u_int;
uint8_t dtype = args[3].u_int;
if(args[1].u_rom_obj == mp_const_none) {
m = n;
} else {
m = mp_obj_get_int(args[1].u_rom_obj);
}
ndarray_obj_t *ndarray = create_new_ndarray(m, n, dtype);
mp_obj_t one = mp_obj_new_int(1);
size_t i = 0;
if((k >= 0) && (k < n)) {
while(k < n) {
mp_binary_set_val_array(dtype, ndarray->array->items, i*n+k, one);
k++;
i++;
}
} else if((k < 0) && (-k < m)) {
k = -k;
i = 0;
while(k < m) {
mp_binary_set_val_array(dtype, ndarray->array->items, k*n+i, one);
k++;
i++;
}
}
return MP_OBJ_FROM_PTR(ndarray);
}
MP_DEFINE_CONST_FUN_OBJ_2(linalg_dot_obj, linalg_dot);
mp_obj_t linalg_det(mp_obj_t oin) {
if(!mp_obj_is_type(oin, &ulab_ndarray_type)) {
mp_raise_TypeError("function defined for ndarrays only");
if(!MP_OBJ_IS_TYPE(oin, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("function defined for ndarrays only"));
}
ndarray_obj_t *in = MP_OBJ_TO_PTR(oin);
if(in->m != in->n) {
mp_raise_ValueError("input must be square matrix");
mp_raise_ValueError(translate("input must be square matrix"));
}
mp_float_t *tmp = m_new(mp_float_t, in->n*in->n);
@ -329,13 +204,15 @@ mp_obj_t linalg_det(mp_obj_t oin) {
return mp_obj_new_float(det);
}
MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det);
mp_obj_t linalg_eig(mp_obj_t oin) {
if(!mp_obj_is_type(oin, &ulab_ndarray_type)) {
mp_raise_TypeError("function defined for ndarrays only");
if(!MP_OBJ_IS_TYPE(oin, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("function defined for ndarrays only"));
}
ndarray_obj_t *in = MP_OBJ_TO_PTR(oin);
if(in->m != in->n) {
mp_raise_ValueError("input must be square matrix");
mp_raise_ValueError(translate("input must be square matrix"));
}
mp_float_t *array = m_new(mp_float_t, in->array->len);
for(size_t i=0; i < in->array->len; i++) {
@ -347,7 +224,7 @@ mp_obj_t linalg_eig(mp_obj_t oin) {
// compare entry (m, n) to (n, m)
// TODO: this must probably be scaled!
if(epsilon < MICROPY_FLOAT_C_FUN(fabs)(array[m*in->n + n] - array[n*in->n + m])) {
mp_raise_ValueError("input matrix is asymmetric");
mp_raise_ValueError(translate("input matrix is asymmetric"));
}
}
}
@ -440,7 +317,7 @@ mp_obj_t linalg_eig(mp_obj_t oin) {
if(iterations == 0) {
// the computation did not converge; numpy raises LinAlgError
m_del(mp_float_t, array, in->array->len);
mp_raise_ValueError("iterations did not converge");
mp_raise_ValueError(translate("iterations did not converge"));
}
ndarray_obj_t *eigenvalues = create_new_ndarray(1, in->n, NDARRAY_FLOAT);
mp_float_t *eigvalues = (mp_float_t *)eigenvalues->array->items;
@ -455,3 +332,23 @@ mp_obj_t linalg_eig(mp_obj_t oin) {
return tuple;
return MP_OBJ_FROM_PTR(eigenvalues);
}
MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig);
STATIC const mp_rom_map_elem_t ulab_linalg_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_linalg) },
{ MP_ROM_QSTR(MP_QSTR_size), (mp_obj_t)&linalg_size_obj },
{ MP_ROM_QSTR(MP_QSTR_inv), (mp_obj_t)&linalg_inv_obj },
{ MP_ROM_QSTR(MP_QSTR_dot), (mp_obj_t)&linalg_dot_obj },
{ MP_ROM_QSTR(MP_QSTR_det), (mp_obj_t)&linalg_det_obj },
{ MP_ROM_QSTR(MP_QSTR_eig), (mp_obj_t)&linalg_eig_obj },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_linalg_globals, ulab_linalg_globals_table);
mp_obj_module_t ulab_linalg_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_linalg_globals,
};
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,16 +6,15 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _LINALG_
#define _LINALG_
#include "ulab.h"
#include "ndarray.h"
#define SWAP(t, a, b) { t tmp = a; a = b; b = tmp; }
#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
#define epsilon 1.2e-7
#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
@ -23,17 +23,13 @@
#define JACOBI_MAX 20
mp_obj_t linalg_transpose(mp_obj_t );
mp_obj_t linalg_reshape(mp_obj_t , mp_obj_t );
mp_obj_t linalg_size(size_t , const mp_obj_t *, mp_map_t *);
#if ULAB_LINALG_MODULE || ULAB_POLY_MODULE
bool linalg_invert_matrix(mp_float_t *, size_t );
mp_obj_t linalg_inv(mp_obj_t );
mp_obj_t linalg_dot(mp_obj_t , mp_obj_t );
mp_obj_t linalg_zeros(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t linalg_ones(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t linalg_eye(size_t , const mp_obj_t *, mp_map_t *);
#endif
mp_obj_t linalg_det(mp_obj_t );
mp_obj_t linalg_eig(mp_obj_t );
#if ULAB_LINALG_MODULE
extern mp_obj_module_t ulab_linalg_module;
#endif
#endif

View file

@ -3,13 +3,18 @@ USERMODULES_DIR := $(USERMOD_DIR)
# Add all C files to SRC_USERMOD.
SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c
SRC_USERMOD += $(USERMODULES_DIR)/create.c
SRC_USERMOD += $(USERMODULES_DIR)/linalg.c
SRC_USERMOD += $(USERMODULES_DIR)/vectorise.c
SRC_USERMOD += $(USERMODULES_DIR)/poly.c
SRC_USERMOD += $(USERMODULES_DIR)/fft.c
SRC_USERMOD += $(USERMODULES_DIR)/numerical.c
SRC_USERMOD += $(USERMODULES_DIR)/filter.c
SRC_USERMOD += $(USERMODULES_DIR)/extras.c
SRC_USERMOD += $(USERMODULES_DIR)/ulab.c
# We can add our module folder to include paths if needed
# This is not actually needed in this example.
CFLAGS_USERMOD += -I$(USERMODULES_DIR)
CFLAGS_EXTRA = -DMODULE_ULAB_ENABLED=1

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@ -153,7 +154,7 @@ mp_obj_t ndarray_copy(mp_obj_t self_in) {
STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT } },
};
@ -164,16 +165,13 @@ STATIC uint8_t ndarray_init_helper(size_t n_args, const mp_obj_t *pos_args, mp_m
return dtype;
}
mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 2, true);
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
uint8_t dtype = ndarray_init_helper(n_args, args, &kw_args);
STATIC mp_obj_t ndarray_make_new_core(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args, mp_map_t *kw_args) {
uint8_t dtype = ndarray_init_helper(n_args, args, kw_args);
size_t len1, len2=0, i=0;
mp_obj_t len_in = mp_obj_len_maybe(args[0]);
if (len_in == MP_OBJ_NULL) {
mp_raise_ValueError("first argument must be an iterable");
mp_raise_ValueError(translate("first argument must be an iterable"));
} else {
// len1 is either the number of rows (for matrices), or the number of elements (row vectors)
len1 = MP_OBJ_SMALL_INT_VALUE(len_in);
@ -189,7 +187,7 @@ mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw,
// Next, we have to check, whether all elements in the outer loop have the same length
if(i > 0) {
if(len2 != MP_OBJ_SMALL_INT_VALUE(len_in)) {
mp_raise_ValueError("iterables are not of the same length");
mp_raise_ValueError(translate("iterables are not of the same length"));
}
}
len2 = MP_OBJ_SMALL_INT_VALUE(len_in);
@ -214,14 +212,31 @@ mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw,
return MP_OBJ_FROM_PTR(self);
}
size_t slice_length(mp_bound_slice_t slice) {
// TODO: check, whether this is true!
if(slice.step < 0) {
slice.step = -slice.step;
return (slice.start - slice.stop) / slice.step;
} else {
return (slice.stop - slice.start) / slice.step;
#ifdef CIRCUITPY
mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
mp_arg_check_num(n_args, kw_args, 1, 2, true);
size_t n_kw = 0;
if (kw_args != 0) {
n_kw = kw_args->used;
}
mp_map_init_fixed_table(kw_args, n_kw, args + n_args);
return ndarray_make_new_core(type, n_args, n_kw, args, kw_args);
}
#else
mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 1, 2, true);
mp_map_t kw_args;
mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
return ndarray_make_new_core(type, n_args, n_kw, args, &kw_args);
}
#endif
size_t slice_length(mp_bound_slice_t slice) {
int32_t len, correction = 1;
if(slice.step > 0) correction = -1;
len = (slice.stop - slice.start + (slice.step + correction)) / slice.step;
if(len < 0) return 0;
return (size_t)len;
}
size_t true_length(mp_obj_t bool_list) {
@ -231,7 +246,7 @@ size_t true_length(mp_obj_t bool_list) {
mp_obj_t item, iterable = mp_getiter(bool_list, &iter_buf);
size_t trues = 0;
while((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
if(!mp_obj_is_type(item, &mp_type_bool)) {
if(!MP_OBJ_IS_TYPE(item, &mp_type_bool)) {
// numpy seems to be a little bit inconsistent in when an index is considered
// to be True/False. Bail out immediately, if the items are not True/False
return 0;
@ -248,19 +263,19 @@ mp_bound_slice_t generate_slice(mp_uint_t n, mp_obj_t index) {
mp_bound_slice_t slice;
if(MP_OBJ_IS_TYPE(index, &mp_type_slice)) {
mp_seq_get_fast_slice_indexes(n, index, &slice);
} else if(mp_obj_is_int(index)) {
} else if(MP_OBJ_IS_INT(index)) {
int32_t _index = mp_obj_get_int(index);
if(_index < 0) {
_index += n;
}
if((_index >= n) || (_index < 0)) {
mp_raise_msg(&mp_type_IndexError, "index is out of bounds");
mp_raise_msg(&mp_type_IndexError, translate("index is out of bounds"));
}
slice.start = _index;
slice.stop = _index + 1;
slice.step = 1;
} else {
mp_raise_msg(&mp_type_IndexError, "indices must be integers, slices, or Boolean lists");
mp_raise_msg(&mp_type_IndexError, translate("indices must be integers, slices, or Boolean lists"));
}
return slice;
}
@ -289,8 +304,8 @@ mp_obj_t insert_slice_list(ndarray_obj_t *ndarray, size_t m, size_t n,
mp_obj_t row_list, mp_obj_t column_list,
ndarray_obj_t *values) {
if((m != values->m) && (n != values->n)) {
if((values->array->len != 1)) { // not a single item
mp_raise_ValueError("could not broadast input array from shape");
if(values->array->len != 1) { // not a single item
mp_raise_ValueError(translate("could not broadast input array from shape"));
}
}
size_t cindex, rindex;
@ -377,7 +392,7 @@ mp_obj_t iterate_slice_list(ndarray_obj_t *ndarray, size_t m, size_t n,
mp_obj_t row_list, mp_obj_t column_list,
ndarray_obj_t *values) {
if((m == 0) || (n == 0)) {
mp_raise_msg(&mp_type_IndexError, "empty index range");
mp_raise_msg(&mp_type_IndexError, translate("empty index range"));
}
if(values != NULL) {
@ -462,7 +477,7 @@ mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarray_obj_t
mp_bound_slice_t row_slice = simple_slice(0, 0, 1), column_slice = simple_slice(0, 0, 1);
size_t m = 0, n = 0;
if(mp_obj_is_int(index) && (ndarray->m == 1) && (values == NULL)) {
if(MP_OBJ_IS_INT(index) && (ndarray->m == 1) && (values == NULL)) {
// we have a row vector, and don't want to assign
column_slice = generate_slice(ndarray->n, index);
if(slice_length(column_slice) == 1) { // we were asked for a single item
@ -471,7 +486,7 @@ mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarray_obj_t
}
}
if(mp_obj_is_int(index) || MP_OBJ_IS_TYPE(index, &mp_type_slice)) {
if(MP_OBJ_IS_INT(index) || MP_OBJ_IS_TYPE(index, &mp_type_slice)) {
if(ndarray->m == 1) { // we have a row vector
column_slice = generate_slice(ndarray->n, index);
row_slice = simple_slice(0, 1, 1);
@ -495,15 +510,15 @@ mp_obj_t ndarray_get_slice(ndarray_obj_t *ndarray, mp_obj_t index, ndarray_obj_t
else { // we certainly have a tuple, so let us deal with it
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(index);
if(tuple->len != 2) {
mp_raise_msg(&mp_type_IndexError, "too many indices");
mp_raise_msg(&mp_type_IndexError, translate("too many indices"));
}
if(!(MP_OBJ_IS_TYPE(tuple->items[0], &mp_type_list) ||
MP_OBJ_IS_TYPE(tuple->items[0], &mp_type_slice) ||
mp_obj_is_int(tuple->items[0])) ||
MP_OBJ_IS_INT(tuple->items[0])) ||
!(MP_OBJ_IS_TYPE(tuple->items[1], &mp_type_list) ||
MP_OBJ_IS_TYPE(tuple->items[1], &mp_type_slice) ||
mp_obj_is_int(tuple->items[1]))) {
mp_raise_msg(&mp_type_IndexError, "indices must be integers, slices, or Boolean lists");
MP_OBJ_IS_INT(tuple->items[1]))) {
mp_raise_msg(&mp_type_IndexError, translate("indices must be integers, slices, or Boolean lists"));
}
if(MP_OBJ_IS_TYPE(tuple->items[0], &mp_type_list)) { // rows are indexed by Boolean list
m = true_length(tuple->items[0]);
@ -543,11 +558,11 @@ mp_obj_t ndarray_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
return ndarray_get_slice(self, index, NULL);
} else { // assignment to slices; the value must be an ndarray, or a scalar
if(!MP_OBJ_IS_TYPE(value, &ulab_ndarray_type) &&
!mp_obj_is_int(value) && !mp_obj_is_float(value)) {
mp_raise_ValueError("right hand side must be an ndarray, or a scalar");
!MP_OBJ_IS_INT(value) && !mp_obj_is_float(value)) {
mp_raise_ValueError(translate("right hand side must be an ndarray, or a scalar"));
} else {
ndarray_obj_t *values = NULL;
if(mp_obj_is_int(value)) {
if(MP_OBJ_IS_INT(value)) {
values = create_new_ndarray(1, 1, self->array->typecode);
mp_binary_set_val_array(values->array->typecode, values->array->items, 0, value);
} else if(mp_obj_is_float(value)) {
@ -625,22 +640,14 @@ mp_obj_t ndarray_shape(mp_obj_t self_in) {
return mp_obj_new_tuple(2, tuple);
}
mp_obj_t ndarray_rawsize(mp_obj_t self_in) {
// returns a 5-tuple with the
//
// 0. number of rows
// 1. number of columns
// 2. length of the storage (should be equal to the product of 1. and 2.)
// 3. length of the data storage in bytes
// 4. datum size in bytes
mp_obj_t ndarray_size(mp_obj_t self_in) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(mp_obj_new_tuple(5, NULL));
tuple->items[0] = MP_OBJ_NEW_SMALL_INT(self->m);
tuple->items[1] = MP_OBJ_NEW_SMALL_INT(self->n);
tuple->items[2] = MP_OBJ_NEW_SMALL_INT(self->array->len);
tuple->items[3] = MP_OBJ_NEW_SMALL_INT(self->bytes);
tuple->items[4] = MP_OBJ_NEW_SMALL_INT(mp_binary_get_size('@', self->array->typecode, NULL));
return tuple;
return mp_obj_new_int(self->array->len);
}
mp_obj_t ndarray_itemsize(mp_obj_t self_in) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
return MP_OBJ_NEW_SMALL_INT(mp_binary_get_size('@', self->array->typecode, NULL));
}
mp_obj_t ndarray_flatten(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
@ -655,7 +662,7 @@ mp_obj_t ndarray_flatten(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a
GET_STR_DATA_LEN(args[0].u_obj, order, len);
if((len != 1) || ((memcmp(order, "C", 1) != 0) && (memcmp(order, "F", 1) != 0))) {
mp_raise_ValueError("flattening order must be either 'C', or 'F'");
mp_raise_ValueError(translate("flattening order must be either 'C', or 'F'"));
}
// if order == 'C', we simply have to set m, and n, there is nothing else to do
@ -678,11 +685,6 @@ mp_obj_t ndarray_flatten(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a
return self_copy;
}
mp_obj_t ndarray_asbytearray(mp_obj_t self_in) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
return MP_OBJ_FROM_PTR(self->array);
}
// Binary operations
mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
@ -694,7 +696,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
// TODO: implement in-place operators
mp_obj_t RHS = MP_OBJ_NULL;
bool rhs_is_scalar = true;
if(mp_obj_is_int(rhs)) {
if(MP_OBJ_IS_INT(rhs)) {
int32_t ivalue = mp_obj_get_int(rhs);
if((ivalue > 0) && (ivalue < 256)) {
CREATE_SINGLE_ITEM(RHS, uint8_t, NDARRAY_UINT8, ivalue);
@ -715,12 +717,12 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
rhs_is_scalar = false;
}
//else
if(mp_obj_is_type(lhs, &ulab_ndarray_type) && mp_obj_is_type(RHS, &ulab_ndarray_type)) {
if(MP_OBJ_IS_TYPE(lhs, &ulab_ndarray_type) && MP_OBJ_IS_TYPE(RHS, &ulab_ndarray_type)) {
// next, the ndarray stuff
ndarray_obj_t *ol = MP_OBJ_TO_PTR(lhs);
ndarray_obj_t *or = MP_OBJ_TO_PTR(RHS);
if(!rhs_is_scalar && ((ol->m != or->m) || (ol->n != or->n))) {
mp_raise_ValueError("operands could not be broadcast together");
mp_raise_ValueError(translate("operands could not be broadcast together"));
}
// At this point, the operands should have the same shape
switch(op) {
@ -823,7 +825,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
RUN_BINARY_LOOP(NDARRAY_FLOAT, mp_float_t, mp_float_t, mp_float_t, ol, or, op);
}
} else { // this should never happen
mp_raise_TypeError("wrong input type");
mp_raise_TypeError(translate("wrong input type"));
}
// this instruction should never be reached, but we have to make the compiler happy
return MP_OBJ_NULL;
@ -831,7 +833,7 @@ mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {
return MP_OBJ_NULL; // op not supported
}
} else {
mp_raise_TypeError("wrong operand type on the right hand side");
mp_raise_TypeError(translate("wrong operand type on the right hand side"));
}
}
@ -849,7 +851,7 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
case MP_UNARY_OP_INVERT:
if(self->array->typecode == NDARRAY_FLOAT) {
mp_raise_ValueError("operation is not supported for given type");
mp_raise_ValueError(translate("operation is not supported for given type"));
}
// we can invert the content byte by byte, there is no need to distinguish
// between different typecodes
@ -909,3 +911,67 @@ mp_obj_t ndarray_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
default: return MP_OBJ_NULL; // operator not supported
}
}
mp_obj_t ndarray_transpose(mp_obj_t self_in) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
// the size of a single item in the array
uint8_t _sizeof = mp_binary_get_size('@', self->array->typecode, NULL);
// NOTE:
// if the matrices are square, we can simply swap items, but
// generic matrices can't be transposed in place, so we have to
// declare a temporary variable
// NOTE:
// In the old matrix, the coordinate (m, n) is m*self->n + n
// We have to assign this to the coordinate (n, m) in the new
// matrix, i.e., to n*self->m + m (since the new matrix has self->m columns)
// one-dimensional arrays can be transposed by simply swapping the dimensions
if((self->m != 1) && (self->n != 1)) {
uint8_t *c = (uint8_t *)self->array->items;
// self->bytes is the size of the bytearray, irrespective of the typecode
uint8_t *tmp = m_new(uint8_t, self->bytes);
for(size_t m=0; m < self->m; m++) {
for(size_t n=0; n < self->n; n++) {
memcpy(tmp+_sizeof*(n*self->m + m), c+_sizeof*(m*self->n + n), _sizeof);
}
}
memcpy(self->array->items, tmp, self->bytes);
m_del(uint8_t, tmp, self->bytes);
}
SWAP(size_t, self->m, self->n);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_transpose_obj, ndarray_transpose);
mp_obj_t ndarray_reshape(mp_obj_t self_in, mp_obj_t shape) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
if(!MP_OBJ_IS_TYPE(shape, &mp_type_tuple) || (MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(shape)) != 2)) {
mp_raise_ValueError(translate("shape must be a 2-tuple"));
}
mp_obj_iter_buf_t iter_buf;
mp_obj_t item, iterable = mp_getiter(shape, &iter_buf);
uint16_t m, n;
item = mp_iternext(iterable);
m = mp_obj_get_int(item);
item = mp_iternext(iterable);
n = mp_obj_get_int(item);
if(m*n != self->m*self->n) {
// TODO: the proper error message would be "cannot reshape array of size %d into shape (%d, %d)"
mp_raise_ValueError(translate("cannot reshape array (incompatible input/output shape)"));
}
self->m = m;
self->n = n;
return MP_OBJ_FROM_PTR(self);
}
MP_DEFINE_CONST_FUN_OBJ_2(ndarray_reshape_obj, ndarray_reshape);
mp_int_t ndarray_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
ndarray_obj_t *self = MP_OBJ_TO_PTR(self_in);
// buffer_p.get_buffer() returns zero for success, while mp_get_buffer returns true for success
return !mp_get_buffer(self->array, bufinfo, flags);
}

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _NDARRAY_
#define _NDARRAY_
@ -24,6 +25,12 @@
#define FLOAT_TYPECODE 'd'
#endif
#if !CIRCUITPY
#define translate(x) x
#endif
#define SWAP(t, a, b) { t tmp = a; a = b; b = tmp; }
extern const mp_obj_type_t ulab_ndarray_type;
enum NDARRAY_TYPE {
@ -53,16 +60,30 @@ void ndarray_assign_elements(mp_obj_array_t *, mp_obj_t , uint8_t , size_t *);
ndarray_obj_t *create_new_ndarray(size_t , size_t , uint8_t );
mp_obj_t ndarray_copy(mp_obj_t );
#ifdef CIRCUITPY
mp_obj_t ndarray_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args);
#else
mp_obj_t ndarray_make_new(const mp_obj_type_t *, size_t , size_t , const mp_obj_t *);
#endif
mp_obj_t ndarray_subscr(mp_obj_t , mp_obj_t , mp_obj_t );
mp_obj_t ndarray_getiter(mp_obj_t , mp_obj_iter_buf_t *);
mp_obj_t ndarray_binary_op(mp_binary_op_t , mp_obj_t , mp_obj_t );
mp_obj_t ndarray_unary_op(mp_unary_op_t , mp_obj_t );
mp_obj_t ndarray_shape(mp_obj_t );
mp_obj_t ndarray_rawsize(mp_obj_t );
mp_obj_t ndarray_size(mp_obj_t );
mp_obj_t ndarray_itemsize(mp_obj_t );
mp_obj_t ndarray_flatten(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t ndarray_asbytearray(mp_obj_t );
mp_obj_t ndarray_reshape(mp_obj_t , mp_obj_t );
MP_DECLARE_CONST_FUN_OBJ_2(ndarray_reshape_obj);
mp_obj_t ndarray_transpose(mp_obj_t );
MP_DECLARE_CONST_FUN_OBJ_1(ndarray_transpose_obj);
mp_int_t ndarray_get_buffer(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags);
//void ndarray_attributes(mp_obj_t , qstr , mp_obj_t *);
#define CREATE_SINGLE_ITEM(outarray, type, typecode, value) do {\
ndarray_obj_t *tmp = create_new_ndarray(1, 1, (typecode));\

61
code/ndarray_properties.h Normal file
View file

@ -0,0 +1,61 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
* 2020 Zoltán Vörös
*/
#ifndef _NDARRAY_PROPERTIES_
#define _NDARRAY_PROPERTIES_
#include "py/runtime.h"
#include "py/binary.h"
#include "py/obj.h"
#include "py/objarray.h"
#include "ndarray.h"
#if CIRCUITPY
typedef struct _mp_obj_property_t {
mp_obj_base_t base;
mp_obj_t proxy[3]; // getter, setter, deleter
} mp_obj_property_t;
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_shape_obj, ndarray_shape);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_size_obj, ndarray_size);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_itemsize_obj, ndarray_itemsize);
STATIC const mp_obj_property_t ndarray_shape_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&ndarray_get_shape_obj,
mp_const_none,
mp_const_none },
};
STATIC const mp_obj_property_t ndarray_size_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&ndarray_get_size_obj,
mp_const_none,
mp_const_none },
};
STATIC const mp_obj_property_t ndarray_itemsize_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&ndarray_get_itemsize_obj,
mp_const_none,
mp_const_none },
};
#else
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_size_obj, ndarray_size);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_itemsize_obj, ndarray_itemsize);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape);
#endif
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
@ -18,6 +19,8 @@
#include "py/misc.h"
#include "numerical.h"
#if ULAB_NUMERICAL_MODULE
enum NUMERICAL_FUNCTION_TYPE {
NUMERICAL_MIN,
NUMERICAL_MAX,
@ -28,56 +31,33 @@ enum NUMERICAL_FUNCTION_TYPE {
NUMERICAL_STD,
};
mp_obj_t numerical_linspace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_num, MP_ARG_INT, {.u_int = 50} },
{ MP_QSTR_endpoint, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_true_obj)} },
{ MP_QSTR_retstep, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_false_obj)} },
{ MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(2, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
uint16_t len = args[2].u_int;
if(len < 2) {
mp_raise_ValueError("number of points must be at least 2");
}
mp_float_t value, step;
value = mp_obj_get_float(args[0].u_obj);
uint8_t typecode = args[5].u_int;
if(args[3].u_obj == mp_const_true) step = (mp_obj_get_float(args[1].u_obj)-value)/(len-1);
else step = (mp_obj_get_float(args[1].u_obj)-value)/len;
ndarray_obj_t *ndarray = create_new_ndarray(1, len, typecode);
if(typecode == NDARRAY_UINT8) {
uint8_t *array = (uint8_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (uint8_t)value;
} else if(typecode == NDARRAY_INT8) {
int8_t *array = (int8_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (int8_t)value;
} else if(typecode == NDARRAY_UINT16) {
uint16_t *array = (uint16_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (uint16_t)value;
} else if(typecode == NDARRAY_INT16) {
int16_t *array = (int16_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = (int16_t)value;
} else {
mp_float_t *array = (mp_float_t *)ndarray->array->items;
for(size_t i=0; i < len; i++, value += step) array[i] = value;
}
if(args[4].u_obj == mp_const_false) {
return MP_OBJ_FROM_PTR(ndarray);
} else {
mp_obj_t tuple[2];
tuple[0] = ndarray;
tuple[1] = mp_obj_new_float(step);
return mp_obj_new_tuple(2, tuple);
}
void axis_sorter(ndarray_obj_t *ndarray, mp_obj_t axis, size_t *m, size_t *n, size_t *N,
size_t *increment, size_t *len, size_t *start_inc) {
if(axis == mp_const_none) { // flatten the array
*m = 1;
*n = 1;
*len = ndarray->array->len;
*N = 1;
*increment = 1;
*start_inc = ndarray->array->len;
} else if((mp_obj_get_int(axis) == 1)) { // along the horizontal axis
*m = ndarray->m;
*n = 1;
*len = ndarray->n;
*N = ndarray->m;
*increment = 1;
*start_inc = ndarray->n;
} else { // along vertical axis
*m = 1;
*n = ndarray->n;
*len = ndarray->m;
*N = ndarray->n;
*increment = ndarray->n;
*start_inc = 1;
}
}
mp_obj_t numerical_sum_mean_std_array(mp_obj_t oin, uint8_t optype) {
mp_obj_t numerical_sum_mean_std_iterable(mp_obj_t oin, uint8_t optype, size_t ddof) {
mp_float_t value, sum = 0.0, sq_sum = 0.0;
mp_obj_iter_buf_t iter_buf;
mp_obj_t item, iterable = mp_getiter(oin, &iter_buf);
@ -85,233 +65,191 @@ mp_obj_t numerical_sum_mean_std_array(mp_obj_t oin, uint8_t optype) {
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
value = mp_obj_get_float(item);
sum += value;
if(optype == NUMERICAL_STD) {
sq_sum += value*value;
}
}
if(optype == NUMERICAL_SUM) {
return mp_obj_new_float(sum);
} else if(optype == NUMERICAL_MEAN) {
return mp_obj_new_float(sum/len);
} else {
} else { // this should be the case of the standard deviation
// TODO: note that we could get away with a single pass, if we used the Weldorf algorithm
// That should save a fair amount of time, because we would have to extract the values only once
iterable = mp_getiter(oin, &iter_buf);
sum /= len; // this is now the mean!
return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(sq_sum/len-sum*sum));
}
}
STATIC mp_float_t numerical_sum_mean_std_single_line(void *data, size_t start, size_t stop,
size_t stride, uint8_t typecode, uint8_t optype) {
mp_float_t sum = 0.0, sq_sum = 0.0, value;
size_t len = 0;
for(size_t i=start; i < stop; i+=stride, len++) {
value = ndarray_get_float_value(data, typecode, i);
sum += value;
if(optype == NUMERICAL_STD) {
sq_sum += value*value;
}
}
if(len == 0) {
mp_raise_ValueError("data length is 0!");
}
if(optype == NUMERICAL_SUM) {
return sum;
} else if(optype == NUMERICAL_MEAN) {
return sum/len;
} else {
sum /= len; // this is now the mean!
return MICROPY_FLOAT_C_FUN(sqrt)(sq_sum/len-sum*sum);
}
}
STATIC mp_obj_t numerical_sum_mean_std_matrix(mp_obj_t oin, mp_obj_t axis, uint8_t optype) {
ndarray_obj_t *in = MP_OBJ_TO_PTR(oin);
if((axis == mp_const_none) || (in->m == 1) || (in->n == 1)) {
// return the value for the flattened array
return mp_obj_new_float(numerical_sum_mean_std_single_line(in->array->items, 0,
in->array->len, 1, in->array->typecode, optype));
} else {
uint8_t _axis = mp_obj_get_int(axis);
size_t m = (_axis == 0) ? 1 : in->m;
size_t n = (_axis == 0) ? in->n : 1;
size_t len = in->array->len;
mp_float_t sms;
// TODO: pass in->array->typcode to create_new_ndarray
ndarray_obj_t *out = create_new_ndarray(m, n, NDARRAY_FLOAT);
// TODO: these two cases could probably be combined in a more elegant fashion...
if(_axis == 0) { // vertical
for(size_t i=0; i < n; i++) {
sms = numerical_sum_mean_std_single_line(in->array->items, i, len,
n, in->array->typecode, optype);
((float_t *)out->array->items)[i] = sms;
}
} else { // horizontal
for(size_t i=0; i < m; i++) {
sms = numerical_sum_mean_std_single_line(in->array->items, i*in->n,
(i+1)*in->n, 1, in->array->typecode, optype);
((float_t *)out->array->items)[i] = sms;
}
}
return MP_OBJ_FROM_PTR(out);
}
}
size_t numerical_argmin_argmax_array(ndarray_obj_t *in, size_t start,
size_t stop, size_t stride, uint8_t op) {
size_t best_idx = start;
if(in->array->typecode == NDARRAY_UINT8) {
ARG_MIN_LOOP(in, uint8_t, start, stop, stride, op);
} else if(in->array->typecode == NDARRAY_INT8) {
ARG_MIN_LOOP(in, int8_t, start, stop, stride, op);
} else if(in->array->typecode == NDARRAY_UINT16) {
ARG_MIN_LOOP(in, uint16_t, start, stop, stride, op);
} else if(in->array->typecode == NDARRAY_INT16) {
ARG_MIN_LOOP(in, uint16_t, start, stop, stride, op);
} else if(in->array->typecode == NDARRAY_FLOAT) {
ARG_MIN_LOOP(in, mp_float_t, start, stop, stride, op);
}
return best_idx;
}
void copy_value_into_ndarray(ndarray_obj_t *target, ndarray_obj_t *source, size_t target_idx, size_t source_idx) {
// since we are simply copying, it doesn't matter, whether the arrays are signed or unsigned,
// we can cast them in any way we like
// This could also be done with byte copies. I don't know, whether that would have any benefits
if((target->array->typecode == NDARRAY_UINT8) || (target->array->typecode == NDARRAY_INT8)) {
((uint8_t *)target->array->items)[target_idx] = ((uint8_t *)source->array->items)[source_idx];
} else if((target->array->typecode == NDARRAY_UINT16) || (target->array->typecode == NDARRAY_INT16)) {
((uint16_t *)target->array->items)[target_idx] = ((uint16_t *)source->array->items)[source_idx];
} else {
((float *)target->array->items)[target_idx] = ((float *)source->array->items)[source_idx];
}
}
STATIC mp_obj_t numerical_argmin_argmax(mp_obj_t oin, mp_obj_t axis, uint8_t optype) {
if(MP_OBJ_IS_TYPE(oin, &mp_type_tuple) || MP_OBJ_IS_TYPE(oin, &mp_type_list) ||
MP_OBJ_IS_TYPE(oin, &mp_type_range)) {
// This case will work for single iterables only
size_t idx = 0, best_idx = 0;
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(oin, &iter_buf);
mp_obj_t best_obj = MP_OBJ_NULL;
mp_obj_t item;
mp_uint_t op = MP_BINARY_OP_LESS;
if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) op = MP_BINARY_OP_MORE;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
if ((best_obj == MP_OBJ_NULL) || (mp_binary_op(op, item, best_obj) == mp_const_true)) {
best_obj = item;
best_idx = idx;
}
idx++;
value = mp_obj_get_float(item) - sum;
sq_sum += value * value;
}
if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) {
return MP_OBJ_NEW_SMALL_INT(best_idx);
} else {
return best_obj;
}
} else if(mp_obj_is_type(oin, &ulab_ndarray_type)) {
ndarray_obj_t *in = MP_OBJ_TO_PTR(oin);
size_t best_idx;
if((axis == mp_const_none) || (in->m == 1) || (in->n == 1)) {
// return the value for the flattened array
best_idx = numerical_argmin_argmax_array(in, 0, in->array->len, 1, optype);
if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) {
return MP_OBJ_NEW_SMALL_INT(best_idx);
} else {
if(in->array->typecode == NDARRAY_FLOAT) {
return mp_obj_new_float(ndarray_get_float_value(in->array->items, in->array->typecode, best_idx));
} else {
return mp_binary_get_val_array(in->array->typecode, in->array->items, best_idx);
}
}
} else { // we have to work with a full matrix here
uint8_t _axis = mp_obj_get_int(axis);
size_t m = (_axis == 0) ? 1 : in->m;
size_t n = (_axis == 0) ? in->n : 1;
size_t len = in->array->len;
ndarray_obj_t *ndarray = NULL;
if((optype == NUMERICAL_MAX) || (optype == NUMERICAL_MIN)) {
ndarray = create_new_ndarray(m, n, in->array->typecode);
} else { // argmin/argmax
// TODO: one might get away with uint8_t, if both m, and n < 255
ndarray = create_new_ndarray(m, n, NDARRAY_UINT16);
}
// TODO: these two cases could probably be combined in a more elegant fashion...
if(_axis == 0) { // vertical
for(size_t i=0; i < n; i++) {
best_idx = numerical_argmin_argmax_array(in, i, len, n, optype);
if((optype == NUMERICAL_MIN) || (optype == NUMERICAL_MAX)) {
copy_value_into_ndarray(ndarray, in, i, best_idx);
} else {
((uint16_t *)ndarray->array->items)[i] = (uint16_t)(best_idx / n);
}
}
} else { // horizontal
for(size_t i=0; i < m; i++) {
best_idx = numerical_argmin_argmax_array(in, i*in->n, (i+1)*in->n, 1, optype);
if((optype == NUMERICAL_MIN) || (optype == NUMERICAL_MAX)) {
copy_value_into_ndarray(ndarray, in, i, best_idx);
} else {
((uint16_t *)ndarray->array->items)[i] = (uint16_t)(best_idx - i*in->n);
}
}
}
return MP_OBJ_FROM_PTR(ndarray);
}
return mp_const_none;
}
mp_raise_TypeError("input type is not supported");
return mp_obj_new_float(MICROPY_FLOAT_C_FUN(sqrt)(sq_sum/(len-ddof)));
}
}
STATIC mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t type) {
STATIC mp_obj_t numerical_sum_mean_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype) {
size_t m, n, increment, start, start_inc, N, len;
axis_sorter(ndarray, axis, &m, &n, &N, &increment, &len, &start_inc);
ndarray_obj_t *results = create_new_ndarray(m, n, NDARRAY_FLOAT);
mp_float_t sum, sq_sum;
mp_float_t *farray = (mp_float_t *)results->array->items;
for(size_t j=0; j < N; j++) { // result index
start = j * start_inc;
sum = sq_sum = 0.0;
if(ndarray->array->typecode == NDARRAY_UINT8) {
RUN_SUM(ndarray, uint8_t, optype, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_INT8) {
RUN_SUM(ndarray, int8_t, optype, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_UINT16) {
RUN_SUM(ndarray, uint16_t, optype, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_INT16) {
RUN_SUM(ndarray, int16_t, optype, len, start, increment);
} else { // this will be mp_float_t, no need to check
RUN_SUM(ndarray, mp_float_t, optype, len, start, increment);
}
if(optype == NUMERICAL_SUM) {
farray[j] = sum;
} else { // this is the case of the mean
farray[j] = sum / len;
}
}
if(results->array->len == 1) {
return mp_obj_new_float(farray[0]);
}
return MP_OBJ_FROM_PTR(results);
}
mp_obj_t numerical_std_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, size_t ddof) {
size_t m, n, increment, start, start_inc, N, len;
mp_float_t sum, sum_sq;
axis_sorter(ndarray, axis, &m, &n, &N, &increment, &len, &start_inc);
if(ddof > len) {
mp_raise_ValueError(translate("ddof must be smaller than length of data set"));
}
ndarray_obj_t *results = create_new_ndarray(m, n, NDARRAY_FLOAT);
mp_float_t *farray = (mp_float_t *)results->array->items;
for(size_t j=0; j < N; j++) { // result index
start = j * start_inc;
sum = 0.0;
sum_sq = 0.0;
if(ndarray->array->typecode == NDARRAY_UINT8) {
RUN_STD(ndarray, uint8_t, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_INT8) {
RUN_STD(ndarray, int8_t, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_UINT16) {
RUN_STD(ndarray, uint16_t, len, start, increment);
} else if(ndarray->array->typecode == NDARRAY_INT16) {
RUN_STD(ndarray, int16_t, len, start, increment);
} else { // this will be mp_float_t, no need to check
RUN_STD(ndarray, mp_float_t, len, start, increment);
}
farray[j] = MICROPY_FLOAT_C_FUN(sqrt)(sum_sq/(len - ddof));
}
if(results->array->len == 1) {
return mp_obj_new_float(farray[0]);
}
return MP_OBJ_FROM_PTR(results);
}
mp_obj_t numerical_argmin_argmax_iterable(mp_obj_t oin, mp_obj_t axis, uint8_t optype) {
size_t idx = 0, best_idx = 0;
mp_obj_iter_buf_t iter_buf;
mp_obj_t iterable = mp_getiter(oin, &iter_buf);
mp_obj_t best_obj = MP_OBJ_NULL;
mp_obj_t item;
mp_uint_t op = MP_BINARY_OP_LESS;
if((optype == NUMERICAL_ARGMAX) || (optype == NUMERICAL_MAX)) op = MP_BINARY_OP_MORE;
while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
if ((best_obj == MP_OBJ_NULL) || (mp_binary_op(op, item, best_obj) == mp_const_true)) {
best_obj = item;
best_idx = idx;
}
idx++;
}
if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) {
return MP_OBJ_NEW_SMALL_INT(best_idx);
} else {
return best_obj;
}
}
mp_obj_t numerical_argmin_argmax_ndarray(ndarray_obj_t *ndarray, mp_obj_t axis, uint8_t optype) {
size_t m, n, increment, start, start_inc, N, len;
axis_sorter(ndarray, axis, &m, &n, &N, &increment, &len, &start_inc);
ndarray_obj_t *results;
if((optype == NUMERICAL_ARGMIN) || (optype == NUMERICAL_ARGMAX)) {
// we could save some RAM by taking NDARRAY_UINT8, if the dimensions
// are smaller than 256, but the code would become more involving
// (we would also need extra flash space)
results = create_new_ndarray(m, n, NDARRAY_UINT16);
} else {
results = create_new_ndarray(m, n, ndarray->array->typecode);
}
for(size_t j=0; j < N; j++) { // result index
start = j * start_inc;
if((ndarray->array->typecode == NDARRAY_UINT8) || (ndarray->array->typecode == NDARRAY_INT8)) {
if((optype == NUMERICAL_MAX) || (optype == NUMERICAL_MIN)) {
RUN_ARGMIN(ndarray, results, uint8_t, uint8_t, len, start, increment, optype, j);
} else {
RUN_ARGMIN(ndarray, results, uint8_t, uint16_t, len, start, increment, optype, j);
}
} else if((ndarray->array->typecode == NDARRAY_UINT16) || (ndarray->array->typecode == NDARRAY_INT16)) {
RUN_ARGMIN(ndarray, results, uint16_t, uint16_t, len, start, increment, optype, j);
} else {
if((optype == NUMERICAL_MAX) || (optype == NUMERICAL_MIN)) {
RUN_ARGMIN(ndarray, results, mp_float_t, mp_float_t, len, start, increment, optype, j);
} else {
RUN_ARGMIN(ndarray, results, mp_float_t, uint16_t, len, start, increment, optype, j);
}
}
}
return MP_OBJ_FROM_PTR(results);
}
STATIC mp_obj_t numerical_function(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args, uint8_t optype) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} } ,
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none} } ,
{ MP_QSTR_axis, MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(1, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_obj_t oin = args[0].u_obj;
mp_obj_t axis = args[1].u_obj;
if((axis != mp_const_none) && (mp_obj_get_int(axis) != 0) && (mp_obj_get_int(axis) != 1)) {
// this seems to pass with False, and True...
mp_raise_ValueError("axis must be None, 0, or 1");
mp_raise_ValueError(translate("axis must be None, 0, or 1"));
}
if(MP_OBJ_IS_TYPE(oin, &mp_type_tuple) || MP_OBJ_IS_TYPE(oin, &mp_type_list) ||
MP_OBJ_IS_TYPE(oin, &mp_type_range)) {
switch(type) {
switch(optype) {
case NUMERICAL_MIN:
case NUMERICAL_ARGMIN:
case NUMERICAL_MAX:
case NUMERICAL_ARGMAX:
return numerical_argmin_argmax(oin, axis, type);
return numerical_argmin_argmax_iterable(oin, axis, optype);
case NUMERICAL_SUM:
case NUMERICAL_MEAN:
case NUMERICAL_STD:
return numerical_sum_mean_std_array(oin, type);
return numerical_sum_mean_std_iterable(oin, optype, 0);
default: // we should never reach this point, but whatever
return mp_const_none;
}
} else if(MP_OBJ_IS_TYPE(oin, &ulab_ndarray_type)) {
switch(type) {
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin);
switch(optype) {
case NUMERICAL_MIN:
case NUMERICAL_MAX:
case NUMERICAL_ARGMIN:
case NUMERICAL_ARGMAX:
return numerical_argmin_argmax(oin, axis, type);
return numerical_argmin_argmax_ndarray(ndarray, axis, optype);
case NUMERICAL_SUM:
case NUMERICAL_MEAN:
case NUMERICAL_STD:
return numerical_sum_mean_std_matrix(oin, axis, type);
return numerical_sum_mean_ndarray(ndarray, axis, optype);
default:
mp_raise_NotImplementedError("operation is not implemented on ndarrays");
mp_raise_NotImplementedError(translate("operation is not implemented on ndarrays"));
}
} else {
mp_raise_TypeError("input must be tuple, list, range, or ndarray");
mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray"));
}
return mp_const_none;
}
@ -320,35 +258,73 @@ mp_obj_t numerical_min(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_arg
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MIN);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_min_obj, 1, numerical_min);
mp_obj_t numerical_max(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MAX);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_max_obj, 1, numerical_max);
mp_obj_t numerical_argmin(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMIN);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmin_obj, 1, numerical_argmin);
mp_obj_t numerical_argmax(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_ARGMAX);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmax_obj, 1, numerical_argmax);
mp_obj_t numerical_sum(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_SUM);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sum_obj, 1, numerical_sum);
mp_obj_t numerical_mean(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_MEAN);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_mean_obj, 1, numerical_mean);
mp_obj_t numerical_std(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
return numerical_function(n_args, pos_args, kw_args, NUMERICAL_STD);
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } } ,
{ MP_QSTR_axis, MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_ddof, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_obj_t oin = args[0].u_obj;
mp_obj_t axis = args[1].u_obj;
size_t ddof = args[2].u_int;
if((axis != mp_const_none) && (mp_obj_get_int(axis) != 0) && (mp_obj_get_int(axis) != 1)) {
// this seems to pass with False, and True...
mp_raise_ValueError(translate("axis must be None, 0, or 1"));
}
if(MP_OBJ_IS_TYPE(oin, &mp_type_tuple) || MP_OBJ_IS_TYPE(oin, &mp_type_list) || MP_OBJ_IS_TYPE(oin, &mp_type_range)) {
return numerical_sum_mean_std_iterable(oin, NUMERICAL_STD, ddof);
} else if(MP_OBJ_IS_TYPE(oin, &ulab_ndarray_type)) {
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(oin);
return numerical_std_ndarray(ndarray, axis, ddof);
} else {
mp_raise_TypeError(translate("input must be tuple, list, range, or ndarray"));
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_std_obj, 1, numerical_std);
mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
@ -359,7 +335,7 @@ mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
if((args[2].u_obj != mp_const_none) &&
(mp_obj_get_int(args[2].u_obj) != 0) &&
(mp_obj_get_int(args[2].u_obj) != 1)) {
mp_raise_ValueError("axis must be None, 0, or 1");
mp_raise_ValueError(translate("axis must be None, 0, or 1"));
}
ndarray_obj_t *in = MP_OBJ_TO_PTR(oin);
@ -424,22 +400,24 @@ mp_obj_t numerical_roll(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
}
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_roll_obj, 2, numerical_roll);
mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(1, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError("flip argument must be an ndarray");
if(!MP_OBJ_IS_TYPE(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("flip argument must be an ndarray"));
}
if((args[1].u_obj != mp_const_none) &&
(mp_obj_get_int(args[1].u_obj) != 0) &&
(mp_obj_get_int(args[1].u_obj) != 1)) {
mp_raise_ValueError("axis must be None, 0, or 1");
mp_raise_ValueError(translate("axis must be None, 0, or 1"));
}
ndarray_obj_t *in = MP_OBJ_TO_PTR(args[0].u_obj);
@ -471,9 +449,11 @@ mp_obj_t numerical_flip(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
return out;
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_flip_obj, 1, numerical_flip);
mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_n, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1 } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1 } },
};
@ -481,8 +461,8 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(1, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError("diff argument must be an ndarray");
if(!MP_OBJ_IS_TYPE(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("diff argument must be an ndarray"));
}
ndarray_obj_t *in = MP_OBJ_TO_PTR(args[0].u_obj);
@ -492,10 +472,10 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
} else if(args[2].u_int == 0) { // differtiate along vertical axis
increment = in->n;
} else {
mp_raise_ValueError("axis must be -1, 0, or 1");
mp_raise_ValueError(translate("axis must be -1, 0, or 1"));
}
if((args[1].u_int < 0) || (args[1].u_int > 9)) {
mp_raise_ValueError("n must be between 0, and 9");
mp_raise_ValueError(translate("n must be between 0, and 9"));
}
uint8_t n = args[1].u_int;
int8_t *stencil = m_new(int8_t, n+1);
@ -539,9 +519,11 @@ mp_obj_t numerical_diff(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
return MP_OBJ_FROM_PTR(out);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_diff_obj, 1, numerical_diff);
mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inplace) {
if(!mp_obj_is_type(oin, &ulab_ndarray_type)) {
mp_raise_TypeError("sort argument must be an ndarray");
if(!MP_OBJ_IS_TYPE(oin, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("sort argument must be an ndarray"));
}
ndarray_obj_t *ndarray;
@ -572,7 +554,7 @@ mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inplace) {
end = ndarray->m;
N = ndarray->m;
} else {
mp_raise_ValueError("axis must be -1, 0, None, or 1");
mp_raise_ValueError(translate("axis must be -1, 0, None, or 1"));
}
size_t q, k, p, c;
@ -598,7 +580,7 @@ mp_obj_t numerical_sort_helper(mp_obj_t oin, mp_obj_t axis, uint8_t inplace) {
// numpy function
mp_obj_t numerical_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_int = -1 } },
};
@ -607,10 +589,13 @@ mp_obj_t numerical_sort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_ar
return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 0);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_obj, 1, numerical_sort);
// method of an ndarray
mp_obj_t numerical_sort_inplace(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_int = -1 } },
};
@ -620,15 +605,17 @@ mp_obj_t numerical_sort_inplace(size_t n_args, const mp_obj_t *pos_args, mp_map_
return numerical_sort_helper(args[0].u_obj, args[1].u_obj, 1);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj, 1, numerical_sort_inplace);
mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj) } },
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_rom_obj = mp_const_none } },
{ MP_QSTR_axis, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_int = -1 } },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(1, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if(!mp_obj_is_type(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError("argsort argument must be an ndarray");
if(!MP_OBJ_IS_TYPE(args[0].u_obj, &ulab_ndarray_type)) {
mp_raise_TypeError(translate("argsort argument must be an ndarray"));
}
ndarray_obj_t *ndarray = MP_OBJ_TO_PTR(args[0].u_obj);
@ -658,7 +645,7 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
end = m;
N = m;
} else {
mp_raise_ValueError("axis must be -1, 0, None, or 1");
mp_raise_ValueError(translate("axis must be -1, 0, None, or 1"));
}
// at the expense of flash, we could save RAM by creating
@ -689,3 +676,30 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
}
return MP_OBJ_FROM_PTR(indices);
}
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort);
STATIC const mp_rom_map_elem_t ulab_numerical_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_numerical) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&numerical_sum_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&numerical_mean_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_std), (mp_obj_t)&numerical_std_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&numerical_min_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argmin), (mp_obj_t)&numerical_argmin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argmax), (mp_obj_t)&numerical_argmax_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_numerical_globals, ulab_numerical_globals_table);
mp_obj_module_t ulab_numerical_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_numerical_globals,
};
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,47 +6,62 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _NUMERICAL_
#define _NUMERICAL_
#include "ulab.h"
#include "ndarray.h"
mp_obj_t numerical_linspace(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_sum(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_mean(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_std(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_min(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_max(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_argmin(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_argmax(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_roll(size_t , const mp_obj_t *, mp_map_t *);
#if ULAB_NUMERICAL_MODULE
extern mp_obj_module_t ulab_numerical_module;
// TODO: implement minimum/maximum, and cumsum
mp_obj_t numerical_minimum(mp_obj_t , mp_obj_t );
mp_obj_t numerical_maximum(mp_obj_t , mp_obj_t );
mp_obj_t numerical_cumsum(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_flip(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_diff(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_sort(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_sort_inplace(size_t , const mp_obj_t *, mp_map_t *);
mp_obj_t numerical_argsort(size_t , const mp_obj_t *, mp_map_t *);
//mp_obj_t numerical_minimum(mp_obj_t , mp_obj_t );
//mp_obj_t numerical_maximum(mp_obj_t , mp_obj_t );
//mp_obj_t numerical_cumsum(size_t , const mp_obj_t *, mp_map_t *);
// this macro could be tighter, if we moved the ifs to the argmin function, assigned <, as well as >
#define ARG_MIN_LOOP(in, type, start, stop, stride, op) do {\
type *array = (type *)(in)->array->items;\
#define RUN_ARGMIN(in, out, typein, typeout, len, start, increment, op, pos) do {\
typein *array = (typein *)(in)->array->items;\
typeout *outarray = (typeout *)(out)->array->items;\
size_t best_index = 0;\
if(((op) == NUMERICAL_MAX) || ((op) == NUMERICAL_ARGMAX)) {\
for(size_t i=(start)+(stride); i < (stop); i+=(stride)) {\
if((array)[i] > (array)[best_idx]) {\
best_idx = i;\
}\
for(size_t i=1; i < (len); i++) {\
if(array[(start)+i*(increment)] > array[(start)+best_index*(increment)]) best_index = i;\
}\
if((op) == NUMERICAL_MAX) outarray[(pos)] = array[(start)+best_index*(increment)];\
else outarray[(pos)] = best_index;\
} else{\
for(size_t i=(start)+(stride); i < (stop); i+=(stride)) {\
if((array)[i] < (array)[best_idx]) best_idx = i;\
for(size_t i=1; i < (len); i++) {\
if(array[(start)+i*(increment)] < array[(start)+best_index*(increment)]) best_index = i;\
}\
if((op) == NUMERICAL_MIN) outarray[(pos)] = array[(start)+best_index*(increment)];\
else outarray[(pos)] = best_index;\
}\
} while(0)
#define RUN_SUM(ndarray, type, optype, len, start, increment) do {\
type *array = (type *)(ndarray)->array->items;\
type value;\
for(size_t j=0; j < (len); j++) {\
value = array[(start)+j*(increment)];\
sum += value;\
}\
} while(0)
#define RUN_STD(ndarray, type, len, start, increment) do {\
type *array = (type *)(ndarray)->array->items;\
mp_float_t value;\
for(size_t j=0; j < (len); j++) {\
sum += array[(start)+j*(increment)];\
}\
sum /= (len);\
for(size_t j=0; j < (len); j++) {\
value = (array[(start)+j*(increment)] - sum);\
sum_sq += value * value;\
}\
} while(0)
@ -132,4 +148,19 @@ mp_obj_t numerical_argsort(size_t , const mp_obj_t *, mp_map_t *);
}\
} while(0)
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_min_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_max_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmin_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argmax_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sum_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_mean_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_std_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_roll_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_flip_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_diff_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj);
MP_DECLARE_CONST_FUN_OBJ_KW(numerical_argsort_obj);
#endif
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include "py/obj.h"
#include "py/runtime.h"
#include "py/objarray.h"
@ -15,19 +16,19 @@
#include "linalg.h"
#include "poly.h"
#if ULAB_POLY_MODULE
bool object_is_nditerable(mp_obj_t o_in) {
if(mp_obj_is_type(o_in, &ulab_ndarray_type) ||
mp_obj_is_type(o_in, &mp_type_tuple) ||
mp_obj_is_type(o_in, &mp_type_list) ||
mp_obj_is_type(o_in, &mp_type_range)) {
if(MP_OBJ_IS_TYPE(o_in, &ulab_ndarray_type) ||
MP_OBJ_IS_TYPE(o_in, &mp_type_tuple) ||
MP_OBJ_IS_TYPE(o_in, &mp_type_list) ||
MP_OBJ_IS_TYPE(o_in, &mp_type_range)) {
return true;
}
return false;
}
size_t get_nditerable_len(mp_obj_t o_in) {
if(mp_obj_is_type(o_in, &ulab_ndarray_type)) {
if(MP_OBJ_IS_TYPE(o_in, &ulab_ndarray_type)) {
ndarray_obj_t *in = MP_OBJ_TO_PTR(o_in);
return in->array->len;
} else {
@ -82,12 +83,14 @@ mp_obj_t poly_polyval(mp_obj_t o_p, mp_obj_t o_x) {
return MP_OBJ_FROM_PTR(out);
}
MP_DEFINE_CONST_FUN_OBJ_2(poly_polyval_obj, poly_polyval);
mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
if((n_args != 2) && (n_args != 3)) {
mp_raise_ValueError("number of arguments must be 2, or 3");
mp_raise_ValueError(translate("number of arguments must be 2, or 3"));
}
if(!object_is_nditerable(args[0])) {
mp_raise_ValueError("input data must be an iterable");
mp_raise_ValueError(translate("input data must be an iterable"));
}
uint16_t lenx = 0, leny = 0;
uint8_t deg = 0;
@ -99,7 +102,7 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
leny = (uint16_t)mp_obj_get_int(mp_obj_len_maybe(args[0]));
deg = (uint8_t)mp_obj_get_int(args[1]);
if(leny < deg) {
mp_raise_ValueError("more degrees of freedom than data points");
mp_raise_ValueError(translate("more degrees of freedom than data points"));
}
lenx = leny;
x = m_new(mp_float_t, lenx); // assume uniformly spaced data points
@ -112,11 +115,11 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
lenx = (uint16_t)mp_obj_get_int(mp_obj_len_maybe(args[0]));
leny = (uint16_t)mp_obj_get_int(mp_obj_len_maybe(args[0]));
if(lenx != leny) {
mp_raise_ValueError("input vectors must be of equal length");
mp_raise_ValueError(translate("input vectors must be of equal length"));
}
deg = (uint8_t)mp_obj_get_int(args[2]);
if(leny < deg) {
mp_raise_ValueError("more degrees of freedom than data points");
mp_raise_ValueError(translate("more degrees of freedom than data points"));
}
x = m_new(mp_float_t, lenx);
fill_array_iterable(x, args[0]);
@ -156,7 +159,7 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
m_del(mp_float_t, x, lenx);
m_del(mp_float_t, y, lenx);
m_del(mp_float_t, prod, (deg+1)*(deg+1));
mp_raise_ValueError("could not invert Vandermonde matrix");
mp_raise_ValueError(translate("could not invert Vandermonde matrix"));
}
// at this point, we have the inverse of X^T * X
// y is a column vector; x is free now, we can use it for storing intermediate values
@ -191,3 +194,20 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
}
return MP_OBJ_FROM_PTR(beta);
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj, 2, 3, poly_polyfit);
STATIC const mp_rom_map_elem_t ulab_poly_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_poly) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_polyval), (mp_obj_t)&poly_polyval_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_polyfit), (mp_obj_t)&poly_polyfit_obj },
};
STATIC MP_DEFINE_CONST_DICT(mp_module_ulab_poly_globals, ulab_poly_globals_table);
mp_obj_module_t ulab_poly_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_poly_globals,
};
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,13 +6,20 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _POLY_
#define _POLY_
mp_obj_t poly_polyval(mp_obj_t , mp_obj_t );
mp_obj_t poly_polyfit(size_t , const mp_obj_t *);
#include "ulab.h"
#if ULAB_POLY_MODULE
extern mp_obj_module_t ulab_poly_module;
MP_DECLARE_CONST_FUN_OBJ_2(poly_polyval_obj);
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj);
#endif
#endif

View file

@ -1,13 +1,14 @@
/*
* This file is part of the micropython-ulab project,
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@ -17,92 +18,32 @@
#include "py/obj.h"
#include "py/objarray.h"
#include "ulab.h"
#include "ndarray.h"
#include "ndarray_properties.h"
#include "create.h"
#include "linalg.h"
#include "vectorise.h"
#include "poly.h"
#include "fft.h"
#include "filter.h"
#include "numerical.h"
#include "extras.h"
#define ULAB_VERSION 0.263
STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, "0.36.0");
typedef struct _mp_obj_float_t {
mp_obj_base_t base;
mp_float_t value;
} mp_obj_float_t;
mp_obj_float_t ulab_version = {{&mp_type_float}, ULAB_VERSION};
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_rawsize_obj, ndarray_rawsize);
MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);
MP_DEFINE_CONST_FUN_OBJ_1(ndarray_asbytearray_obj, ndarray_asbytearray);
MP_DEFINE_CONST_FUN_OBJ_1(linalg_transpose_obj, linalg_transpose);
MP_DEFINE_CONST_FUN_OBJ_2(linalg_reshape_obj, linalg_reshape);
MP_DEFINE_CONST_FUN_OBJ_KW(linalg_size_obj, 1, linalg_size);
MP_DEFINE_CONST_FUN_OBJ_1(linalg_inv_obj, linalg_inv);
MP_DEFINE_CONST_FUN_OBJ_2(linalg_dot_obj, linalg_dot);
MP_DEFINE_CONST_FUN_OBJ_KW(linalg_zeros_obj, 0, linalg_zeros);
MP_DEFINE_CONST_FUN_OBJ_KW(linalg_ones_obj, 0, linalg_ones);
MP_DEFINE_CONST_FUN_OBJ_KW(linalg_eye_obj, 0, linalg_eye);
MP_DEFINE_CONST_FUN_OBJ_1(linalg_det_obj, linalg_det);
MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sinh_obj, vectorise_sinh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tanh_obj, vectorise_tanh);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_linspace_obj, 2, numerical_linspace);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sum_obj, 1, numerical_sum);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_mean_obj, 1, numerical_mean);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_std_obj, 1, numerical_std);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_min_obj, 1, numerical_min);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_max_obj, 1, numerical_max);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmin_obj, 1, numerical_argmin);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argmax_obj, 1, numerical_argmax);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_roll_obj, 2, numerical_roll);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_flip_obj, 1, numerical_flip);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_diff_obj, 1, numerical_diff);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_obj, 1, numerical_sort);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_sort_inplace_obj, 1, numerical_sort_inplace);
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort);
STATIC MP_DEFINE_CONST_FUN_OBJ_2(poly_polyval_obj, poly_polyval);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj, 2, 3, poly_polyfit);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_fft_obj, 1, 2, fft_fft);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_ifft_obj, 1, 2, fft_ifft);
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_spectrum_obj, 1, 2, fft_spectrum);
STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_reshape), MP_ROM_PTR(&ndarray_reshape_obj) },
{ MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&ndarray_transpose_obj) },
{ MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) },
{ MP_ROM_QSTR(MP_QSTR_shape), MP_ROM_PTR(&ndarray_shape_obj) },
{ MP_ROM_QSTR(MP_QSTR_rawsize), MP_ROM_PTR(&ndarray_rawsize_obj) },
{ MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) },
{ MP_ROM_QSTR(MP_QSTR_asbytearray), MP_ROM_PTR(&ndarray_asbytearray_obj) },
{ MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&linalg_transpose_obj) },
{ MP_ROM_QSTR(MP_QSTR_reshape), MP_ROM_PTR(&linalg_reshape_obj) },
{ MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&ndarray_size_obj) },
{ MP_ROM_QSTR(MP_QSTR_itemsize), MP_ROM_PTR(&ndarray_itemsize_obj) },
#if CIRCUITPY
{ MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) },
#endif
};
STATIC MP_DEFINE_CONST_DICT(ulab_ndarray_locals_dict, ulab_ndarray_locals_dict_table);
@ -116,62 +57,39 @@ const mp_obj_type_t ulab_ndarray_type = {
.getiter = ndarray_getiter,
.unary_op = ndarray_unary_op,
.binary_op = ndarray_binary_op,
.buffer_p = { .get_buffer = ndarray_get_buffer, },
.locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict,
};
STATIC const mp_map_elem_t ulab_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_ulab) },
{ MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version) },
{ MP_ROM_QSTR(MP_QSTR___version__), MP_ROM_PTR(&ulab_version_obj) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_array), (mp_obj_t)&ulab_ndarray_type },
{ MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&linalg_size_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_inv), (mp_obj_t)&linalg_inv_obj },
{ MP_ROM_QSTR(MP_QSTR_dot), (mp_obj_t)&linalg_dot_obj },
{ MP_ROM_QSTR(MP_QSTR_zeros), (mp_obj_t)&linalg_zeros_obj },
{ MP_ROM_QSTR(MP_QSTR_ones), (mp_obj_t)&linalg_ones_obj },
{ MP_ROM_QSTR(MP_QSTR_eye), (mp_obj_t)&linalg_eye_obj },
{ MP_ROM_QSTR(MP_QSTR_det), (mp_obj_t)&linalg_det_obj },
{ MP_ROM_QSTR(MP_QSTR_eig), (mp_obj_t)&linalg_eig_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vectorise_acosh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vectorise_asin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vectorise_asinh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vectorise_atan_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vectorise_atanh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vectorise_ceil_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vectorise_cos_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vectorise_erf_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vectorise_erfc_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vectorise_exp_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vectorise_expm1_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vectorise_floor_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vectorise_gamma_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_lgamma), (mp_obj_t)&vectorise_lgamma_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vectorise_log_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vectorise_log10_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vectorise_log2_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vectorise_sin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vectorise_sinh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vectorise_sqrt_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vectorise_tan_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_linspace), (mp_obj_t)&numerical_linspace_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sum), (mp_obj_t)&numerical_sum_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&numerical_mean_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_std), (mp_obj_t)&numerical_std_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_min), (mp_obj_t)&numerical_min_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_max), (mp_obj_t)&numerical_max_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argmin), (mp_obj_t)&numerical_argmin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argmax), (mp_obj_t)&numerical_argmax_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_roll), (mp_obj_t)&numerical_roll_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_flip), (mp_obj_t)&numerical_flip_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_diff), (mp_obj_t)&numerical_diff_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sort), (mp_obj_t)&numerical_sort_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_argsort), (mp_obj_t)&numerical_argsort_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_polyval), (mp_obj_t)&poly_polyval_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_polyfit), (mp_obj_t)&poly_polyfit_obj },
{ 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_spectrum), (mp_obj_t)&fft_spectrum_obj },
{ MP_ROM_QSTR(MP_QSTR_zeros), (mp_obj_t)&create_zeros_obj },
{ MP_ROM_QSTR(MP_QSTR_ones), (mp_obj_t)&create_ones_obj },
{ MP_ROM_QSTR(MP_QSTR_eye), (mp_obj_t)&create_eye_obj },
{ MP_ROM_QSTR(MP_QSTR_linspace), (mp_obj_t)&create_linspace_obj },
#if ULAB_LINALG_MODULE
{ MP_ROM_QSTR(MP_QSTR_linalg), MP_ROM_PTR(&ulab_linalg_module) },
#endif
#if ULAB_VECTORISE_MODULE
{ MP_ROM_QSTR(MP_QSTR_vector), MP_ROM_PTR(&ulab_vectorise_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_FFT_MODULE
{ MP_ROM_QSTR(MP_QSTR_fft), MP_ROM_PTR(&ulab_fft_module) },
#endif
#if ULAB_FILTER_MODULE
{ MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&ulab_filter_module) },
#endif
#if ULAB_EXTRAS_MODULE
{ MP_ROM_QSTR(MP_QSTR_extras), MP_ROM_PTR(&ulab_extras_module) },
#endif
// class constants
{ MP_ROM_QSTR(MP_QSTR_uint8), MP_ROM_INT(NDARRAY_UINT8) },
{ MP_ROM_QSTR(MP_QSTR_int8), MP_ROM_INT(NDARRAY_INT8) },
@ -185,7 +103,7 @@ STATIC MP_DEFINE_CONST_DICT (
ulab_globals_table
);
const mp_obj_module_t ulab_user_cmodule = {
mp_obj_module_t ulab_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_globals,
};

39
code/ulab.h Normal file
View file

@ -0,0 +1,39 @@
/*
* This file is part of the micropython-ulab project,
*
* https://github.com/v923z/micropython-ulab
*
* The MIT License (MIT)
*
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef __ULAB__
#define __ULAB__
// create
#define ULAB_CREATE_MODULE (1)
// vectorise (all functions) takes approx. 3 kB of flash space
#define ULAB_VECTORISE_MODULE (1)
// linalg adds around 6 kB
#define ULAB_LINALG_MODULE (1)
// poly is approx. 2.5 kB
#define ULAB_POLY_MODULE (1)
// numerical is about 12 kB
#define ULAB_NUMERICAL_MODULE (1)
// FFT costs about 2 kB of flash space
#define ULAB_FFT_MODULE (1)
// the filter module takes about 1 kB of flash space
#define ULAB_FILTER_MODULE (1)
// user-defined modules
#define ULAB_EXTRAS_MODULE (0)
#endif

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,9 +6,9 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@ -21,9 +22,10 @@
#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846)
#endif
#if ULAB_VECTORISE_MODULE
mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) {
// Return a single value, if o_in is not iterable
if(mp_obj_is_float(o_in) || mp_obj_is_integer(o_in)) {
if(mp_obj_is_float(o_in) || MP_OBJ_IS_INT(o_in)) {
return mp_obj_new_float(f(mp_obj_get_float(o_in)));
}
mp_float_t x;
@ -60,26 +62,111 @@ mp_obj_t vectorise_generic_vector(mp_obj_t o_in, mp_float_t (*f)(mp_float_t)) {
return mp_const_none;
}
MATH_FUN_1(acos, acos);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acos_obj, vectorise_acos);
MATH_FUN_1(acosh, acosh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_acosh_obj, vectorise_acosh);
MATH_FUN_1(asin, asin);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asin_obj, vectorise_asin);
MATH_FUN_1(asinh, asinh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_asinh_obj, vectorise_asinh);
MATH_FUN_1(atan, atan);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atan_obj, vectorise_atan);
MATH_FUN_1(atanh, atanh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_atanh_obj, vectorise_atanh);
MATH_FUN_1(ceil, ceil);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_ceil_obj, vectorise_ceil);
MATH_FUN_1(cos, cos);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cos_obj, vectorise_cos);
MATH_FUN_1(cosh, cosh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_cosh_obj, vectorise_cosh);
MATH_FUN_1(erf, erf);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erf_obj, vectorise_erf);
MATH_FUN_1(erfc, erfc);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_erfc_obj, vectorise_erfc);
MATH_FUN_1(exp, exp);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_exp_obj, vectorise_exp);
MATH_FUN_1(expm1, expm1);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_expm1_obj, vectorise_expm1);
MATH_FUN_1(floor, floor);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_floor_obj, vectorise_floor);
MATH_FUN_1(gamma, tgamma);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_gamma_obj, vectorise_gamma);
MATH_FUN_1(lgamma, lgamma);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_lgamma_obj, vectorise_lgamma);
MATH_FUN_1(log, log);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log_obj, vectorise_log);
MATH_FUN_1(log10, log10);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log10_obj, vectorise_log10);
MATH_FUN_1(log2, log2);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_log2_obj, vectorise_log2);
MATH_FUN_1(sin, sin);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sin_obj, vectorise_sin);
MATH_FUN_1(sinh, sinh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sinh_obj, vectorise_sinh);
MATH_FUN_1(sqrt, sqrt);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_sqrt_obj, vectorise_sqrt);
MATH_FUN_1(tan, tan);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tan_obj, vectorise_tan);
MATH_FUN_1(tanh, tanh);
MP_DEFINE_CONST_FUN_OBJ_1(vectorise_tanh_obj, vectorise_tanh);
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) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_acos), (mp_obj_t)&vectorise_acos_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_acosh), (mp_obj_t)&vectorise_acosh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_asin), (mp_obj_t)&vectorise_asin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_asinh), (mp_obj_t)&vectorise_asinh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_atan), (mp_obj_t)&vectorise_atan_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_atanh), (mp_obj_t)&vectorise_atanh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_ceil), (mp_obj_t)&vectorise_ceil_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_cos), (mp_obj_t)&vectorise_cos_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_erf), (mp_obj_t)&vectorise_erf_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_erfc), (mp_obj_t)&vectorise_erfc_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_exp), (mp_obj_t)&vectorise_exp_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_expm1), (mp_obj_t)&vectorise_expm1_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_floor), (mp_obj_t)&vectorise_floor_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_gamma), (mp_obj_t)&vectorise_gamma_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_lgamma), (mp_obj_t)&vectorise_lgamma_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log), (mp_obj_t)&vectorise_log_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log10), (mp_obj_t)&vectorise_log10_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_log2), (mp_obj_t)&vectorise_log2_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sin), (mp_obj_t)&vectorise_sin_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sinh), (mp_obj_t)&vectorise_sinh_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_sqrt), (mp_obj_t)&vectorise_sqrt_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_tan), (mp_obj_t)&vectorise_tan_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_tanh), (mp_obj_t)&vectorise_tanh_obj },
};
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

View file

@ -1,3 +1,4 @@
/*
* This file is part of the micropython-ulab project,
*
@ -5,37 +6,18 @@
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Zoltán Vörös
* Copyright (c) 2019-2020 Zoltán Vörös
*/
#ifndef _VECTORISE_
#define _VECTORISE_
#include "ulab.h"
#include "ndarray.h"
mp_obj_t vectorise_acos(mp_obj_t );
mp_obj_t vectorise_acosh(mp_obj_t );
mp_obj_t vectorise_asin(mp_obj_t );
mp_obj_t vectorise_asinh(mp_obj_t );
mp_obj_t vectorise_atan(mp_obj_t );
mp_obj_t vectorise_atanh(mp_obj_t );
mp_obj_t vectorise_ceil(mp_obj_t );
mp_obj_t vectorise_cos(mp_obj_t );
mp_obj_t vectorise_erf(mp_obj_t );
mp_obj_t vectorise_erfc(mp_obj_t );
mp_obj_t vectorise_exp(mp_obj_t );
mp_obj_t vectorise_expm1(mp_obj_t );
mp_obj_t vectorise_floor(mp_obj_t );
mp_obj_t vectorise_gamma(mp_obj_t );
mp_obj_t vectorise_lgamma(mp_obj_t );
mp_obj_t vectorise_log(mp_obj_t );
mp_obj_t vectorise_log10(mp_obj_t );
mp_obj_t vectorise_log2(mp_obj_t );
mp_obj_t vectorise_sin(mp_obj_t );
mp_obj_t vectorise_sinh(mp_obj_t );
mp_obj_t vectorise_sqrt(mp_obj_t );
mp_obj_t vectorise_tan(mp_obj_t );
mp_obj_t vectorise_tanh(mp_obj_t );
#if ULAB_VECTORISE_MODULE
mp_obj_module_t ulab_vectorise_module;
#define ITERATE_VECTOR(type, source, out) do {\
type *input = (type *)(source)->array->items;\
@ -47,6 +29,7 @@ mp_obj_t vectorise_tanh(mp_obj_t );
#define MATH_FUN_1(py_name, c_name) \
mp_obj_t vectorise_ ## py_name(mp_obj_t x_obj) { \
return vectorise_generic_vector(x_obj, MICROPY_FLOAT_C_FUN(c_name)); \
}
}
#endif
#endif

View file

@ -22,7 +22,7 @@ copyright = '2019, Zoltán Vörös'
author = 'Zoltán Vörös'
# The full version, including alpha/beta/rc tags
release = '0.26'
release = '0.32.2'
# -- General configuration ---------------------------------------------------

View file

@ -1,19 +1,19 @@
Introduction
============
In
https://micropython-usermod.readthedocs.io/en/latest/usermods_14.html, I
mentioned that I have another story, for another day. The day has come,
so here is my story.
In the `last
chapter <https://micropython-usermod.readthedocs.io/en/latest/usermods_15.html>`__
of the usermod documentation, I mentioned that I have another story, for
another day. The day has come, so here is my story.
Enter ulab
----------
``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 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.
Purpose
-------
@ -27,8 +27,9 @@ microcontroller, the data volume is probably small, but it might lead to
catastrophic system failure, if these data are not processed in time,
because the microcontroller is supposed to interact with the outside
world in a timely fashion. In fact, this latter objective was the
initiator of this project: I needed the Fourier transform of the ADC
signal, and all the available options were simply too slow.
initiator of this project: I needed the Fourier transform of a signal
coming from the ADC of the pyboard, and all available options were
simply too slow.
In addition to speed, another issue that one has to keep in mind when
working with embedded systems is the amount of available RAM: I believe,
@ -42,15 +43,15 @@ matter, whether they are all smaller than 100, or larger than one
hundred million. This is obviously a waste of resources in an
environment, where resources are scarce.
Finally, there is a reason for using micropython in the first place.
Finally, there is a reason for using ``micropython`` in the first place.
Namely, that a microcontroller can be programmed in a very elegant, and
*pythonic* way. But if it is so, why should we not extend this idea to
other tasks and concepts that might come up in this context? If there
was no other reason than this *elegance*, I would find that convincing
enough.
Based on the above-mentioned considerations, all functions are
implemented in a way that
Based on the above-mentioned considerations, all functions in ``ulab``
are implemented in a way that
1. conforms to ``numpy`` as much as possible
2. is so frugal with RAM as possible,
@ -62,15 +63,17 @@ The main points of ``ulab`` are
2 dimensions (arrays and matrices). These containers support all the
relevant unary and binary operators (e.g., ``len``, ==, +, \*, etc.)
- vectorised computations on micropython iterables and numerical
arrays/matrices (in numpy-speak, universal functions)
arrays/matrices (in ``numpy``-speak, universal functions)
- basic linear algebra routines (matrix inversion, multiplication,
reshaping, transposition, determinant, and eigenvalues)
- polynomial fits to numerical data
- fast Fourier transforms
At the time of writing this manual (for version 0.26), the library adds
approximately 30 kB of extra compiled code to the micropython
(pyboard.v.11) firmware.
At the time of writing this manual (for version 0.33.2), the library
adds approximately 30 kB of extra compiled code to the micropython
(pyboard.v.11) firmware. However, if you are tight with flash space, you
can easily shave off a couple of kB. See the section on `customising
ulab <#Custom_builds>`__.
Resources and legal matters
---------------------------
@ -95,8 +98,9 @@ Friendly request
If you use ``ulab``, and bump into a bug, or think that a particular
function is missing, or its behaviour does not conform to ``numpy``,
please, raise an issue on github, so that the community can profit from
your experiences.
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
@ -108,6 +112,89 @@ These last comments apply to the documentation, too. If, in your
opinion, the documentation is obscure, misleading, or not detailed
enough, please, let me know, so that *we* can fix it.
Differences between micropython-ulab and circuitpython-ulab
-----------------------------------------------------------
``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>`__.
There are, however, a couple of instances, where the usage in the two
environments is slightly different at the python level. These are how
the packges can be imported, and how the class properties can be
accessed. In both cases, the ``circuitpython`` implementation results in
``numpy``-conform code. ``numpy``-compatibility in ``micropython`` will
be implemented as soon as ``micropython`` itself has the required tools.
Till then we have to live with a workaround, which I will point out at
the relevant places.
Customising ``ulab``
====================
``ulab`` implements a great number of functions, which are organised in
sub-modules. E.g., functions related to Fourier transforms are located
in the ``ulab.fft`` sub-module, so you would import ``fft`` as
.. code:: python
import ulab
from ulab import fft
by which point you can get the FFT of your data by calling
``fft.fft(...)``.
The idea of such grouping of functions and methods is to provide a means
for granularity: It is quite possible that you do not need all functions
in a particular application. If you want to save some flash space, you
can easily exclude arbitrary sub-modules from the firmware. The
`ulab.h <https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h>`__
header file contains a pre-processor flag for each sub-module. The
default setting is 1 for each of them. Setting them to 0 removes the
module from the compiled firmware.
The first couple of lines of the file look like this
.. code:: c
// vectorise (all functions) takes approx. 3 kB of flash space
#define ULAB_VECTORISE_MODULE (1)
// linalg adds around 6 kB
#define ULAB_LINALG_MODULE (1)
// poly is approx. 2.5 kB
#define ULAB_POLY_MODULE (1)
In order to simplify navigation in the header, each flag begins with
``ULAB_``, and continues with the name of the sub-module. This name is
also the ``.c`` file, where the sub-module is implemented. So, e.g., the
linear algebra routines can be found in ``linalg.c``, and the
corresponding compiler flag is ``ULAB_LINALG_MODULE``. Each section
displays a hint as to how much space you can save by un-setting the
flag.
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.
Except for ``fft``, the standard sub-modules, ``vector``, ``linalg``,
``numerical``, ``and poly``\ all ``numpy``-compatible. User-defined
functions that accept ``ndarray``\ s as their argument should be
implemented in the ``extra`` sub-module, or its sub-modules. Hints as to
how to do that can be found in the section `Extending
ulab <#Extending-ulab>`__.
Supported functions and methods
===============================
@ -162,23 +249,21 @@ calls on general iterables)
Methods of ndarrays
-------------------
`.shape <#.shape>`__
`.shape\* <#.shape>`__
`size\* <#size>`__
`itemsize\* <#itemsize>`__
`.reshape <#.reshape>`__
`.rawsize\*\* <#.rawsize>`__
`.transpose <#.transpose>`__
`.flatten\*\* <#.flatten>`__
`.asbytearray <#.asbytearray>`__
Matrix methods
--------------
`size <#size>`__
`inv <#inv>`__
`dot <#dot>`__
@ -239,14 +324,20 @@ FFT routines
`spectrum\*\* <#spectrum>`__
Filter functions
----------------
`convolve <#convolve>`__
ndarray, the basic container
============================
The ``ndarray`` is the underlying container of numerical data. It is
derived from micropythons own ``array`` object, but has a great number
of extra features starting with how it can be initialised, how
of extra features starting with how it can be initialised, which
operations can be done on it, and which functions can accept it as an
argument.
argument. One important property of an ``ndarray`` is that it is also a
proper ``micropython`` iterable.
Since the ``ndarray`` is a binary container, it is also compact, meaning
that it takes only a couple of bytes of extra RAM in addition to what is
@ -260,7 +351,7 @@ 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
float your particular platform implements by looking at the output of
the `.rawsize <#.rawsize>`__ class method.
the `.itemsize <#.itemsize>`__ class property.
On the following pages, we will see how one can work with
``ndarray``\ s. Those familiar with ``numpy`` should find that the
@ -268,7 +359,7 @@ nomenclature and naming conventions of ``numpy`` are adhered to as
closely as possible. I will point out the few differences, where
necessary.
For the sake of comparison, in addition to ``ulab`` code snippets,
For the sake of comparison, in addition to the ``ulab`` code snippets,
sometimes the equivalent ``numpy`` code is also presented. You can find
out, where the snippet is supposed to run by looking at its first line,
the header.
@ -392,8 +483,11 @@ Methods of ndarrays
.shape
~~~~~~
The ``.shape`` method returns a 2-tuple with the number of rows, and
columns.
The ``.shape`` method (property) returns a 2-tuple with the number of
rows, and columns.
**WARNING:** In ``circuitpython``, you can call the method as a
property, i.e.,
.. code::
@ -403,7 +497,38 @@ columns.
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)
print("shape of b:", b.shape)
.. parsed-literal::
a:
array([1, 2, 3, 4], dtype=int8)
shape of a: (1, 4)
b:
array([[1, 2],
[3, 4]], dtype=int8)
shape of b: (2, 2)
**WARNING:** On the other hand, since properties are not implemented in
``micropython``, there you would call the method as a function, i.e.,
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3, 4], dtype=np.int8)
print("a:\n", a)
print("shape of a:", a.shape)
b= np.array([[1, 2], [3, 4]], dtype=np.int8)
print("\nb:\n", b)
@ -423,6 +548,139 @@ columns.
.size
~~~~~
The ``.size`` method (property) returns an integer with the number of
elements in the array.
**WARNING:** In ``circuitpython``, the ``numpy`` nomenclature applies,
i.e.,
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3], dtype=np.int8)
print("a:\n", a)
print("size of a:", a.size)
b= np.array([[1, 2], [3, 4]], dtype=np.int8)
print("\nb:\n", b)
print("size of b:", b.size)
.. parsed-literal::
a:
array([1, 2, 3], dtype=int8)
size of a: 3
b:
array([[1, 2],
[3, 4]], dtype=int8)
size of b: 4
**WARNING:** In ``micropython``, ``size`` is a method, i.e.,
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3], dtype=np.int8)
print("a:\n", a)
print("size of a:", a.size)
b= np.array([[1, 2], [3, 4]], dtype=np.int8)
print("\nb:\n", b)
print("size of b:", b.size())
.. parsed-literal::
a:
array([1, 2, 3], dtype=int8)
size of a: 3
b:
array([[1, 2],
[3, 4]], dtype=int8)
size of b: 4
.itemsize
~~~~~~~~~
The ``.itemsize`` method (property) returns an integer with the siz
enumber of elements in the array.
**WARNING:** In ``circuitpython``:
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3], dtype=np.int8)
print("a:\n", a)
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)
.. parsed-literal::
a:
array([1, 2, 3], dtype=int8)
itemsize of a: 1
b:
array([[1.0, 2.0],
[3.0, 4.0]], dtype=float)
itemsize of b: 8
**WARNING:** In ``micropython``:
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3], dtype=np.int8)
print("a:\n", a)
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())
.. parsed-literal::
a:
array([1, 2, 3], dtype=int8)
itemsize of a: 1
b:
array([[1.0, 2.0],
[3.0, 4.0]], dtype=float)
itemsize of b: 8
.reshape
~~~~~~~~
@ -459,41 +717,6 @@ consistent with the old, a ``ValueError`` exception will be raised.
.rawsize
~~~~~~~~
The ``rawsize`` method of the ``ndarray`` returns a 5-tuple with the
following data
1. number of rows
2. number of columns
3. length of the storage (should be equal to the product of 1. and 2.)
4. length of the data storage in bytes
5. datum size in bytes (1 for ``uint8``/``int8``, 2 for
``uint16``/``int16``, and 4, or 8 for ``floats``, see `ndarray, the
basic container <#ndarray,-the-basic-container>`__)
**WARNING:** ``rawsize`` is a ``ulab``-only method; it has no equivalent
in ``numpy``.
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3, 4], dtype=np.float)
print("a: \t\t", a)
print("rawsize of a: \t", a.rawsize())
.. parsed-literal::
a: array([1.0, 2.0, 3.0, 4.0], dtype=float)
rawsize of a: (1, 4, 4, 16, 4)
.flatten
~~~~~~~~
@ -535,83 +758,6 @@ verbatim copy of the contents.
.asbytearray
~~~~~~~~~~~~
The contents of an ``ndarray`` can be accessed directly by calling the
``.asbytearray`` method. This will simply return a pointer to the
underlying flat ``array`` object, which can then be manipulated
directly.
**WARNING:** ``asbytearray`` is a ``ulab``-only method; it has no
equivalent in ``numpy``.
In the example below, note the difference between ``a``, and ``buffer``:
while both are designated as an array, you recognise the micropython
array from the fact that it prints the typecode (``b`` in this
particular case). The ``ndarray``, on the other hand, prints out the
``dtype`` (``int8`` here).
.. code::
# code to be run in micropython
import ulab as np
a = np.array([1, 2, 3, 4], dtype=np.int8)
print('a: ', a)
buffer = a.asbytearray()
print("array content:", buffer)
buffer[1] = 123
print("array content:", buffer)
.. parsed-literal::
a: array([1, 2, 3, 4], dtype=int8)
array content: array('b', [1, 2, 3, 4])
array content: array('b', [1, 123, 3, 4])
This in itself wouldnt be very interesting, but since ``buffer`` is a
proper micropython ``array``, we can pass it to functions that can
employ the buffer protocol. E.g., all the ``ndarray`` facilities can be
applied to the results of timed ADC conversions.
.. code::
# code to be run in micropython
import pyb
import ulab as np
n = 100
adc = pyb.ADC(pyb.Pin.board.X19)
tim = pyb.Timer(6, freq=10)
a = np.array([0]*n, dtype=np.uint8)
buffer = a.asbytearray()
adc.read_timed(buffer, tim)
print("ADC results:\t", a)
print("mean of results:\t", np.mean(a))
print("std of results:\t", np.std(a))
.. parsed-literal::
ADC results: array([48, 2, 2, ..., 0, 0, 0], dtype=uint8)
mean of results: 1.22
std of results: 4.744639
Likewise, data can be read directly into ``ndarray``\ s from other
interfaces, e.g., SPI, I2C etc, and also, by laying bare the
``ndarray``, we can pass results of ``ulab`` computations to anything
that can read from a buffer.
.transpose
~~~~~~~~~~
@ -2786,28 +2932,32 @@ parts of the transform separately.
# code to be run in micropython
import ulab as np
from ulab import numerical
from ulab import vector
from ulab import fft
from ulab import linalg
x = np.linspace(0, 10, num=1024)
y = np.sin(x)
z = np.zeros(len(x))
x = numerical.linspace(0, 10, num=1024)
y = vector.sin(x)
z = linalg.zeros(len(x))
a, b = np.fft(x)
a, b = fft.fft(x)
print('real part:\t', a)
print('\nimaginary part:\t', b)
c, d = np.fft(x, z)
c, d = fft.fft(x, z)
print('\nreal part:\t', c)
print('\nimaginary part:\t', d)
.. parsed-literal::
real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)
imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)
real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)
imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)
real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)
imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)
real part: array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)
imaginary part: array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)
@ -2989,10 +3139,48 @@ Y_2(k) &=& -\frac{i}{2}\left(Y(k) - Y^*(N-k)\right)
:math:`Y_1, Y_2`, and :math:`Y`, respectively, are the Fourier
transforms of :math:`y_1, y_2`, and :math:`y = y_1 + iy_2`.
Filter routines
===============
numpy:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html
convolve
--------
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
x = np.array((1,2,3))
y = np.array((1,10,100,1000))
print(np.convolve(x, y))
.. parsed-literal::
array([1.0, 12.0, 123.0, 1230.0, 2300.0, 3000.0], dtype=float)
Extending ulab
==============
New functions can easily be added to ``ulab`` in a couple of simple
As mentioned at the beginning, ``ulab`` is a set of sub-modules, out of
which one, ``extra`` is explicitly reserved for user code. You should
implement your functions in this sub-module, or sub-modules thereof.
The new functions can easily be added to ``extra`` in a couple of simple
steps. At the C level, the type definition of an ``ndarray`` is as
follows:
@ -3042,7 +3230,7 @@ or
The ambiguity is caused by the fact that not all platforms implement
``double``, and there one has to take ``float``\ s. But you havent
actually got to be concerned by this, because at the very beginning of
``ndarray.h``, this is already taken care of: the preprocessor figures
``ndarray.h``, this is already taken care of: the pre-processor figures
out, what the ``float`` implementation of the hardware platform is, and
defines the ``NDARRAY_FLOAT`` typecode accordingly. All you have to keep
in mind is that wherever you would use ``float`` or ``double``, you have
@ -3219,47 +3407,15 @@ and return that, you could work along the following lines:
return MP_PTR_TO_OBJ(ndarray);
}
In the boilerplate above, we cast the pointer to ``array->items`` to the
required type. There are certain operations, however, when you do not
need the casting. If you do not want to change the arrays values, only
their position within the array, you can get away with copying the
memory content, regardless the type. A good example for such a scenario
is the transpose function in
https://github.com/v923z/micropython-ulab/blob/master/code/linalg.c.
Compiling your module
---------------------
Once you have implemented the functionality you wanted, you have to
include the source code in the make file by adding it to
``micropython.mk``:
.. code:: makefile
USERMODULES_DIR := $(USERMOD_DIR)
# Add all C files to SRC_USERMOD.
SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c
SRC_USERMOD += $(USERMODULES_DIR)/linalg.c
SRC_USERMOD += $(USERMODULES_DIR)/vectorise.c
SRC_USERMOD += $(USERMODULES_DIR)/poly.c
SRC_USERMOD += $(USERMODULES_DIR)/fft.c
SRC_USERMOD += $(USERMODULES_DIR)/numerical.c
SRC_USERMOD += $(USERMODULES_DIR)/ulab.c
SRC_USERMOD += $(USERMODULES_DIR)/your_module.c
CFLAGS_USERMOD += -I$(USERMODULES_DIR)
In addition, you also have to add the function objects to ``ulab.c``,
and create a ``QString`` for the function name:
Once the function implementation is done, you should add the function
object to the globals dictionary of the ``extra`` sub-module as
.. code:: c
...
MP_DEFINE_CONST_FUN_OBJ_1(useless_function_obj, userless_function);
...
STATIC const mp_map_elem_t ulab_globals_table[] = {
STATIC const mp_map_elem_t extra_globals_table[] = {
...
{ MP_OBJ_NEW_QSTR(MP_QSTR_useless), (mp_obj_t)&useless_function_obj },
...
@ -3277,24 +3433,18 @@ and so on. For a thorough discussion on how function objects have to be
defined, see, e.g., the `user module programming
manual <#https://micropython-usermod.readthedocs.io/en/latest/>`__.
At this point, you should be able to compile the module with your
extension by running ``make`` on the command line
And with that, you are done. You can simply call the compiler as
.. code:: bash
make USER_C_MODULES=../../../ulab all
make BOARD=PYBV11 USER_C_MODULES=../../../ulab all
for the unix port, and
and the rest is taken care of.
.. code:: bash
make BOARD=PYBV11 CROSS_COMPILE=<arm_tools_path>/bin/arm-none-eabi- USER_C_MODULES=../../../ulab all
for the ``pyboard``, provided that the you have defined
.. code:: makefile
#define MODULE_ULAB_ENABLED (1)
somewhere in ``micropython/port/unix/mpconfigport.h``, or
``micropython/stm32/mpconfigport.h``, respectively.
In the boilerplate above, we cast the pointer to ``array->items`` to the
required type. There are certain operations, however, when you do not
need the casting. If you do not want to change the arrays values, only
their position within the array, you can get away with copying the
memory content, regardless the type. A good example for such a scenario
is the transpose function in
https://github.com/v923z/micropython-ulab/blob/master/code/linalg.c.

View file

@ -1,3 +1,104 @@
Thu, 27 Feb 2020
version 0.36.0
moved zeros, ones, eye and linspace into separate module (they are still bound at the top level)
Thu, 27 Feb 2020
version 0.35.0
Move zeros, ones back into top level ulab module
Tue, 18 Feb 2020
version 0.34.0
split ulab into multiple modules
Sun, 16 Feb 2020
version 0.33.2
moved properties into ndarray_properties.h, implemented pointer arithmetic in fft.c to save some time
Fri, 14 Feb 2020
version 0.33.1
added the __name__attribute to all sub-modules
Thu, 13 Feb 2020
version 0.33.0
sub-modules are now proper sub-modules of ulab
Mon, 17 Feb 2020
version 0.32.1
temporary fix for issue #40
Tue, 11 Feb 2020
version 0.32.0
added itemsize, size and shape attributes to ndarrays, and removed rawsize
Mon, 10 Feb 2020
version 0.31.0
removed asbytearray, and added buffer protocol to ndarrays, fixed bad error in filter.c
Sun, 09 Feb 2020
version 0.30.2
fixed slice_length in ndarray.c
Sat, 08 Feb 2020
version 0.30.1
fixed typecode error, added variable inspection, and replaced ternary operators in filter.c
Fri, 07 Feb 2020
version 0.30.0
ulab functions can arbitrarily be excluded from the firmware via the ulab.h configuration file
Thu, 06 Feb 2020
version 0.27.0
add convolve, the start of a 'filter' functionality group
Wed, 29 Jan 2020
version 0.26.7
fixed indexing error in linalg.dot
Mon, 20 Jan 2020
version 0.26.6
replaced MP_ROM_PTR(&mp_const_none_obj), so that module can be compiled for the nucleo board
Tue, 7 Jan 2020
version 0.26.5
fixed glitch in numerical.c, numerical.h
Mon, 6 Jan 2020
version 0.26.4
switched version constant to string
Tue, 31 Dec 2019

View file

@ -24,11 +24,11 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2019-11-05T16:01:28.817558Z",
"start_time": "2019-11-05T16:01:28.810704Z"
"end_time": "2020-02-26T17:04:16.562607Z",
"start_time": "2020-02-26T17:04:16.502531Z"
}
},
"outputs": [
@ -66,7 +66,7 @@
"author = 'Zoltán Vörös'\n",
"\n",
"# The full version, including alpha/beta/rc tags\n",
"release = '0.26'\n",
"release = '0.32.2'\n",
"\n",
"\n",
"# -- General configuration ---------------------------------------------------\n",
@ -120,10 +120,11 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"start_time": "2019-11-06T17:37:29.723Z"
"end_time": "2020-02-26T17:04:49.527515Z",
"start_time": "2020-02-26T17:04:21.416456Z"
}
},
"outputs": [],
@ -292,11 +293,11 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 17,
"metadata": {
"ExecuteTime": {
"end_time": "2019-11-04T20:57:36.730820Z",
"start_time": "2019-11-04T20:57:36.723446Z"
"end_time": "2020-02-16T14:53:49.098172Z",
"start_time": "2020-02-16T14:53:49.093201Z"
}
},
"outputs": [],
@ -310,11 +311,11 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": 18,
"metadata": {
"ExecuteTime": {
"end_time": "2019-11-04T20:57:38.576560Z",
"start_time": "2019-11-04T20:57:38.521927Z"
"end_time": "2020-02-16T14:53:53.396267Z",
"start_time": "2020-02-16T14:53:53.375754Z"
}
},
"outputs": [],
@ -383,13 +384,20 @@
"ip.register_magics(PyboardMagic)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## pyboard"
]
},
{
"cell_type": "code",
"execution_count": 520,
"execution_count": 111,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-20T06:48:01.610725Z",
"start_time": "2019-10-20T06:48:00.856261Z"
"end_time": "2020-02-16T18:36:59.172039Z",
"start_time": "2020-02-16T18:36:59.144651Z"
}
},
"outputs": [],
@ -401,11 +409,11 @@
},
{
"cell_type": "code",
"execution_count": 501,
"execution_count": 115,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-19T13:36:42.010602Z",
"start_time": "2019-10-19T13:36:42.003900Z"
"end_time": "2020-02-16T18:50:42.907664Z",
"start_time": "2020-02-16T18:50:42.903709Z"
}
},
"outputs": [],
@ -477,21 +485,21 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In https://micropython-usermod.readthedocs.io/en/latest/usermods_14.html, I mentioned that I have another story, for another day. The day has come, so here is my story.\n",
"In the [last chapter](https://micropython-usermod.readthedocs.io/en/latest/usermods_15.html) of the usermod documentation, I mentioned that I have another story, for another day. The day has come, so here is my story.\n",
"\n",
"## Enter ulab\n",
"\n",
"`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.\n",
"`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.\n",
"\n",
"## Purpose\n",
"\n",
"Of course, the first question that one has to answer is, why on Earth one would need a fast math library on a microcontroller. After all, it is not expected that heavy number crunching is going to take place on bare metal. It is not meant to. On a PC, the main reason for writing fast code is the sheer amount of data that one wants to process. On a microcontroller, the data volume is probably small, but it might lead to catastrophic system failure, if these data are not processed in time, because the microcontroller is supposed to interact with the outside world in a timely fashion. In fact, this latter objective was the initiator of this project: I needed the Fourier transform of the ADC signal, and all the available options were simply too slow. \n",
"Of course, the first question that one has to answer is, why on Earth one would need a fast math library on a microcontroller. After all, it is not expected that heavy number crunching is going to take place on bare metal. It is not meant to. On a PC, the main reason for writing fast code is the sheer amount of data that one wants to process. On a microcontroller, the data volume is probably small, but it might lead to catastrophic system failure, if these data are not processed in time, because the microcontroller is supposed to interact with the outside world in a timely fashion. In fact, this latter objective was the initiator of this project: I needed the Fourier transform of a signal coming from the ADC of the pyboard, and all available options were simply too slow. \n",
"\n",
"In addition to speed, another issue that one has to keep in mind when working with embedded systems is the amount of available RAM: I believe, everything here could be implemented in pure python with relatively little effort, but the price we would have to pay for that is not only speed, but RAM, too. python code, if is not frozen, and compiled into the firmware, has to be compiled at runtime, which is not exactly a cheap process. On top of that, if numbers are stored in a list or tuple, which would be the high-level container, then they occupy 8 bytes, no matter, whether they are all smaller than 100, or larger than one hundred million. This is obviously a waste of resources in an environment, where resources are scarce. \n",
"\n",
"Finally, there is a reason for using micropython in the first place. Namely, that a microcontroller can be programmed in a very elegant, and *pythonic* way. But if it is so, why should we not extend this idea to other tasks and concepts that might come up in this context? If there was no other reason than this *elegance*, I would find that convincing enough.\n",
"Finally, there is a reason for using `micropython` in the first place. Namely, that a microcontroller can be programmed in a very elegant, and *pythonic* way. But if it is so, why should we not extend this idea to other tasks and concepts that might come up in this context? If there was no other reason than this *elegance*, I would find that convincing enough.\n",
"\n",
"Based on the above-mentioned considerations, all functions are implemented in a way that \n",
"Based on the above-mentioned considerations, all functions in `ulab` are implemented in a way that \n",
"\n",
"1. conforms to `numpy` as much as possible\n",
"2. is so frugal with RAM as possible,\n",
@ -500,12 +508,12 @@
"The main points of `ulab` are \n",
"\n",
"- compact, iterable and slicable containers of numerical data in 1, and 2 dimensions (arrays and matrices). These containers support all the relevant unary and binary operators (e.g., `len`, ==, +, *, etc.)\n",
"- vectorised computations on micropython iterables and numerical arrays/matrices (in numpy-speak, universal functions)\n",
"- vectorised computations on micropython iterables and numerical arrays/matrices (in `numpy`-speak, universal functions)\n",
"- basic linear algebra routines (matrix inversion, multiplication, reshaping, transposition, determinant, and eigenvalues)\n",
"- polynomial fits to numerical data\n",
"- fast Fourier transforms\n",
"\n",
"At the time of writing this manual (for version 0.26), the library adds approximately 30 kB of extra compiled code to the micropython (pyboard.v.11) firmware. \n",
"At the time of writing this manual (for version 0.33.2), the library adds approximately 30 kB of extra compiled code to the micropython (pyboard.v.11) firmware. However, if you are tight with flash space, you can easily shave off a couple of kB. See the section on [customising ulab](#Custom_builds).\n",
"\n",
"## Resources and legal matters\n",
"\n",
@ -515,11 +523,53 @@
"\n",
"## Friendly request\n",
"\n",
"If you use `ulab`, and bump into a bug, or think that a particular function is missing, or its behaviour does not conform to `numpy`, please, raise an issue on github, so that the community can profit from your experiences. \n",
"If you use `ulab`, and bump into a bug, or think that a particular function is missing, or its behaviour does not conform to `numpy`, please, raise a [ulab issue](#https://github.com/v923z/micropython-ulab/issues) on github, so that the community can profit from your experiences. \n",
"\n",
"Even better, if you find the project 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.\n",
"\n",
"These last comments apply to the documentation, too. If, in your opinion, the documentation is obscure, misleading, or not detailed enough, please, let me know, so that *we* can fix it."
"These last comments apply to the documentation, too. If, in your opinion, the documentation is obscure, misleading, or not detailed enough, please, let me know, so that *we* can fix it.\n",
"\n",
"## Differences between micropython-ulab and circuitpython-ulab\n",
"\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 slightly different at the python level. These are how the packges can be imported, and how the class properties can be accessed. In both cases, the `circuitpython` implementation results in `numpy`-conform code. `numpy`-compatibility in `micropython` will be implemented as soon as `micropython` itself has the required tools. Till then we have to live with a workaround, which I will point out at the relevant places."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Customising `ulab`\n",
"\n",
"`ulab` implements a great number of functions, which are organised in sub-modules. E.g., functions related to Fourier transforms are located in the `ulab.fft` sub-module, so you would import `fft` as\n",
"\n",
"```python\n",
"import ulab\n",
"from ulab import fft\n",
"```\n",
"by which point you can get the FFT of your data by calling `fft.fft(...)`. \n",
"\n",
"The idea of such grouping of functions and methods is to provide a means for granularity: It is quite possible that you do not need all functions in a particular application. If you want to save some flash space, you can easily exclude arbitrary sub-modules from the firmware. The [ulab.h](https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h) header file contains a pre-processor flag for each sub-module. The default setting is 1 for each of them. Setting them to 0 removes the module from the compiled firmware. \n",
"\n",
"The first couple of lines of the file look like this\n",
"\n",
"```c\n",
"// vectorise (all functions) takes approx. 3 kB of flash space\n",
"#define ULAB_VECTORISE_MODULE (1)\n",
"\n",
"// linalg adds around 6 kB\n",
"#define ULAB_LINALG_MODULE (1)\n",
"\n",
"// poly is approx. 2.5 kB\n",
"#define ULAB_POLY_MODULE (1)\n",
"```\n",
"\n",
"In order to simplify navigation in the header, each flag begins with `ULAB_`, and continues with the name of the sub-module. This name is also the `.c` file, where the sub-module is implemented. So, e.g., the linear algebra routines can be found in `linalg.c`, and the corresponding compiler flag is `ULAB_LINALG_MODULE`. Each section displays a hint as to how much space you can save by un-setting the flag.\n",
"\n",
"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",
"Except for `fft`, the standard sub-modules, `vector`, `linalg`, `numerical`, `and poly`all `numpy`-compatible. User-defined functions that accept `ndarray`s as their argument should be implemented in the `extra` sub-module, or its sub-modules. Hints as to how to do that can be found in the section [Extending ulab](#Extending-ulab)."
]
},
{
@ -589,22 +639,20 @@
"\n",
"## Methods of ndarrays\n",
"\n",
"[.shape](#.shape)\n",
"[.shape<sup>*</sup>](#.shape)\n",
"\n",
"[size<sup>*</sup>](#size)\n",
"\n",
"[itemsize<sup>*</sup>](#itemsize)\n",
"\n",
"[.reshape](#.reshape)\n",
"\n",
"[.rawsize<sup>**</sup>](#.rawsize)\n",
"\n",
"[.transpose](#.transpose)\n",
"\n",
"[.flatten<sup>**</sup>](#.flatten)\n",
"\n",
"[.asbytearray](#.asbytearray)\n",
"\n",
"## Matrix methods\n",
"\n",
"[size](#size)\n",
"\n",
"[inv](#inv)\n",
"\n",
"[dot](#dot)\n",
@ -659,7 +707,11 @@
"\n",
"[ifft<sup>**</sup>](#ifft)\n",
"\n",
"[spectrum<sup>**</sup>](#spectrum)"
"[spectrum<sup>**</sup>](#spectrum)\n",
"\n",
"## Filter functions\n",
"\n",
"[convolve](#convolve)"
]
},
{
@ -668,13 +720,13 @@
"source": [
"# ndarray, the basic container\n",
"\n",
"The `ndarray` is the underlying container of numerical data. It is derived from micropython's own `array` object, but has a great number of extra features starting with how it can be initialised, how operations can be done on it, and which functions can accept it as an argument.\n",
"The `ndarray` is the underlying container of numerical data. It is derived from micropython's own `array` object, but has a great number of extra features starting with how it can be initialised, which operations can be done on it, and which functions can accept it as an argument. One important property of an `ndarray` is that it is also a proper `micropython` iterable.\n",
"\n",
"Since the `ndarray` is a binary container, it is also compact, meaning that it takes only a couple of bytes of extra RAM in addition to what is required for storing the numbers themselves. `ndarray`s are also type-aware, i.e., one can save RAM by specifying a data type, and using the smallest reasonable one. Five such types are defined, namely `uint8`, `int8`, which occupy a single byte of memory per datum, `uint16`, and `int16`, which occupy two bytes per datum, and `float`, which occupies four or eight bytes per datum. The precision/size of the `float` type depends on the definition of `mp_float_t`. Some platforms, e.g., the PYBD, implement `double`s, but some, e.g., the pyboard.v.11, don't. You can find out, what type of float your particular platform implements by looking at the output of the [.rawsize](#.rawsize) class method.\n",
"Since the `ndarray` is a binary container, it is also compact, meaning that it takes only a couple of bytes of extra RAM in addition to what is required for storing the numbers themselves. `ndarray`s are also type-aware, i.e., one can save RAM by specifying a data type, and using the smallest reasonable one. Five such types are defined, namely `uint8`, `int8`, which occupy a single byte of memory per datum, `uint16`, and `int16`, which occupy two bytes per datum, and `float`, which occupies four or eight bytes per datum. The precision/size of the `float` type depends on the definition of `mp_float_t`. Some platforms, e.g., the PYBD, implement `double`s, but some, e.g., the pyboard.v.11, don't. You can find out, what type of float your particular platform implements by looking at the output of the [.itemsize](#.itemsize) class property.\n",
"\n",
"On the following pages, we will see how one can work with `ndarray`s. Those familiar with `numpy` should find that the nomenclature and naming conventions of `numpy` are adhered to as closely as possible. I will point out the few differences, where necessary.\n",
"\n",
"For the sake of comparison, in addition to `ulab` code snippets, sometimes the equivalent `numpy` code is also presented. You can find out, where the snippet is supposed to run by looking at its first line, the header.\n",
"For the sake of comparison, in addition to the `ulab` code snippets, sometimes the equivalent `numpy` code is also presented. You can find out, where the snippet is supposed to run by looking at its first line, the header.\n",
"\n",
"Hint: you can easily port existing `numpy` code, if you `import ulab as np`."
]
@ -842,16 +894,18 @@
"source": [
"### .shape\n",
"\n",
"The `.shape` method returns a 2-tuple with the number of rows, and columns."
"The `.shape` method (property) returns a 2-tuple with the number of rows, and columns. \n",
"\n",
"**WARNING:** In `circuitpython`, you can call the method as a property, i.e., "
]
},
{
"cell_type": "code",
"execution_count": 283,
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-16T15:30:33.810628Z",
"start_time": "2019-10-16T15:30:33.796088Z"
"end_time": "2020-02-11T19:01:40.377272Z",
"start_time": "2020-02-11T19:01:40.364448Z"
}
},
"outputs": [
@ -879,13 +933,261 @@
"\n",
"a = np.array([1, 2, 3, 4], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"shape of a:\", a.shape())\n",
"print(\"shape of a:\", a.shape)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"shape of b:\", b.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**WARNING:** On the other hand, since properties are not implemented in `micropython`, there you would call the method as a function, i.e., "
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T19:01:40.377272Z",
"start_time": "2020-02-11T19:01:40.364448Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\n",
" array([1, 2, 3, 4], dtype=int8)\n",
"shape of a: (1, 4)\n",
"\n",
"b:\n",
" array([[1, 2],\n",
"\t [3, 4]], dtype=int8)\n",
"shape of b: (2, 2)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3, 4], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"shape of a:\", a.shape)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"shape of b:\", b.shape())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### .size\n",
"\n",
"The `.size` method (property) returns an integer with the number of elements in the array. \n",
"\n",
"**WARNING:** In `circuitpython`, the `numpy` nomenclature applies, i.e., "
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T06:32:22.721112Z",
"start_time": "2020-02-11T06:32:22.713111Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\n",
" array([1, 2, 3], dtype=int8)\n",
"size of a: 3\n",
"\n",
"b:\n",
" array([[1, 2],\n",
"\t [3, 4]], dtype=int8)\n",
"size of b: 4\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"size of a:\", a.size)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"size of b:\", b.size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**WARNING:** In `micropython`, `size` is a method, i.e., "
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T06:32:22.721112Z",
"start_time": "2020-02-11T06:32:22.713111Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\n",
" array([1, 2, 3], dtype=int8)\n",
"size of a: 3\n",
"\n",
"b:\n",
" array([[1, 2],\n",
"\t [3, 4]], dtype=int8)\n",
"size of b: 4\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"size of a:\", a.size)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.int8)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"size of b:\", b.size())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### .itemsize\n",
"\n",
"The `.itemsize` method (property) returns an integer with the siz enumber of elements in the array.\n",
"\n",
"**WARNING:** In `circuitpython`:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T19:05:04.296601Z",
"start_time": "2020-02-11T19:05:04.280669Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\n",
" array([1, 2, 3], dtype=int8)\n",
"itemsize of a: 1\n",
"\n",
"b:\n",
" array([[1.0, 2.0],\n",
"\t [3.0, 4.0]], dtype=float)\n",
"itemsize of b: 8\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"itemsize of a:\", a.itemsize)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.float)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"itemsize of b:\", b.itemsize)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**WARNING:** In `micropython`:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T19:05:04.296601Z",
"start_time": "2020-02-11T19:05:04.280669Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a:\n",
" array([1, 2, 3], dtype=int8)\n",
"itemsize of a: 1\n",
"\n",
"b:\n",
" array([[1.0, 2.0],\n",
"\t [3.0, 4.0]], dtype=float)\n",
"itemsize of b: 8\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3], dtype=np.int8)\n",
"print(\"a:\\n\", a)\n",
"print(\"itemsize of a:\", a.itemsize)\n",
"\n",
"b= np.array([[1, 2], [3, 4]], dtype=np.float)\n",
"print(\"\\nb:\\n\", b)\n",
"print(\"itemsize of b:\", b.itemsize())"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -934,54 +1236,6 @@
"print('a (1 by 16):', a.reshape((1, 16)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### .rawsize\n",
"\n",
"The `rawsize` method of the `ndarray` returns a 5-tuple with the following data\n",
"\n",
"1. number of rows\n",
"2. number of columns\n",
"3. length of the storage (should be equal to the product of 1. and 2.)\n",
"4. length of the data storage in bytes \n",
"5. datum size in bytes (1 for `uint8`/`int8`, 2 for `uint16`/`int16`, and 4, or 8 for `floats`, see [ndarray, the basic container](#ndarray,-the-basic-container))\n",
"\n",
"**WARNING:** `rawsize` is a `ulab`-only method; it has no equivalent in `numpy`."
]
},
{
"cell_type": "code",
"execution_count": 510,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-19T17:44:26.983908Z",
"start_time": "2019-10-19T17:44:26.764912Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a: \t\t array([1.0, 2.0, 3.0, 4.0], dtype=float)\n",
"rawsize of a: \t (1, 4, 4, 16, 4)\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3, 4], dtype=np.float)\n",
"print(\"a: \\t\\t\", a)\n",
"print(\"rawsize of a: \\t\", a.rawsize())"
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -1035,109 +1289,6 @@
"print(\"b flattened (F): \\t\", b.flatten(order='F'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### .asbytearray\n",
"\n",
"The contents of an `ndarray` can be accessed directly by calling the `.asbytearray` method. This will simply return a pointer to the underlying flat `array` object, which can then be manipulated directly.\n",
"\n",
"**WARNING:** `asbytearray` is a `ulab`-only method; it has no equivalent in `numpy`.\n",
"\n",
"In the example below, note the difference between `a`, and `buffer`: while both are designated as an array, you recognise the micropython array from the fact that it prints the typecode (`b` in this particular case). The `ndarray`, on the other hand, prints out the `dtype` (`int8` here)."
]
},
{
"cell_type": "code",
"execution_count": 211,
"metadata": {
"ExecuteTime": {
"end_time": "2019-11-01T14:25:39.008074Z",
"start_time": "2019-11-01T14:25:38.876546Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"a: array([1, 2, 3, 4], dtype=int8)\n",
"array content: array('b', [1, 2, 3, 4])\n",
"array content: array('b', [1, 123, 3, 4])\n",
"\n",
"\n"
]
}
],
"source": [
"%%micropython -unix 1\n",
"\n",
"import ulab as np\n",
"\n",
"a = np.array([1, 2, 3, 4], dtype=np.int8)\n",
"print('a: ', a)\n",
"buffer = a.asbytearray()\n",
"print(\"array content:\", buffer)\n",
"buffer[1] = 123\n",
"print(\"array content:\", buffer)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This in itself wouldn't be very interesting, but since `buffer` is a proper micropython `array`, we can pass it to functions that can employ the buffer protocol. E.g., all the `ndarray` facilities can be applied to the results of timed ADC conversions."
]
},
{
"cell_type": "code",
"execution_count": 563,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-20T07:29:00.153589Z",
"start_time": "2019-10-20T07:28:50.210383Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ADC results:\t array([48, 2, 2, ..., 0, 0, 0], dtype=uint8)\n",
"mean of results:\t 1.22\n",
"std of results:\t 4.744639\n",
"\n"
]
}
],
"source": [
"%%micropython -pyboard 1\n",
"\n",
"import pyb\n",
"import ulab as np\n",
"\n",
"n = 100\n",
"\n",
"adc = pyb.ADC(pyb.Pin.board.X19)\n",
"tim = pyb.Timer(6, freq=10)\n",
"\n",
"a = np.array([0]*n, dtype=np.uint8)\n",
"buffer = a.asbytearray()\n",
"adc.read_timed(buffer, tim)\n",
"\n",
"print(\"ADC results:\\t\", a)\n",
"print(\"mean of results:\\t\", np.mean(a))\n",
"print(\"std of results:\\t\", np.std(a))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Likewise, data can be read directly into `ndarray`s from other interfaces, e.g., SPI, I2C etc, and also, by laying bare the `ndarray`, we can pass results of `ulab` computations to anything that can read from a buffer."
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -3988,11 +4139,11 @@
},
{
"cell_type": "code",
"execution_count": 458,
"execution_count": 114,
"metadata": {
"ExecuteTime": {
"end_time": "2019-10-19T13:07:43.168629Z",
"start_time": "2019-10-19T13:07:43.130341Z"
"end_time": "2020-02-16T18:38:07.294862Z",
"start_time": "2020-02-16T18:38:07.233842Z"
}
},
"outputs": [
@ -4000,13 +4151,13 @@
"name": "stdout",
"output_type": "stream",
"text": [
"real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\n",
"\n",
"imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\n",
"\n",
"real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\n",
"\n",
"imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\n",
"real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\r\n",
"\r\n",
"imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\r\n",
"\r\n",
"real part:\t array([5119.996, -5.004663, -5.004798, ..., -5.005482, -5.005643, -5.006577], dtype=float)\r\n",
"\r\n",
"imaginary part:\t array([0.0, 1631.333, 815.659, ..., -543.764, -815.6588, -1631.333], dtype=float)\r\n",
"\n"
]
}
@ -4015,16 +4166,20 @@
"%%micropython -pyboard 1\n",
"\n",
"import ulab as np\n",
"from ulab import numerical\n",
"from ulab import vector\n",
"from ulab import fft\n",
"from ulab import linalg\n",
"\n",
"x = np.linspace(0, 10, num=1024)\n",
"y = np.sin(x)\n",
"z = np.zeros(len(x))\n",
"x = numerical.linspace(0, 10, num=1024)\n",
"y = vector.sin(x)\n",
"z = linalg.zeros(len(x))\n",
"\n",
"a, b = np.fft(x)\n",
"a, b = fft.fft(x)\n",
"print('real part:\\t', a)\n",
"print('\\nimaginary part:\\t', b)\n",
"\n",
"c, d = np.fft(x, z)\n",
"c, d = fft.fft(x, z)\n",
"print('\\nreal part:\\t', c)\n",
"print('\\nimaginary part:\\t', d)"
]
@ -4268,13 +4423,65 @@
"where $N$ is the length of $y_1$, and $Y_1, Y_2$, and $Y$, respectively, are the Fourier transforms of $y_1, y_2$, and $y = y_1 + iy_2$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Filter routines"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"numpy: https://docs.scipy.org/doc/numpy/reference/generated/numpy.convolve.html\n",
"\n",
"## convolve\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",
"\n",
"x = np.array((1,2,3))\n",
"y = np.array((1,10,100,1000))\n",
"\n",
"print(np.convolve(x, y))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Extending ulab\n",
"\n",
"New functions can easily be added to `ulab` in a couple of simple steps. At the C level, the type definition of an `ndarray` is as follows:\n",
"As mentioned at the beginning, `ulab` is a set of sub-modules, out of which one, `extra` is explicitly reserved for user code. You should implement your functions in this sub-module, or sub-modules thereof.\n",
"\n",
"The new functions can easily be added to `extra` in a couple of simple steps. At the C level, the type definition of an `ndarray` is as follows:\n",
"\n",
"```c\n",
"typedef struct _ndarray_obj_t {\n",
@ -4313,7 +4520,7 @@
" NDARRAY_FLOAT = 'd',\n",
"};\n",
"```\n",
"The ambiguity is caused by the fact that not all platforms implement `double`, and there one has to take `float`s. But you haven't actually got to be concerned by this, because at the very beginning of `ndarray.h`, this is already taken care of: the preprocessor figures out, what the `float` implementation of the hardware platform is, and defines the `NDARRAY_FLOAT` typecode accordingly. All you have to keep in mind is that wherever you would use `float` or `double`, you have to use `mp_float_t`. That type is defined in `py/mpconfig.h` of the micropython code base.\n",
"The ambiguity is caused by the fact that not all platforms implement `double`, and there one has to take `float`s. But you haven't actually got to be concerned by this, because at the very beginning of `ndarray.h`, this is already taken care of: the pre-processor figures out, what the `float` implementation of the hardware platform is, and defines the `NDARRAY_FLOAT` typecode accordingly. All you have to keep in mind is that wherever you would use `float` or `double`, you have to use `mp_float_t`. That type is defined in `py/mpconfig.h` of the micropython code base.\n",
"\n",
"Therefore, a 4-by-5 matrix of type float can be created as\n",
"\n",
@ -4458,46 +4665,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In the boilerplate above, we cast the pointer to `array->items` to the required type. There are certain operations, however, when you do not need the casting. If you do not want to change the array's values, only their position within the array, you can get away with copying the memory content, regardless the type. A good example for such a scenario is the transpose function in https://github.com/v923z/micropython-ulab/blob/master/code/linalg.c."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Compiling your module\n",
"\n",
"Once you have implemented the functionality you wanted, you have to include the source code in the make file by adding it to `micropython.mk`:\n",
"\n",
"```makefile\n",
"USERMODULES_DIR := $(USERMOD_DIR)\n",
"\n",
"# Add all C files to SRC_USERMOD.\n",
"SRC_USERMOD += $(USERMODULES_DIR)/ndarray.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/linalg.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/vectorise.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/poly.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/fft.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/numerical.c\n",
"SRC_USERMOD += $(USERMODULES_DIR)/ulab.c\n",
"\n",
"SRC_USERMOD += $(USERMODULES_DIR)/your_module.c\n",
"\n",
"CFLAGS_USERMOD += -I$(USERMODULES_DIR)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In addition, you also have to add the function objects to `ulab.c`, and create a `QString` for the function name:\n",
"Once the function implementation is done, you should add the function object to the globals dictionary of the `extra` sub-module as \n",
"\n",
"```c\n",
"...\n",
" MP_DEFINE_CONST_FUN_OBJ_1(useless_function_obj, userless_function);\n",
"...\n",
" STATIC const mp_map_elem_t ulab_globals_table[] = {\n",
" STATIC const mp_map_elem_t extra_globals_table[] = {\n",
"...\n",
" { MP_OBJ_NEW_QSTR(MP_QSTR_useless), (mp_obj_t)&useless_function_obj },\n",
"...\n",
@ -4509,29 +4683,21 @@
"```c\n",
" MP_DEFINE_CONST_FUN_OBJ_1(useless_function_obj, userless_function);\n",
"```\n",
"depends naturally on what exactly you implemented, i.e., how many arguments the function takes, whether only positional arguments allowed and so on. For a thorough discussion on how function objects have to be defined, see, e.g., the [user module programming manual](#https://micropython-usermod.readthedocs.io/en/latest/)."
"depends naturally on what exactly you implemented, i.e., how many arguments the function takes, whether only positional arguments allowed and so on. For a thorough discussion on how function objects have to be defined, see, e.g., the [user module programming manual](#https://micropython-usermod.readthedocs.io/en/latest/).\n",
"\n",
"And with that, you are done. You can simply call the compiler as \n",
"\n",
"```bash\n",
"make BOARD=PYBV11 USER_C_MODULES=../../../ulab all\n",
"```\n",
"and the rest is taken care of."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"At this point, you should be able to compile the module with your extension by running `make` on the command line\n",
"\n",
"```bash\n",
"make USER_C_MODULES=../../../ulab all\n",
"```\n",
"for the unix port, and \n",
"\n",
"```bash\n",
"make BOARD=PYBV11 CROSS_COMPILE=<arm_tools_path>/bin/arm-none-eabi- USER_C_MODULES=../../../ulab all\n",
"```\n",
"for the `pyboard`, provided that the you have defined \n",
"\n",
"```makefile\n",
"#define MODULE_ULAB_ENABLED (1)\n",
"```\n",
"somewhere in `micropython/port/unix/mpconfigport.h`, or `micropython/stm32/mpconfigport.h`, respectively."
"In the boilerplate above, we cast the pointer to `array->items` to the required type. There are certain operations, however, when you do not need the casting. If you do not want to change the array's values, only their position within the array, you can get away with copying the memory content, regardless the type. A good example for such a scenario is the transpose function in https://github.com/v923z/micropython-ulab/blob/master/code/linalg.c."
]
}
],

File diff suppressed because it is too large Load diff

2
tests/00smoke.py Normal file
View file

@ -0,0 +1,2 @@
import ulab
print(ulab.eye(3))

3
tests/00smoke.py.exp Normal file
View file

@ -0,0 +1,3 @@
array([[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]], dtype=float)

12
tests/constructors.py Normal file
View file

@ -0,0 +1,12 @@
from ulab import linalg
import ulab
print(ulab.ones(3))
print(ulab.ones((2,3)))
print(ulab.zeros(3))
print(ulab.zeros((2,3)))
print(ulab.eye(3))
print(ulab.ones(1, dtype=ulab.int8))
print(ulab.ones(2, dtype=ulab.uint8))
print(ulab.ones(3, dtype=ulab.int16))
print(ulab.ones(4, dtype=ulab.uint16))
print(ulab.ones(5, dtype=ulab.float))

14
tests/constructors.py.exp Normal file
View file

@ -0,0 +1,14 @@
array([1.0, 1.0, 1.0], dtype=float)
array([[1.0, 1.0, 1.0],
[1.0, 1.0, 1.0]], dtype=float)
array([0.0, 0.0, 0.0], dtype=float)
array([[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]], dtype=float)
array([[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]], dtype=float)
array([1], dtype=int8)
array([1, 1], dtype=uint8)
array([1, 1, 1], dtype=int16)
array([1, 1, 1, 1], dtype=uint16)
array([1.0, 1.0, 1.0, 1.0, 1.0], dtype=float)

20
tests/operators.py Normal file
View file

@ -0,0 +1,20 @@
import ulab
a = ulab.ones(3)
print(a+a)
print(a-a)
print(a*a)
print(a/a)
print(a+2)
print(a-2)
print(a*2)
print(a/2)
print(a<1)
print(a<2)
print(a<=0)
print(a<=1)
print(a>1)
print(a>2)
print(a>=0)
print(a>=1)
#print(a==0) # These print just true or false. Is it right? is it a micropython limitation?
#print(a==1)

16
tests/operators.py.exp Normal file
View file

@ -0,0 +1,16 @@
array([2.0, 2.0, 2.0], dtype=float)
array([0.0, 0.0, 0.0], dtype=float)
array([1.0, 1.0, 1.0], dtype=float)
array([1.0, 1.0, 1.0], dtype=float)
array([3.0, 3.0, 3.0], dtype=float)
array([-1.0, -1.0, -1.0], dtype=float)
array([2.0, 2.0, 2.0], dtype=float)
array([0.5, 0.5, 0.5], dtype=float)
[False, False, False]
[True, True, True]
[False, False, False]
[True, True, True]
[False, False, False]
[False, False, False]
[True, True, True]
[True, True, True]