Git Product home page Git Product logo

v923z / micropython-ulab Goto Github PK

View Code? Open in Web Editor NEW
395.0 15.0 111.0 3.25 MB

a numpy-like fast vector module for micropython, circuitpython, and their derivatives

Home Page: https://micropython-ulab.readthedocs.io/en/latest

License: MIT License

C 80.75% Makefile 0.20% Python 17.90% Shell 1.10% CMake 0.04%
micropython-ulab numpy micropython module circuitpython openmv firmware ulab circuitpython-ulab scipy

micropython-ulab's Introduction

ulab

Documentation Status

ulab is a numpy-like array manipulation library for micropython and CircuitPython. The module is written in C, defines compact containers (ndarrays) for numerical data of one to four dimensions, and is fast. The library is a software-only standard micropython user module, i.e., it has no hardware dependencies, and can be compiled for any platform. 8-, and 16-bit signed and unsigned integer dtypes, as well as float, and, optionally, complex are supported. The float implementation of micropython (32-bit float, or 64-bit double) is automatically detected and handled.

  1. Supported functions and methods
    1. ndarray methods
    2. numpy and scipy functions
    3. ulab utilities
    4. user module
  2. Usage
  3. Finding help
  4. Benchmarks
  5. Firmware
    1. Customising the firmware
    2. Platforms including ulab
    3. Compiling
      1. UNIX
      2. STM-based boards
      3. ESP32-based boards
      4. RP2-based boards
      5. Compiling for CircuitPython
  6. Issues, contributing, and testing
    1. Testing

Supported functions and methods

ndarray methods

ulab implements numpy's ndarray with the ==, !=, <, <=, >, >=, +, -, /, *, **, +=, -=, *=, /=, **= binary operators, and the len, ~, -, +, abs unary operators that operate element-wise. Type-aware ndarrays can be initialised from any micropython iterable, lists of iterables via the array constructor, or by means of the arange, concatenate, diag, eye, frombuffer, full, linspace, logspace, ones, or zeros functions.

ndarrays can be sliced, and iterated on, and have a number of their own methods, and properties, such as flatten(), itemsize, reshape(), shape, size, strides, tobytes(), tolist(), and transpose() and T. If the firmware is compiled with complex support, the imag, and real properties are automatically included.

numpy and scipy functions

In addition, ulab includes universal functions, many numpy functions, and functions from the numpy.fft, numpy.linalg, numpy.random, scipy.linalg, scipy.optimize, scipy.signal, and scipy.special modules. A complete list of available routines can be found under micropython-ulab.

ulab utilities

The utils module contains functions for interfacing with peripheral devices supporting the buffer protocol. These functions do not have an obvious numpy equivalent, but share a similar programming interface, and allow direct data input-output between numerical arrays and hardware components.

user module

User-defined functions operating on numerical data can easily be added via the user module. This allows for transparent extensions, without having to change anything in the core. Hints as to how to work with ndarrays at the C level can be found in the programming manual.

Usage

ulab sports a numpy/scipy-compatible interface, which makes porting of CPython code straightforward. The following snippet should run equally well in micropython, or on a PC.

try:
    from ulab import numpy
    from ulab import scipy
except ImportError:
    import numpy
    import scipy.special

x = numpy.array([1, 2, 3])
scipy.special.erf(x)

Finding help

Documentation can be found on readthedocs under micropython-ulab, as well as at circuitpython-ulab. A number of practical examples are listed in Jeff Epler's excellent circuitpython-ulab overview. The tricks chapter of the user manual discusses methods by which RAM and speed can be leveraged in particular numerical problems.

Benchmarks

Representative numbers on performance can be found under ulab samples.

Firmware

Pre-built, and up-to-date firmware files for select platforms can be downloaded from micropython-builder.

Customising the firmware

If flash space is a concern, unnecessary functions can be excluded from the compiled firmware with pre-processor switches. In addition, ulab also has options for trading execution speed for firmware size. A thorough discussion on how the firmware can be customised can be found in the corresponding section of the user manual.

Platforms including ulab

ulab is also included in the following compiled micropython variants and derivatives:

  1. CircuitPython for SAMD51 and nRF microcontrollers https://github.com/adafruit/circuitpython
  2. MicroPython for K210 https://github.com/loboris/MicroPython_K210_LoBo
  3. MaixPy https://github.com/sipeed/MaixPy
  4. OpenMV https://github.com/openmv/openmv
  5. pimoroni-pico https://github.com/pimoroni/pimoroni-pico

Compiling

If you want to try the latest version of ulab on micropython or one of its forks, the firmware can be compiled from the source by following these steps:

UNIX port

Simply clone the ulab repository with

git clone https://github.com/v923z/micropython-ulab.git ulab

and then run

./build.sh [matrix.dims] # Dimensions is 2 by default

This command will clone micropython, and build the unix port automatically, as well as run the test scripts. If you want an interactive unix session, you can launch it in

ulab/micropython/ports/unix

STM-based boards

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

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 this step 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. If your target is unix, you don't need to specify the BOARD parameter.

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.

ESP32-based boards

ulab can be tested on the ESP32 in wokwi's micropython emulator without having to compile the C code. This utility also offers the possibility to save and share your micropython code.

Firmware for Espressif hardware can be built in two different ways, which are discussed in the next two paragraphs. A solution for issues with the firmware size is outlined in the last paragraph of this section.

Compiling with cmake

Beginning with version 1.15, micropython switched to cmake on the ESP32 port. If your operating system supports CMake > 3.12, you can either simply download, and run the single build script, or follow the step in this section. Otherwise, you should skip to the next one, where the old, make-based approach is discussed.

In case you encounter difficulties during the build process, you can consult the (general instructions for the ESP32)[https://github.com/micropython/micropython/tree/master/ports/esp32#micropython-port-to-the-esp32].

First, clone the ulab, the micropython, as well as the espressif repositories:

export BUILD_DIR=$(pwd)

git clone https://github.com/v923z/micropython-ulab.git ulab
git clone https://github.com/micropython/micropython.git

cd $BUILD_DIR/micropython/

git clone -b v4.0.2 --recursive https://github.com/espressif/esp-idf.git

Also later releases of esp-idf are possible (e.g. v4.2.1).

Then install the ESP-IDF tools:

cd esp-idf
./install.sh
. ./export.sh

Next, build the micropython cross-compiler, and the ESP sub-modules:

cd $BUILD_DIR/micropython/mpy-cross
make
cd $BUILD_DIR/micropython/ports/esp32
make submodules

At this point, all requirements are installed and built. We can now compile the firmware with ulab. In $BUILD_DIR/micropython/ports/esp32 create a makefile with the following content:

BOARD = GENERIC
USER_C_MODULES = $(BUILD_DIR)/ulab/code/micropython.cmake

include Makefile

You specify with the BOARD variable, what you want to compile for, a generic board, or TINYPICO (for micropython version >1.1.5, use UM_TINYPICO), etc. Still in $BUILD_DIR/micropython/ports/esp32, you can now run make.

Compiling with make

If your operating system does not support a recent enough version of CMake, you have to stay with micropython version 1.14. The firmware can be compiled either by downloading and running the build script, or following the steps below:

First, clone ulab with

git clone https://github.com/v923z/micropython-ulab.git ulab

and then, in the same directory, micropython

git clone https://github.com/micropython/micropython.git

At this point, you should have ulab, and micropython side by side.

With version 1.14, micropython switched to cmake on the ESP32 port, thus breaking compatibility with user modules. ulab can, however, still be compiled with version 1.14. You can check out a particular version by pinning the release tag as

cd ./micropython/
git checkout tags/v1.14

Next, update the submodules,

git submodule update --init
cd ./mpy-cross && make # build cross-compiler (required)

and find the ESP commit hash

cd ./micropython/ports/esp32
make ESPIDF= # will display supported ESP-IDF commit hashes
# output should look like: """
# ...
# Supported git hash (v3.3): 9e70825d1e1cbf7988cf36981774300066580ea7
# Supported git hash (v4.0) (experimental): 4c81978a3e2220674a432a588292a4c860eef27b

Choose an ESPIDF version from one of the options printed by the previous command:

ESPIDF_VER=9e70825d1e1cbf7988cf36981774300066580ea7

In the micropython directory, create a new directory with

mkdir esp32

Your micropython directory should now look like

ls
ACKNOWLEDGEMENTS    CONTRIBUTING.md  esp32     lib      mpy-cross  README.md
CODECONVENTIONS.md  docs             examples  LICENSE  ports      tests
CODEOFCONDUCT.md    drivers          extmod    logo     py         tools

In ./micropython/esp32, download the software development kit with

git clone https://github.com/espressif/esp-idf.git esp-idf
cd ./esp-idf
git checkout $ESPIDF_VER
git submodule update --init --recursive # get idf submodules
pip install -r ./requirements.txt # install python reqs

Next, still staying in ./micropython/eps32/esd-idf/, install the ESP32 compiler. If using an ESP-IDF version >= 4.x (chosen by $ESPIDF_VER above), this can be done by running . $BUILD_DIR/esp-idf/install.sh. Otherwise, for version 3.x, run the following commands in in .micropython/esp32/esp-idf:

# for 64 bit linux
curl https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar xvz

# for 32 bit
# curl https://dl.espressif.com/dl/xtensa-esp32-elf-linux32-1.22.0-80-g6c4433a-5.2.0.tar.gz | tar xvz

# don't worry about adding to path; we'll specify that later

# also, see https://docs.espressif.com/projects/esp-idf/en/v3.3.2/get-started for more info

Finally, build the firmware:

cd ./micropython/ports/esp32
# temporarily add esp32 compiler to path
export PATH=../../esp32/esp-idf/xtensa-esp32-elf/bin:$PATH
export ESPIDF=../../esp32/esp-idf # req'd by Makefile
export BOARD=GENERIC # options are dirs in ./boards
export USER_C_MODULES=../../../ulab # include ulab in firmware

make submodules & make all

If it compiles without error, you can plug in your ESP32 via USB and then flash it with:

make erase && make deploy

What to do, if the firmware is too large?

When selecting BOARD=TINYPICO, the firmware is built but fails to deploy, because it is too large for the standard partitions. We can rectify the problem by creating a new partition table. In order to do so, in $BUILD_DIR/micropython/ports/esp32/, copy the following 8 lines to a file named partitions_ulab.cvs:

# Notes: the offset of the partition table itself is set in
# $ESPIDF/components/partition_table/Kconfig.projbuild and the
# offset of the factory/ota_0 partition is set in makeimg.py
# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 0x200000,
vfs,      data, fat,     0x220000, 0x180000,

This expands the factory partition by 128 kB, and reduces the size of vfs by the same amount. Having defined the new partition table, we should extend sdkconfig.board by adding the following two lines:

CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_ulab.csv"

This file can be found in $BUILD_DIR/micropython/ports/esp32/boards/TINYPICO/. Finally, run make clean, and make. The new firmware contains the modified partition table, and should fit on the microcontroller.

RP2-based boards

RP2 firmware can be compiled either by downloading and running the single build script/build script for Pico W, or executing the commands below.

First, clone micropython:

git clone https://github.com/micropython/micropython.git

Then, setup the required submodules:

cd micropython
git submodule update --init lib/tinyusb
git submodule update --init lib/pico-sdk
cd lib/pico-sdk
git submodule update --init lib/tinyusb

You'll also need to compile mpy-cross:

cd ../../mpy-cross
make

That's all you need to do for the micropython repository. Now, let us clone ulab (in a directory outside the micropython repository):

cd ../../
git clone https://github.com/v923z/micropython-ulab ulab

With this setup, we can now build the firmware. Back in the micropython repository, use these commands:

cd ports/rp2
make USER_C_MODULES=/path/to/ulab/code/micropython.cmake

If micropython and ulab were in the same folder on the computer, you can set USER_C_MODULES=../../../ulab/code/micropython.cmake. The compiled firmware will be placed in micropython/ports/rp2/build.

Compiling for CircuitPython

Adafruit Industries always include a relatively recent version of ulab in their nightly builds. However, if you really need the bleeding edge, you can easily compile the firmware from the source. Simply clone circuitpython, and move the commit pointer to the latest version of ulab (ulab will automatically be cloned with circuitpython):

git clone https://github.com/adafruit/circuitpython.git

cd circuitpyton/extmod/ulab

# update ulab here
git checkout master
git pull

You might have to check, whether the CIRCUITPY_ULAB variable is set to 1 for the port that you want to compile for. You find this piece of information in the make fragment:

circuitpython/ports/port_of_your_choice/mpconfigport.mk

After this, you would run make with the single BOARD argument, e.g.:

make BOARD=mini_sam_m4

Issues, contributing, and testing

If you find a problem with the code, please, raise an issue! An issue should address a single problem, and should contain a minimal code snippet that demonstrates the difference from the expected behaviour. Reducing a problem to the bare minimum significantly increases the chances of a quick fix.

Feature requests (porting a particular function from numpy or scipy) should also be posted at ulab issue.

Contributions of any kind are always welcome. If you feel like adding to the code, you can simply issue a pull request. If you do so, please, try to adhere to micropython's coding conventions.

However, you can also contribute to the documentation (preferably via the jupyter notebooks, or improve the tests.

Testing

If you decide to lend a hand with testing, here are the steps:

  1. Write a test script that checks a particular function, or a set of related functions!
  2. Drop this script in one of the folders in ulab tests!
  3. Run the ./build.sh script in the root directory of ulab! This will clone the latest micropython, compile the firmware for unix, execute all scripts in the ulab/tests, and compare the results to those in the expected results files, which are also in ulab/tests, and have an extension .exp. In case you have a new snippet, i.e., you have no expected results file, or if the results differ from those in the expected file, a new expected file will be generated in the root directory. You should inspect the contents of this file, and if they are satisfactory, then the file can be moved to the ulab/tests folder, alongside your snippet.

micropython-ulab's People

Contributors

callumjhays avatar ciscorn avatar codemee avatar crazyhackelkorn avatar deqingsun avatar dpgeorge avatar felixnumworks avatar gadgetoid avatar hiltay avatar hugonumworks avatar jepler avatar jetpax avatar jimmo avatar kbsriram avatar mdaeron avatar mouryarahul avatar page200 avatar rcolistete avatar retsyo avatar robertoroos avatar s-ol avatar t-ikegami avatar tekktrik avatar teuler avatar thetazero avatar ubiqio avatar v923z avatar vikas-udupa avatar wired8 avatar yamamotomas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micropython-ulab's Issues

Behaviour of transpose() and reshape() are incompatible with numpy

In numpy, ndarray.transpose() returns a view of the original array and ndarray.reshape() returns a view when possible and otherwise they returns a new array. In both cases the array object passed in as the argument is left unaffected. The current ulab behaviour is to reshape the argument that was passed and return the modified original.

The code for both of these functions will naturally get reworked with the new N-dimensional arrays and views, so this GitHub issue is more a spec for the new code than a plea for fixing the old code.

FWIW, in the code I started writing for ndarray the "if possible" test for reshape was simple and dumb: reshape used a view if and only if the input array or view had never been sliced or transposed (which implies that the stride on each dimension is the product of the lengths of all the more deeply nested dimensions). In practice I just kept a flag that was set on creation of new arrays but cleared for new slice or transposes views. Views created by subscripting using only integer indices copied the flag from the parent. In practice this meant that there are some cases that could have used a view but ended up copying because the code to check those cases seemed unnecessarily complex for Micropython.

Added to OpenMV

Hi, I just wanted to say that Ibrahim added your code to the OpenMV Cam.

Thanks!

(Yes, I know this is not the best way to contact you).

ndarrays are not modifiable

I've been playing around with ulab and it's awesome, but I've run into some issues because I can't modify the elements of an ndarray.

For example:

>>> a = 100 * np.linspace(0,10,num=1024)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported types for __mul__: 'int', 'ndarray'

and

>>> for i in range(len(b)):
... b[i] = i
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: index is out of range

By the way, THANK YOU for making this module. I'm still getting my bearings with mpy and usermods, but your documentation has been very helpful. Hopefully soon I'll have some time to add some of these features in myself.

EDIT: I just tried >>> a = np.linspace(0,10,num=1024) * 100 and it worked, so that's a solid workaround!

Support for matrix multiplication operator

Micropython now supports the PEP 465 syntax for using @ as an infix binary operator for matrix multiplication (following the merge of micropython/micropython#4947). Classes implemented in C will get their binary_op method called with the MP_BINARY_OP_MAT_MULTIPLY operator code when the user does this. It would be great if ulab supported this syntax in the same way that numpy does, since it makes code much more concise and readable, as well as making numpy code more directly portable.

Bug in ulab.linalg.dot function

@v923z linalg.dot seems to have a certain bug:

MicroPython v1.12-368-gbd63c26dd on 2020-04-13; PYBD-SF6W with STM32F767IIK
Type "help()" for more information.
>>> ulab.__version__
'0.41.2'

>>> A
array([[1.0, 2.0],
         [3.0, 4.0]], dtype=float)
>>> B
array([[1.0, 2.0, 3.0, 4.0],
         [5.0, 6.0, 7.0, 8.0]], dtype=float)
>>> ulab.linalg.dot(A,B)
array([[11.0, 23.0, 14.0, 30.0],
         [17.0, 37.0, 20.0, 44.0]], dtype=float)

whereas the correct result from Numpy is:

np.dot(A,B)
array([[11, 14, 17, 20],
       [23, 30, 37, 44]])

Seems that it has something to do with way the elements are stored in the array!
Can you reproduce the same at your end?

Slicing difference vs numpy

It seems that too few elements are in the result.

>>> np.array([0,1,2,3,4])[::2]
array([0, 2, 4])

vs

>>> ulab.array([0,1,2,3,4])[::2]
array([0.0, 2.0], dtype=float)

Let me know if you'd like me to look into this.

CNN extend for Numpy-like interface

Hi, Glad to find this repo.
Numpy is the base of science analysis in python. So micropython-ulab is a meaningful repo.

And now CNN is very prevent everywhere. So we wrote Planer, Powerful Light Artificial NEuRon. It is a CNN framework based on pure Numpy-like interface (with out any kernel code, just use reshape, transpose, concadinate, dot, and + - * /). I think it can run on with ulab as backend (with no modify or little modify).

I will buy a micropython board to have a try, if I encounter any problem, could you give a support?

building in CircuitPython

This is amazing! I've been trying to build ulab in CircuitPython, which I should be able to do as CP is a fork of micropython, but I've been having a lot of trouble. I'm seeing a lot of issues with shadowed declarations and comparison between signed and unsigned integers. When I attempt to make the board in the appropriate port using:

make BOARD=pybadge USER_C_MODULES=../../../ulab all

I get the following error:

Including User C Module from ../../../ulab/code
QSTR updated
FREEZE ../../frozen/circuitpython-stage/pybadge
../../../ulab/code/ndarray.c: In function 'ndarray_print_row':
../../../ulab/code/ndarray.c:81:20: error: declaration of 'i' shadows a previous local [-Werror=shadow]
for(size_t i=1; i<3; i++) {
^
../../../ulab/code/ndarray.c:66:12: note: shadowed declaration is here
size_t i;
^
../../../ulab/code/ndarray.c: In function 'ndarray_make_new':
../../../ulab/code/ndarray.c:168:30: error: passing argument 2 of 'mp_arg_check_num' makes pointer from integer without a cast [-Werror=int-conversion]
mp_arg_check_num(n_args, n_kw, 1, 2, true);
^~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:80:6: note: expected 'mp_map_t * {aka struct _mp_map_t *}' but argument is of type 'size_t {aka unsigned int}'
void mp_arg_check_num(size_t n_args, mp_map_t *kw_args, size_t n_args_min, size_t n_args_max, bool takes_kw);
^~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:176:29: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("first argument must be an iterable");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:191:25: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
if(len2 != MP_OBJ_SMALL_INT_VALUE(len_in)) {
^~
../../../ulab/code/ndarray.c:192:41: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("iterables are not of the same length");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'true_length':
../../../ulab/code/ndarray.c:234:13: error: implicit declaration of function 'mp_obj_is_type'; did you mean 'mp_obj_is_true'? [-Werror=implicit-function-declaration]
if(!mp_obj_is_type(item, &mp_type_bool)) {
^~~~~~~~~~~~~~
mp_obj_is_true
../../../ulab/code/ndarray.c:234:13: error: nested extern declaration of 'mp_obj_is_type' [-Werror=nested-externs]
../../../ulab/code/ndarray.c: In function 'generate_slice':
../../../ulab/code/ndarray.c:251:15: error: implicit declaration of function 'mp_obj_is_int'; did you mean 'mp_obj_list_init'? [-Werror=implicit-function-declaration]
} else if(mp_obj_is_int(index)) {
^~~~~~~~~~~~~
mp_obj_list_init
../../../ulab/code/ndarray.c:251:15: error: nested extern declaration of 'mp_obj_is_int' [-Werror=nested-externs]
../../../ulab/code/ndarray.c:256:20: error: comparison between signed and unsigned integer expressions [-Werror=sign-compare]
if((_index >= n) || (_index < 0)) {
^~
../../../ulab/code/ndarray.c:257:47: error: passing argument 2 of 'mp_raise_msg' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_msg(&mp_type_IndexError, "index is out of bounds");
^~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:151:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg);
^~~~~~~~~~~~
../../../ulab/code/ndarray.c:263:43: error: passing argument 2 of 'mp_raise_msg' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_msg(&mp_type_IndexError, "indices must be integers, slices, or Boolean lists");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:151:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg);
^~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'insert_slice_list':
../../../ulab/code/ndarray.c:293:33: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("could not broadast input array from shape");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:355:27: error: declaration of 'cindex' shadows a previous local [-Werror=shadow]
size_t j = 0, cindex = 0;
^~~~~~
../../../ulab/code/ndarray.c:296:12: note: shadowed declaration is here
size_t cindex, rindex;
^~~~~~
../../../ulab/code/ndarray.c: In function 'iterate_slice_list':
../../../ulab/code/ndarray.c:380:43: error: passing argument 2 of 'mp_raise_msg' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_msg(&mp_type_IndexError, "empty index range");
^~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:151:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg);
^~~~~~~~~~~~
../../../ulab/code/ndarray.c:441:27: error: declaration of 'cindex' shadows a previous local [-Werror=shadow]
size_t j = 0, cindex = 0;
^~~~~~
../../../ulab/code/ndarray.c:390:12: note: shadowed declaration is here
size_t cindex, rindex;
^~~~~~
../../../ulab/code/ndarray.c: In function 'ndarray_get_slice':
../../../ulab/code/ndarray.c:498:47: error: passing argument 2 of 'mp_raise_msg' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_msg(&mp_type_IndexError, "too many indices");
^~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:151:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg);
^~~~~~~~~~~~
../../../ulab/code/ndarray.c:506:51: error: passing argument 2 of 'mp_raise_msg' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_msg(&mp_type_IndexError, "indices must be integers, slices, or Boolean lists");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:151:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_msg(const mp_obj_type_t *exc_type, const compressed_string_t *msg);
^~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'ndarray_subscr':
../../../ulab/code/ndarray.c:547:33: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("right hand side must be an ndarray, or a scalar");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'ndarray_flatten':
../../../ulab/code/ndarray.c:658:29: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("flattening order must be either 'C', or 'F'");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'ndarray_binary_op':
../../../ulab/code/ndarray.c:723:33: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("operands could not be broadcast together");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:826:40: error: passing argument 1 of 'mp_raise_TypeError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_TypeError("wrong input type");
^~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:155:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_TypeError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:834:28: error: passing argument 1 of 'mp_raise_TypeError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_TypeError("wrong operand type on the right hand side");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:155:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_TypeError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c: In function 'ndarray_unary_op':
../../../ulab/code/ndarray.c:852:37: error: passing argument 1 of 'mp_raise_ValueError' from incompatible pointer type [-Werror=incompatible-pointer-types]
mp_raise_ValueError("operation is not supported for given type");
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../../../ulab/code/ndarray.c:15:0:
../../py/runtime.h:153:15: note: expected 'const compressed_string_t * {aka const struct *}' but argument is of type 'const char *'
NORETURN void mp_raise_ValueError(const compressed_string_t *msg);
^~~~~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:865:26: error: declaration of 'array' shadows a previous local [-Werror=shadow]
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:868:25: error: declaration of 'array' shadows a previous local [-Werror=shadow]
int8_t *array = (int8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:871:27: error: declaration of 'array' shadows a previous local [-Werror=shadow]
uint16_t *array = (uint16_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:874:26: error: declaration of 'array' shadows a previous local [-Werror=shadow]
int16_t *array = (int16_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:877:29: error: declaration of 'array' shadows a previous local [-Werror=shadow]
mp_float_t *array = (mp_float_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:892:25: error: declaration of 'array' shadows a previous local [-Werror=shadow]
int8_t *array = (int8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:897:26: error: declaration of 'array' shadows a previous local [-Werror=shadow]
int16_t *array = (int16_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:902:29: error: declaration of 'array' shadows a previous local [-Werror=shadow]
mp_float_t *array = (mp_float_t *)ndarray->array->items;
^~~~~
../../../ulab/code/ndarray.c:857:22: note: shadowed declaration is here
uint8_t *array = (uint8_t *)ndarray->array->items;
^~~~~
cc1: all warnings being treated as errors
make: *** [build-pybadge/code/ndarray.o] Error 1

ulab assumes that floats are boxed: MICROPY_OBJ_REPR_A or B

ulab is assuming that MicroPython is built with MICROPY_OBJ_REPR_A or MICROPY_OBJ_REPR_B, which store floats as boxed objects instead of as immediate values like smallints. In the code below, the float object is assumed to be boxed:

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

let ndarrays be on the right hand side of binary operators

At the moment, ndarrays must be on the left hand side of a binary operator. Thus

a = np.array([1, 2, 3])
a + 1

works, but

a = np.array([1, 2, 3])
1 + a

fails. A solution involves the implementation of the REVERSE operators in binary_op in ndarray.c

platform support?

Is this generic C code that could easily build and run on linux, mac, windows?
Or does it only work on microcontrollers? (which ones?)

Issue with dot on PYBD_SF6

I am having an issue running np.dot(). I am running on PYBD-SF6 using version 0.26.5. It appears that a row matrix times a square matrix works fine. However, when I use dot with a square matrix times a column matrix, the results aren't as expected. It appears to only provide the first element in the expected result (14,32,50)

`MicroPython v1.12-92-gd89ed3e62-dirty on 2020-01-26; PYBD-SF6W with STM32F767IIK
Type "help()" for more information.

import ulab as np
print('you are running ulab version', np.version)
you are running ulab version 0.26.5
a = np.array(([1,2,3],[4,5,6],[7,8,9]))
b = np.array(([1,2,3]))
b.transpose()
c = np.dot(a,b)
c
array([14.0, 0.0, 0.0], dtype=float)

`

Minor inconsistency in accessed the one element from ndarray

I'm not sure if this is intentional:
>>> a = np.array([[1,2,3],[4,5,6],[7,8,9]])
>>> a[1,1]
array([5.0], dtype=float)
>>> a[1][1]
5.0
why in the first case we get an array while in second case a numeric value? In numpy, both result into a numeric value rather in array container.

Wish list

This is the place, where suggestions related to new functions/functionality and the like should be brought up.

argmin/argmax of iterable: results differ from ndarray and numpy

>>> ulab.numerical.argmax([[1,2],[4,3]])
1
>>> ulab.numerical.argmin([[1,2],[4,3]])
0

but

>>> ulab.numerical.argmin(ulab.array([[1,2],[4,3]]))
0
>>> ulab.numerical.argmax(ulab.array([[1,2],[4,3]]))
2

and

>>> numpy.argmin([[1,2],[4,3]])
0
>>> numpy.argmax([[1,2],[4,3]])
2

Epsilon double promotion warning/error.

MICROPY_FLOAT_IMPL_FLOAT turns on -Wdouble-promotion and -fsingle-precision-constant which causes epsilon literal to be float and an implicit conversion from 'float' to 'double' (to match fabs) warning/error. Maybe fabsf should be used instead...

QSTR not updated
CC ../../extmod/ulab/code/linalg.c
../../extmod/ulab/code/linalg.c: In function 'linalg_invert_matrix':
../../extmod/ulab/code/linalg.c:127:32: error: implicit conversion from 'float' to 'double' to match other operand of binary expression [-Werror=double-promotion]
         if(fabs(data[m*(N+1)]) < epsilon) {
                                ^
../../extmod/ulab/code/linalg.c: In function 'linalg_det':
../../extmod/ulab/code/linalg.c:310:35: error: implicit conversion from 'float' to 'double' to match other operand of binary expression [-Werror=double-promotion]
         if(fabs(tmp[m*(in->n+1)]) < epsilon) {
                                   ^
../../extmod/ulab/code/linalg.c: In function 'linalg_eig':
../../extmod/ulab/code/linalg.c:349:24: error: implicit conversion from 'float' to 'double' to match other operand of binary expression [-Werror=double-promotion]
             if(epsilon < fabs(array[m*in->n + n] - array[n*in->n + m])) {
                        ^
cc1: all warnings being treated as errors

No need to define MODULE_ULAB_ENABLED in mpconfigport.h

Since the micropython.mk file has added the following line

CFLAGS_EXTRA = -DMODULE_ULAB_ENABLED=1

There's no need to add #define MODULE_ULAB_ENABLED (1) in the mpconfigport.h.Or it would make redefined errors while compiling code.

Array shapes where one dimension is length 1 not properly represented

Currently ulab fails to properly displays arrays where the length of one of the dimensions is 1. Furthermore it incorrectly represents the shape of arrays with only one dimension.

To see this consider the following code for regular Python and numpy:

>>> import numpy as np
>>> aa = np.array(range(3))
>>> aa, aa.shape
(array([0, 1, 2]), (3,))
>>> bb = np.array(range(3)).reshape((1,3))
>>> bb, bb.shape
(array([[0, 1, 2]]), (1, 3))
>>> cc = np.array(range(3)).reshape((3,1))
>>> cc, cc.shape
(array([[0],
       [1],
       [2]]), (3, 1))

Compare this with the equivalent code in Micropython with ulab:

>>> import ulab as np
>>> aa = np.array(range(3))
>>> aa, aa.shape()
(array([0.0, 1.0, 2.0], dtype=float), (1, 3))
>>> bb = np.array(range(3)).reshape((1,3))
>>> bb, bb.shape()
(array([0.0, 1.0, 2.0], dtype=float), (1, 3))
>>> cc = np.array(range(3)).reshape((3,1))
>>> cc, cc.shape()
(array([0.0, 1.0, 2.0], dtype=float), (3, 1))

In the aa case the shape is misrepresented as being two dimensional even though it is printed as one dimensional. In the bb and cc cases the shape is correctly displayed but the array is printed as if it were one dimensional.

Two dimensional arrays where the length of both dimensions is greater than one are printed correctly and have the shape correctly represented.

ulab std method for matrix

Symptom:
ulab.std() sometimes produce nan in the result
ulab version 0.25

Reproduce:
import ulab as np
a = np.array([[109.5894, 117.2053, 69.3245],
[109.5894, 117.1788, 69.3245],
[109.596, 117.1854, 69.3245],
[109.596, 117.1854, 69.3245],
[109.5894, 117.1722, 69.31788],
[109.5894, 117.1921, 69.31788],
[109.5894, 117.1854, 69.31788],
[109.596, 117.1656, 69.31788],
[109.596, 117.1589, 69.31126],
[109.596, 117.1788, 69.30463],
[109.596, 117.1656, 69.29801],
[109.596, 117.1391, 69.29801],
[109.5894, 117.1656, 69.29801],
[109.5828, 117.1523, 69.29801],
[109.5762, 117.1391, 69.29139],
[109.5695, 117.1457, 69.29139],
[109.5762, 117.1457, 69.29139],
[109.5762, 117.1258, 69.28477],
[109.5695, 117.1192, 69.28477],
[109.5695, 117.0861, 69.28477],
[109.5695, 117.0728, 69.27814],
[109.5695, 117.0728, 69.27814],
[109.5695, 117.0861, 69.28477],
[109.5629, 117.0861, 69.29139],
[109.5695, 117.1059, 69.29139],
[109.5695, 117.1126, 69.28477],
[109.5762, 117.1059, 69.28477],
[109.5828, 117.1258, 69.29139],
[109.5762, 117.1457, 69.29139],
[109.5762, 117.1324, 69.29139],
[109.5762, 117.1192, 69.28477],
[109.5762, 117.1192, 69.28477],
[109.5695, 117.1391, 69.28477],
[109.5695, 117.1059, 69.28477],
[109.5762, 117.1457, 69.28477],
[109.5828, 117.1258, 69.28477],
[109.5828, 117.0993, 69.28477],
[109.5828, 117.1457, 69.28477],
[109.5828, 117.1523, 69.29139],
[109.5894, 117.0993, 69.28477],
[109.5828, 117.1059, 69.27814],
[109.5762, 117.1457, 69.27814],
[109.5695, 117.1126, 69.27814],
[109.5695, 117.1192, 69.27814],
[109.5695, 117.1457, 69.28477],
[109.5695, 117.1324, 69.29139],
[109.5695, 117.1192, 69.28477],
[109.5762, 117.1523, 69.29139],
[109.5695, 117.1722, 69.29801],
[109.5695, 117.1523, 69.29801],
[109.5629, 117.1391, 69.29801],
[109.5695, 117.1722, 69.30463],
[109.5762, 117.1391, 69.31126],
[109.5762, 117.0927, 69.30463],
[109.5695, 117.1391, 69.30463],
[109.5695, 117.0927, 69.30463],
[109.5629, 117.053, 69.30463],
[109.5629, 117.0795, 69.30463],
[109.5563, 117.0728, 69.30463],
[109.5497, 117.0265, 69.29801],
[109.5497, 117.0066, 69.29139],
[109.5497, 117.0199, 69.29139],
[109.5497, 116.9868, 69.29139],
[109.5497, 116.9603, 69.29139],
[109.5497, 116.9801, 69.29801],
[109.5497, 116.9603, 69.29801],
[109.543, 116.9404, 69.29139],
[109.543, 116.9338, 69.28477],
[109.5364, 116.9536, 69.28477],
[109.543, 116.9536, 69.29139],
[109.543, 116.9603, 69.29139],
[109.5364, 116.947, 69.29139],
[109.5298, 116.9669, 69.29139],
[109.5298, 116.9801, 69.29139],
[109.5232, 116.947, 69.29139],
[109.5298, 116.9735, 69.29139],
[109.5232, 117.0066, 69.28477],
[109.5165, 117.0, 69.27814],
[109.5099, 116.9868, 69.27152],
[109.5099, 117.0132, 69.27152],
[109.5099, 117.0199, 69.27152],
[109.5099, 116.9735, 69.27152],
[109.5033, 117.0132, 69.2649],
[109.4967, 117.0662, 69.25828],
[109.4967, 117.0265, 69.25828],
[109.4967, 117.0397, 69.25828],
[109.5033, 117.0728, 69.25166],
[109.4967, 117.053, 69.25166],
[109.4967, 117.0, 69.24503],
[109.4967, 117.0331, 69.23841],
[109.4901, 117.0662, 69.23841],
[109.4967, 117.0464, 69.23841],
[109.4901, 117.0728, 69.23841],
[109.4901, 117.0993, 69.23841],
[109.4901, 117.0662, 69.23841],
[109.4967, 117.0199, 69.23841],
[109.4901, 117.053, 69.23841],
[109.4901, 117.053, 69.23841],
[109.4901, 117.0199, 69.23841],
[109.4834, 117.0596, 69.23841]])

print(np.std(a,axis=0))
Result:
array([nan, nan, nan], dtype=float)

Expected result:
array([0.03424415, 0.07198399, 0.02212126], dtype=float)

Compiling for Nucleo-L476RG

Some changes had to be made to compile ulab into MicroPython v1.12-93 for the Nucleo-L476RG. After the following changes were made, everything seems to work well. NOTE: I'm new to building custom C modules for MicroPython, and these problems may have been caused by my doing something (or many somethings) incorrectly.

  • mp_const_none_obj, mp_const_true_obj, and mp_const_false_obj were unrecognized. Replacing macros such as MP_ROM_PTR(&mp_const_none_obj) with corresponding macros MP_ROM_NONE, MP_ROM_TRUE, and MP_ROM_FALSE allowed successful compilation.

  • As I intend to compile other external C code as well as ulab, it is appropriate to place the ulab directory one level under the main external C code directory, which I've placed inside the root MicroPython directory (is that a faux pas?). The ulab source files are then only found if moved from the ulab/code subdirectory to the main ulab directory. A workaround which seems to work is to move micropython.mk to the main ulab directory, leave the source files in ulab/code, and edit micropython.mk to specify the location of the source and header files as follows:

USERMODULES_DIR := $(USERMOD_DIR)

# Add all C files to SRC_USERMOD.
SRC_USERMOD += $(USERMODULES_DIR)/code/ndarray.c
SRC_USERMOD += $(USERMODULES_DIR)/code/linalg.c
SRC_USERMOD += $(USERMODULES_DIR)/code/vectorise.c
SRC_USERMOD += $(USERMODULES_DIR)/code/poly.c
SRC_USERMOD += $(USERMODULES_DIR)/code/fft.c
SRC_USERMOD += $(USERMODULES_DIR)/code/numerical.c
SRC_USERMOD += $(USERMODULES_DIR)/code/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)/code

Instructions from the ulab README as well as the MicroPython external C modules page were used to duct tape this solution together. I suspect that the directory problems are my own doing, but I'm not sure about the compiler not recognizing mp_const_none_obj, etc.

Happy to supply more details, diffs, patches, or whatever...as soon as I've gone and learned how to generate them correctly.

Subscripting 2-D array returns an array not a single value

When subscripting a 1-D array with an integer a single value is returned but when subscripting a 2-D array with two integers the result is a 1x1 array containing the value. For example:

>>> a = np.array(range(6))
>>> a[4]
4.0
>>> b = np.array(range(6)).reshape((2,3))
>>> b[1,1]
array([4.0], dtype=float)
>>> _.shape()
(1, 1)

Note that in numpy the behaviour of subscripting is simple: dimensions that are indexed with a single value are removed from the result while dimensions that are indexed with a slice or which are not index at all (i.e. there are fewer indices than dimensions) remain. Thus when indexing an array b with 2 dimensions b[0,0] returns a single value, b[0,0:1] returns a 1-D result containing that single value and b[0:1,0:1] returns a 2-D array containing that value. Ideally ulab should do the same.

Note that if the design for N-dimensional arrays discussed in #13 is implemented then this will clearly require the implementation of a more generic subscripting system anyway.

Can't create 2d np.arrays with only 1 element

np.array([[1,2]]) should return a two dimensional array with only one element. Accessing said array with array[0] should return [1,2] and it should be passable to arguments such as argsort() which takes in an ndarray. I have not been able to create such an array with ulab. Using reshape() doesn't affect the issue.

Examples:

ulab
import ulab as np
arr = [[1,2]]
arr2 = np.array(arr)
arr2[0]
1.0

numpy
import numpy as np
arr = [[1,2]]
arr2 = np.array(arr)
arr2[0]
Out[1]: array([1, 2])

Compilation errors for unix port on macOS

I am able to compile micropython+ulab for the PYBD_SF6 but not for unix.

My batch script is the following:

mkdir build
cd build

git clone https://github.com/micropython/micropython.git
git clone https://github.com/v923z/micropython-ulab.git ulab

cd micropython
git submodule update --init

cd mpy-cross/
make

cd ../ports/stm32
echo '#define MODULE_ULAB_ENABLED (1)' | cat >> mpconfigport.h
make BOARD=PYBD_SF6 CROSS_COMPILE=/usr/local/bin/arm-none-eabi- USER_C_MODULES=../../../ulab all

cd ../unix
echo '#define MODULE_ULAB_ENABLED (1)' | cat >> mpconfigport.h
make USER_C_MODULES=../../../ulab all

When running the above code, I get a functional PUBD_SF6 firmware but the unix make results in 3 -Wparentheses-equality errors in ulab/code/ndarray.c:

CC ../../../ulab/code/ndarray.c
../../../ulab/code/ndarray.c:583:20: error: equality comparison with extraneous parentheses [-Werror,-Wparentheses-equality]
    if((ndarray->m == 1)) {
        ~~~~~~~~~~~^~~~
../../../ulab/code/ndarray.c:583:20: note: remove extraneous parentheses around the comparison to silence this warning
    if((ndarray->m == 1)) {
       ~           ^   ~
../../../ulab/code/ndarray.c:583:20: note: use '=' to turn this equality comparison into an assignment
    if((ndarray->m == 1)) {
                   ^~
                   =
../../../ulab/code/ndarray.c:891:39: error: equality comparison with extraneous parentheses [-Werror,-Wparentheses-equality]
            if((self->array->typecode == NDARRAY_INT8)) {
                ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:891:39: note: remove extraneous parentheses around the comparison to silence this warning
            if((self->array->typecode == NDARRAY_INT8)) {
               ~                      ^              ~
../../../ulab/code/ndarray.c:891:39: note: use '=' to turn this equality comparison into an assignment
            if((self->array->typecode == NDARRAY_INT8)) {
                                      ^~
                                      =
../../../ulab/code/ndarray.c:896:46: error: equality comparison with extraneous parentheses [-Werror,-Wparentheses-equality]
            } else if((self->array->typecode == NDARRAY_INT16)) {
                       ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:896:46: note: remove extraneous parentheses around the comparison to silence this warning
            } else if((self->array->typecode == NDARRAY_INT16)) {
                      ~                      ^               ~
../../../ulab/code/ndarray.c:896:46: note: use '=' to turn this equality comparison into an assignment
            } else if((self->array->typecode == NDARRAY_INT16)) {
                                             ^~
                                             =
3 errors generated.
make: *** [build/code/ndarray.o] Error 1

If I add -Wno-parentheses-equality to the CFLAGS in micropython/ports/unix/Makefile, I get different errors, in ulab/code/linalg.c this time:

CC ../../../ulab/code/ndarray.c
CC ../../../ulab/code/linalg.c
../../../ulab/code/linalg.c:127:12: error: using integer absolute value function 'abs' when argument is of floating point type [-Werror,-Wabsolute-value]
        if(abs(data[m*(N+1)]) < epsilon) {
           ^
../../../ulab/code/linalg.c:127:12: note: use function 'fabs' instead
        if(abs(data[m*(N+1)]) < epsilon) {
           ^~~
           fabs
../../../ulab/code/linalg.c:310:12: error: using integer absolute value function 'abs' when argument is of floating point type [-Werror,-Wabsolute-value]
        if(abs(tmp[m*(in->n+1)]) < epsilon) {
           ^
../../../ulab/code/linalg.c:310:12: note: use function 'fabs' instead
        if(abs(tmp[m*(in->n+1)]) < epsilon) {
           ^~~
           fabs
../../../ulab/code/linalg.c:349:26: error: using integer absolute value function 'abs' when argument is of floating point type [-Werror,-Wabsolute-value]
            if(epsilon < abs(array[m*in->n + n] - array[n*in->n + m])) {
                         ^
../../../ulab/code/linalg.c:349:26: note: use function 'fabs' instead
            if(epsilon < abs(array[m*in->n + n] - array[n*in->n + m])) {
                         ^~~
                         fabs
3 errors generated.
make: *** [build/code/linalg.o] Error 1

I suspect this might be due to some quirk of the macOS C compiler, but that's not much more than a random guess.

Is there anything obvious that I'm doing wrong?

Ambiguity in Comparison operators

I was trying to implement numpy.linalg.norm in the pythonic way, e.g., L0-norm, and got this when doing:
>>> a
array([1.2, 0.0, -1.3, 0.25, 1.45, -0.35, 2.0], dtype=float)
>>> a != 0.
True
>>> a[a != 0.]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: too many indices
I see that there isn't any mention of != comparision in the documenation. Is this intensional?

At the moment, I'm achieving L0-norm in two steps:
>>> a = array([1.2, 0.0, -1.3, 0.25, 1.45, -0.35, 2.0], dtype=float)
>>> sum([a < 0.]) + sum([a > 0.])

@v923z Do you suggest some efficient way?

Certain malfuction in numerical.sort function

I'm not sure, is this happening only in my testing! I'm running test on:

MicroPython v1.12-326-g8fff0b0ac on 2020-04-01; PYBD-SF6W with STM32F767IIK

The numerical.sort function is corrupting a certain element in the matrix:
>>> a
array([[6.058420993051577, 2.018588295338188, 6.307303740008193, 6.743567296659661], [3.760399477657426, 1.501626760992716, 1.020398368879967, 1.903483081086812], [5.20075447514754, 0.7655233559988428, 0.2730999062713479, 5.448723074844875], [3.666587663986214, 0.5528238825462162, 7.759826840359533, 9.887756673795574], [3.228270818725618, 1.784248212712498, 5.302361277197747, 6.772730290987155]], dtype=float)
>>> ulab.numerical.sort(a, axis=0)
array([[3.228270818725618, 0.5528238825462162, 0.2730999062713479, 1.903483081086812], [4.550686286587561e-314, 0.7655233559988428, 1.020398368879967, 5.448723074844875], [3.666587663986214, 1.501626760992716, 5.302361277197747, 6.743567296659661], [3.760399477657426, 1.784248212712498, 6.307303740008193, 6.772730290987155], [5.20075447514754, 2.018588295338188, 7.759826840359533, 9.887756673795574]], dtype=float)

Observe that the two elements of zero column has changed abruptly. However, this does happen when axis=1:
>>> ulab.numerical.sort(a, axis=1)
array([[2.018588295338188, 6.058420993051577, 6.307303740008193, 6.743567296659661], [1.020398368879967, 1.501626760992716, 1.903483081086812, 3.760399477657426], [0.2730999062713479, 0.7655233559988428, 5.20075447514754, 5.448723074844875], [0.5528238825462162, 3.666587663986214, 7.759826840359533, 9.887756673795574], [1.784248212712498, 3.228270818725618, 5.302361277197747, 6.772730290987155]], dtype=float)

Can you also verify at your end?

Indexing by Boolean List seems to malfunction

I'm not sure if I'm doing any mistake, but indexing using boolean list is not working as expected. See below the result:

MicroPython v1.12-167-gf020eac6a-dirty on 2020-03-06; PYBD-SF6W with STM32F767IIK
Type "help()" for more information.

import ulab as np
a = np.array(range(9), dtype=np.float)
print("a:\t", a)
a: array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], dtype=float)
print("a < 5:\t", a[a < 5])
a < 5: array([], dtype=float)

Is there any workaround? Thanks for your suggestions.

Missing .T transpose property

In numpy the transpose of an array x can be accessed using the property x.T. This is a widely used feature that should be supported.

It should be noted in numpy the T property points to the same underlying value set, with the array stride values and dimensions reversed. Unless ulab changes to track the array strides for all dimensions this may be difficult to replicate.

Feature request: clip()

Feature request: an equivalent to numpy’s clip() function. Sure, same operation can be done in a couple lines of Python, but the speed of occurring entirely in ulab space would be a big plus. Thanks.

.transpose() missing

I recently updated to version 0.32.0 and notices that the .transpose() function was not available.
`

a = np.zeros((5,3))
a.shape
(5, 3)
a.transpose
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'ndarray' object has no attribute 'transpose'
a.transpose()
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'ndarray' object has no attribute 'transpose'
`

Out of curiosity about using CMSIS library

@v923z Hi, I was wondering if there exists already optimized Math and DSP library for ARM uController and found that there is one provided by ARM itself called CMSIS, I guess you may be aware of it. I see that Micropython does use the only device driver part from CMSIS library from ST in the port stm32. I was wondering will it be possible to include CMSIS DSP library into ULAB, e.g. using arm_math.h and others? I'm not sure about the Licencing thing though!

Crash (wrong argument types not handled?)

Use Ctrl-D to exit, Ctrl-E for paste mode
>>> import ulab
>>> ulab.dot(ulab.zeros(3), 0)
Segmentation fault

I've encountered a number of these; I think it's where both arguments need to be ndarrays, but it's not caught as an exception but leads to segmentation fault / hardfault instead. Do you want them reported separately as I encounter them? (this is the only one I wrote down so far)

ndarray from image ?

How can I create an ndarray of shape (w, h, c) from a C array of pixels (uint8_t) ? I can create 1D arrays by implementing getiter and unary_op, do I need to return an iter as the items of the first iter and so on ?

Issue with np.mean

This is a great package, and I was working through the examples provided in the documentation. I had some trouble replicating the np.mean function. (See output below). I am working on the SF6 hardware on Microptyhon v1.12-43-g54a2584de-dirty using ulab version 0.263

import ulab as np
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print('a: \n', a)

a:
array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]], dtype=float)

print('sum, flat array: ', np.sum(a))

sum, flat array: 45.0

print('mean, horizontal: ', np.mean(a, axis=1))

mean, horizontal: array([2048.00048828125, 5.387879937997763e-315, 0.0], dtype=float)

print('std, vertical: ', np.std(a, axis=0))

std, vertical: array([7.191838266139812, 5.314304082212256e-315, 0.0], dtype=float)

Incorrect results from ulab.dot() in ESP32

When running ulab on the ESP32 the dot product function ulab.dot() yields erroneous results when the inputs are non-square. For instance:

>>> import ulab as np
>>> aa = np.array(range(6)).reshape((2,3))
>>> bb = np.array(range(6)).reshape((3,2))
>>> np.dot(aa,bb)
array([[2.0, 3.0],
         [8.0, 15.0]], dtype=float)
>>> np.dot(bb,aa)
array([[3.0, 4.0, 0.0],
         [9.0, 14.0, 0.0],
         [0.0, 0.0, 0.0]], dtype=float)

While regular binary operators seem to work fine the dot product function behaves very erratically. The fact that it seems to work correctly for square arrays suggest that the problem is likely some transposition of the dimension lengths.

Apparent C-syntax compiling errors

I'm attempting to compile ulab for the PYBD_SF6 from a Mac running macOS 10.13.4.

  • I cloned the 0.24 release of micropython-ulab and the current version of micropython.
  • I ran make in micropython/mpy-cross
  • I ran make BOARD=PYBD_SF6 CROSS_COMPILE=/usr/local/bin/arm-none-eabi- in micropython/ports/stm32, which successfully compiled the "normal" firmware for SF6.
  • But when I instead run make BOARD=PYBD_SF6 CROSS_COMPILE=/usr/local/bin/arm-none-eabi- USER_C_MODULES=../../../ulab all, compilation fails with the error messages below.

Am I doing something wrong here? I'm not an experienced firmware compiler.

CC ../../../ulab/code/ndarray.c
../../../ulab/code/ndarray.c: In function 'ndarray_init_helper':
../../../ulab/code/ndarray.c:157:11: error: 'MP_QSTR_dtype' undeclared (first use in this function); did you mean 'MP_QSTR_type'?
         { MP_QSTR_dtype, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = NDARRAY_FLOAT } },
           ^~~~~~~~~~~~~
           MP_QSTR_type
../../../ulab/code/ndarray.c:157:11: note: each undeclared identifier is reported only once for each function it appears in
../../../ulab/code/ndarray.c: In function 'ndarray_flatten':
../../../ulab/code/ndarray.c:648:11: error: 'MP_QSTR_order' undeclared (first use in this function); did you mean 'MP_QSTR_ord'?
         { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} },
           ^~~~~~~~~~~~~
           MP_QSTR_ord
../../../ulab/code/ndarray.c:648:11: error: initialization of 'short unsigned int' from 'const mp_arg_t *' {aka 'const struct _mp_arg_t *'} makes integer from pointer without a cast [-Werror=int-conversion]
../../../ulab/code/ndarray.c:648:11: note: (near initialization for 'allowed_args[0].qst')
../../../ulab/code/ndarray.c:648:11: error: initializer element is not constant
../../../ulab/code/ndarray.c:648:11: note: (near initialization for 'allowed_args[0].qst')
In file included from ../../py/mpstate.h:35,
                 from ../../py/runtime.h:29,
                 from ../../../ulab/code/ndarray.c:15:
../../../ulab/code/ndarray.c:648:81: error: 'MP_QSTR_C' undeclared (first use in this function); did you mean 'MP_QSTR_r'?
         { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} },
                                                                                 ^~~~~~~~~
../../py/obj.h:92:56: note: in definition of macro 'MP_OBJ_NEW_QSTR'
 #define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 2))
                                                        ^~~
../../../ulab/code/ndarray.c:648:69: note: in expansion of macro 'MP_ROM_QSTR'
         { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} },
                                                                     ^~~~~~~~~~~
../../py/obj.h:92:30: error: initializer element is not constant
 #define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 2))
                              ^
../../py/obj.h:248:24: note: in expansion of macro 'MP_OBJ_NEW_QSTR'
 #define MP_ROM_QSTR(q) MP_OBJ_NEW_QSTR(q)
                        ^~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:648:69: note: in expansion of macro 'MP_ROM_QSTR'
         { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} },
                                                                     ^~~~~~~~~~~
../../py/obj.h:92:30: note: (near initialization for 'allowed_args[0].defval.u_rom_obj')
 #define MP_OBJ_NEW_QSTR(qst) ((mp_obj_t)((((mp_uint_t)(qst)) << 2) | 2))
                              ^
../../py/obj.h:248:24: note: in expansion of macro 'MP_OBJ_NEW_QSTR'
 #define MP_ROM_QSTR(q) MP_OBJ_NEW_QSTR(q)
                        ^~~~~~~~~~~~~~~
../../../ulab/code/ndarray.c:648:69: note: in expansion of macro 'MP_ROM_QSTR'
         { MP_QSTR_order, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_QSTR(MP_QSTR_C)} },
                                                                     ^~~~~~~~~~~
cc1: all warnings being treated as errors
make: *** [build-PYBD_SF6/code/ndarray.o] Error 1

Certain inconsistency in numerical.argsort function

@v923z I'm not sure if this is intentional?

MicroPython v1.12-337-g312c69949 on 2020-04-06; PYBD-SF6W with STM32F767IIK
Type "help()" for more information.
>>> import ulab as np
>>> np.__version__
'0.41.0'
>>> a = np.array([[4,5,6],[1,2,3],[7,8,9]])
>>> np.numerical.argsort(a, axis=0)
array([[1, 1, 1],
         [0, 0, 0],
         [2, 2, 2]], dtype=uint16)
>>> a
array([[4.0, 5.0, 6.0],
         [1.0, 2.0, 3.0],
         [7.0, 8.0, 9.0]], dtype=float)
>>> np.numerical.argsort(a, axis=None)
array([3, 4, 5, 0, 1, 2, 6, 7, 8], dtype=uint16)
>>> a
array([4.0, 5.0, 6.0, 1.0, 2.0, 3.0, 7.0, 8.0, 9.0], dtype=float)

whereas the documentation of ULAB mentions: "Since during the sorting, only the indices are shuffled, argsort does not modify the input array...". The original array is flattened when axis=None. The Numpy behaves as the documentation of ULAB mentions.

indexing by boolean list is slow

Trying to optimize an inner loop, I've tried to limit the computations by indexing an array with a boolean list. unfortunately, it take waaaay more time for each iteration.
example code on circuitpython :

import ulab
import time

a = ulab.ones(1000)
b = [False]*1000
b[5:10] = [True]*5

start = time.monotonic_ns()
a = a * 2
print('time for a*2           : ', time.monotonic_ns() - start )

start = time.monotonic_ns()
a[5:10] = a[5:10] * 2
print('time for a[5:10]*2     : ', time.monotonic_ns() - start )


start = time.monotonic_ns()
a[b] = a[b] * 2
print('time for a[b] = a[b]*2 :', time.monotonic_ns() - start )

output (timing in ns) :

time for a*2           :  423000
time for a[5:10]*2     :  123000
time for a[b] = a[b]*2 : 4745000

I am testing with : Adafruit CircuitPython 5.0.0-37-geebe76997 on 2020-03-09; Adafruit Monster M4SK with samd51j19

multiple definition ulab_ndarray_type

Great work!

I want to add your ulab to MaixPy as a submodule, and I come across two problems,

  • variable multiple defined, maybe should change to extern const mp_obj_type_t ulab_ndarray_type;

const mp_obj_type_t ulab_ndarray_type;

const mp_obj_type_t ulab_ndarray_type = {
{ &mp_type_type },
.name = MP_QSTR_ndarray,
.print = ndarray_print,
.make_new = ndarray_make_new,
.subscr = ndarray_subscr,
.getiter = ndarray_getiter,
.unary_op = ndarray_unary_op,
.binary_op = ndarray_binary_op,
.locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict,
};

  • maybe should include py/obj.h in header files like fft.h

undefined reference to `roundf'

I had been download the latest verison ulab,when make it, it showed the error information as follow:

LINK build-PYBV11/firmware.elf
build-PYBV11/code/vectorise.o: In function vectorise_around': vectorise.c:(.text.vectorise_around+0x76): undefined reference to roundf'
Makefile:597: recipe for target 'build-PYBV11/firmware.elf' failed
make: *** [build-PYBV11/firmware.elf] Error 1

How to solve this issue,Thanks!

Feature requests

If you find that you would need a numpy function that is not implemented in ulab, please, open a new issue for each function you want to have implemented. This issue is locked, and cannot be appended to.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.