Compare commits

...

9 commits

Author SHA1 Message Date
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
Zoltán Vörös
8300de7f11 backup commit, absolutely nothing essential 2020-02-16 19:53:30 +01:00
16 changed files with 619 additions and 331 deletions

View file

@ -182,7 +182,6 @@ mp_obj_t fft_spectrum(size_t n_args, const mp_obj_t *args) {
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fft_spectrum_obj, 1, 2, fft_spectrum);
#if !CIRCUITPY
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 },
@ -196,6 +195,5 @@ mp_obj_module_t ulab_fft_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_fft_globals,
};
#endif
#endif

View file

@ -84,7 +84,6 @@ mp_obj_t filter_convolve(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_a
MP_DEFINE_CONST_FUN_OBJ_KW(filter_convolve_obj, 2, filter_convolve);
#if !CIRCUITPY
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 },
@ -96,6 +95,5 @@ mp_obj_module_t ulab_filter_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_filter_globals,
};
#endif
#endif

View file

@ -424,7 +424,6 @@ mp_obj_t linalg_eig(mp_obj_t oin) {
MP_DEFINE_CONST_FUN_OBJ_1(linalg_eig_obj, linalg_eig);
#if !CIRCUITPY
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 },
@ -443,6 +442,5 @@ mp_obj_module_t ulab_linalg_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_linalg_globals,
};
#endif
#endif

View file

@ -20,43 +20,42 @@
#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;
/* v923z: it is not at all clear to me, why this must be declared; it should already be in obj.h */
typedef struct _mp_obj_none_t {
mp_obj_base_t base;
} mp_obj_none_t;
const mp_obj_type_t mp_type_NoneType;
const mp_obj_none_t mp_const_none_obj = {{&mp_type_NoneType}};
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);
MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);
STATIC const mp_obj_property_t ndarray_shape_obj = {
.base.type = &mp_type_property,
.proxy = {(mp_obj_t)&ndarray_get_shape_obj,
(mp_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_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_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_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_obj_t)&mp_const_none_obj,
(mp_obj_t)&mp_const_none_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

@ -730,8 +730,8 @@ mp_obj_t numerical_argsort(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw
MP_DEFINE_CONST_FUN_OBJ_KW(numerical_argsort_obj, 1, numerical_argsort);
#if !CIRCUITPY
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_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 },
@ -753,6 +753,5 @@ mp_obj_module_t ulab_numerical_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_numerical_globals,
};
#endif
#endif

View file

@ -197,7 +197,6 @@ mp_obj_t poly_polyfit(size_t n_args, const mp_obj_t *args) {
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poly_polyfit_obj, 2, 3, poly_polyfit);
#if !CIRCUITPY
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 },
@ -210,6 +209,5 @@ mp_obj_module_t ulab_poly_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_poly_globals,
};
#endif
#endif

View file

@ -31,13 +31,17 @@
STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, "0.34.0");
MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);
STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) },
{ 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) },
#if !CIRCUITPY
{ MP_ROM_QSTR(MP_QSTR_shape), MP_ROM_PTR(&ndarray_shape_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) },
#endif
// { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) },
};
@ -56,7 +60,6 @@ const mp_obj_type_t ulab_ndarray_type = {
.locals_dict = (mp_obj_dict_t*)&ulab_ndarray_locals_dict,
};
#if !CIRCUITPY
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_obj) },
@ -101,4 +104,3 @@ mp_obj_module_t ulab_user_cmodule = {
};
MP_REGISTER_MODULE(MP_QSTR_ulab, ulab_user_cmodule, MODULE_ULAB_ENABLED);
#endif

View file

@ -135,7 +135,6 @@ 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);
#if !CIRCUITPY
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 },
@ -169,6 +168,5 @@ mp_obj_module_t ulab_vectorise_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_ulab_vectorise_globals,
};
#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.32'
release = '0.32.2'
# -- General configuration ---------------------------------------------------

View file

@ -9,11 +9,11 @@ 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,14 +63,14 @@ 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.32), the library adds
approximately 30 kB of extra compiled code to the micropython
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>`__.
@ -97,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
@ -110,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
===============================
@ -145,35 +230,6 @@ can always be queried as
If you find a bug, please, include this number in your report!
Customising ``ulab``
--------------------
``ulab`` implements a great number of functions, and it is quite
possible that you do not need all of them in a particular application.
If you want to save some flash space, you can easily exclude arbitrary
functions from the firmware. The
`https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h <ulab.h>`__
header file contains a pre-processor flag for all functions in ``ulab``.
The default setting is 1 for each of them, but if you change that to 0,
the corresponding function will not be part of 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_ACOS (1)
#define ULAB_VECTORISE_ACOSH (1)
#define ULAB_VECTORISE_ASIN (1)
#define ULAB_VECTORISE_ASINH (1)
#define ULAB_VECTORISE_ATAN (1)
#define ULAB_VECTORISE_ATANH (1)
In order to simplify navigation in the file, each flag begins with
``ULAB_``, continues with the sub-module, where the function itself is
implemented, and ends with the functions name. Each section displays a
hint as to how much space you can save by un-setting the flag.
Basic ndarray operations
------------------------
@ -193,7 +249,11 @@ calls on general iterables)
Methods of ndarrays
-------------------
`.shape <#.shape>`__
`.shape\* <#.shape>`__
`size\* <#size>`__
`itemsize\* <#itemsize>`__
`.reshape <#.reshape>`__
@ -204,8 +264,6 @@ Methods of ndarrays
Matrix methods
--------------
`size <#size>`__
`inv <#inv>`__
`dot <#dot>`__
@ -276,9 +334,10 @@ 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
@ -292,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
@ -300,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.
@ -427,6 +486,9 @@ Methods of ndarrays
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::
# code to be run in micropython
@ -455,12 +517,46 @@ rows, and columns.
**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)
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)
.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
@ -489,12 +585,44 @@ elements in the array.
**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
@ -523,6 +651,36 @@ enumber of elements in the array.
**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
~~~~~~~~
@ -2774,16 +2932,20 @@ 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)
@ -3014,7 +3176,11 @@ result.
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:
@ -3064,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
@ -3241,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 },
...
@ -3299,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

@ -24,11 +24,11 @@
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T19:06:35.427133Z",
"start_time": "2020-02-11T19:06:35.418598Z"
"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.32'\n",
"release = '0.32.2'\n",
"\n",
"\n",
"# -- General configuration ---------------------------------------------------\n",
@ -120,11 +120,11 @@
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-11T20:30:43.287360Z",
"start_time": "2020-02-11T20:30:40.308932Z"
"end_time": "2020-02-26T17:04:49.527515Z",
"start_time": "2020-02-26T17:04:21.416456Z"
}
},
"outputs": [],
@ -409,11 +409,11 @@
},
{
"cell_type": "code",
"execution_count": 110,
"execution_count": 115,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T17:34:35.250747Z",
"start_time": "2020-02-16T17:34:35.241871Z"
"end_time": "2020-02-16T18:50:42.907664Z",
"start_time": "2020-02-16T18:50:42.903709Z"
}
},
"outputs": [],
@ -489,17 +489,17 @@
"\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",
@ -508,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.32), 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",
"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",
@ -523,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)."
]
},
{
@ -576,42 +618,6 @@
"If you find a bug, please, include this number in your report!"
]
},
{
"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 [https://github.com/v923z/micropython-ulab/blob/master/code/ulab.h](ulab.h) header file contains a pre-processor flag for each sub-module. The default setting is 1 for each of them, but if you change that to 0, the corresponding sub-module will not be part of 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 is part of your `ulab` firmware, or not. The alternative, namely, that you do not have to import anything beyond `ulab`, could be catastrophic: you would learn only at run time that a particular function is not in the firmware, and that is most probably too late.\n",
"\n",
"The standard sub-modules, `vector`, `linalg`, `numerical`, `poly`, and `fft` are 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."
]
},
{
"cell_type": "markdown",
"metadata": {},
@ -633,7 +639,11 @@
"\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",
@ -643,8 +653,6 @@
"\n",
"## Matrix methods\n",
"\n",
"[size](#size)\n",
"\n",
"[inv](#inv)\n",
"\n",
"[dot](#dot)\n",
@ -886,7 +894,9 @@
"source": [
"### .shape\n",
"\n",
"The `.shape` method (property) 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., "
]
},
{
@ -930,13 +940,63 @@
"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."
"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., "
]
},
{
@ -980,13 +1040,63 @@
"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."
"The `.itemsize` method (property) returns an integer with the siz enumber of elements in the array.\n",
"\n",
"**WARNING:** In `circuitpython`:"
]
},
{
@ -1030,6 +1140,54 @@
"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": {},
@ -4321,7 +4479,9 @@
"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",
@ -4505,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",
@ -4556,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."
]
}
],

View file

@ -65,11 +65,11 @@
},
{
"cell_type": "code",
"execution_count": 533,
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T16:11:58.211135Z",
"start_time": "2020-02-16T16:11:58.205913Z"
"end_time": "2020-02-26T05:53:50.706261Z",
"start_time": "2020-02-26T05:53:50.694240Z"
}
},
"outputs": [
@ -82,7 +82,7 @@
}
],
"source": [
"%cd ../../../micropython/ports/unix/"
"%cd ../../micropython/ports/unix/"
]
},
{
@ -90,8 +90,8 @@
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-13T05:56:22.630856Z",
"start_time": "2020-02-13T05:56:22.626421Z"
"end_time": "2020-02-26T05:50:26.530343Z",
"start_time": "2020-02-26T05:50:26.521798Z"
}
},
"outputs": [],
@ -117,8 +117,8 @@
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-13T05:57:09.182667Z",
"start_time": "2020-02-13T05:57:09.165026Z"
"end_time": "2020-02-26T05:50:28.995120Z",
"start_time": "2020-02-26T05:50:28.979972Z"
}
},
"outputs": [],
@ -305,11 +305,11 @@
},
{
"cell_type": "code",
"execution_count": 23,
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-13T06:14:32.546943Z",
"start_time": "2020-02-13T06:14:32.539768Z"
"end_time": "2020-02-26T05:50:31.256457Z",
"start_time": "2020-02-26T05:50:31.239429Z"
}
},
"outputs": [],
@ -494,11 +494,11 @@
},
{
"cell_type": "code",
"execution_count": 494,
"execution_count": 50,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T11:25:21.188898Z",
"start_time": "2020-02-16T11:25:21.180645Z"
"end_time": "2020-02-26T06:20:59.074722Z",
"start_time": "2020-02-26T06:20:59.063733Z"
}
},
"outputs": [
@ -668,11 +668,11 @@
},
{
"cell_type": "code",
"execution_count": 637,
"execution_count": 38,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:39:18.147766Z",
"start_time": "2020-02-16T18:39:18.123737Z"
"end_time": "2020-02-26T06:15:40.809455Z",
"start_time": "2020-02-26T06:15:40.797803Z"
},
"code_folding": []
},
@ -1374,6 +1374,8 @@
" return self_copy;\n",
"}\n",
"\n",
"MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);\n",
"\n",
"// Binary operations\n",
"\n",
"mp_obj_t ndarray_binary_op(mp_binary_op_t op, mp_obj_t lhs, mp_obj_t rhs) {\n",
@ -1682,11 +1684,11 @@
},
{
"cell_type": "code",
"execution_count": 484,
"execution_count": 28,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T11:20:11.149608Z",
"start_time": "2020-02-16T11:20:11.143120Z"
"end_time": "2020-02-26T06:10:22.005503Z",
"start_time": "2020-02-26T06:10:21.992913Z"
}
},
"outputs": [
@ -1694,7 +1696,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"written 1789 bytes to ndarray_properties.h\n"
"written 1561 bytes to ndarray_properties.h\n"
]
}
],
@ -1722,44 +1724,43 @@
"\n",
"#include \"ndarray.h\"\n",
"\n",
"#if CIRCUITPY\n",
"typedef struct _mp_obj_property_t {\n",
" mp_obj_base_t base;\n",
" mp_obj_t proxy[3]; // getter, setter, deleter\n",
"} mp_obj_property_t;\n",
"\n",
"/* v923z: it is not at all clear to me, why this must be declared; it should already be in obj.h */\n",
"typedef struct _mp_obj_none_t {\n",
" mp_obj_base_t base;\n",
"} mp_obj_none_t;\n",
"\n",
"const mp_obj_type_t mp_type_NoneType;\n",
"const mp_obj_none_t mp_const_none_obj = {{&mp_type_NoneType}};\n",
"\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_shape_obj, ndarray_shape);\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_size_obj, ndarray_size);\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_get_itemsize_obj, ndarray_itemsize);\n",
"MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);\n",
"\n",
"STATIC const mp_obj_property_t ndarray_shape_obj = {\n",
" .base.type = &mp_type_property,\n",
" .proxy = {(mp_obj_t)&ndarray_get_shape_obj,\n",
" (mp_obj_t)&mp_const_none_obj,\n",
" (mp_obj_t)&mp_const_none_obj},\n",
" MP_ROM_NONE,\n",
" MP_ROM_NONE },\n",
"};\n",
"\n",
"STATIC const mp_obj_property_t ndarray_size_obj = {\n",
" .base.type = &mp_type_property,\n",
" .proxy = {(mp_obj_t)&ndarray_get_size_obj,\n",
" (mp_obj_t)&mp_const_none_obj,\n",
" (mp_obj_t)&mp_const_none_obj},\n",
" MP_ROM_NONE,\n",
" MP_ROM_NONE },\n",
"};\n",
"\n",
"STATIC const mp_obj_property_t ndarray_itemsize_obj = {\n",
" .base.type = &mp_type_property,\n",
" .proxy = {(mp_obj_t)&ndarray_get_itemsize_obj,\n",
" (mp_obj_t)&mp_const_none_obj,\n",
" (mp_obj_t)&mp_const_none_obj},\n",
" MP_ROM_NONE,\n",
" MP_ROM_NONE },\n",
"};\n",
"#else\n",
"\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_size_obj, ndarray_size);\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_itemsize_obj, ndarray_itemsize);\n",
"MP_DEFINE_CONST_FUN_OBJ_1(ndarray_shape_obj, ndarray_shape);\n",
"\n",
"#endif\n",
"\n",
"#endif"
]
@ -4494,11 +4495,11 @@
},
{
"cell_type": "code",
"execution_count": 639,
"execution_count": 53,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:44:23.161178Z",
"start_time": "2020-02-16T18:44:23.155314Z"
"end_time": "2020-02-26T06:21:51.334700Z",
"start_time": "2020-02-26T06:21:51.329358Z"
}
},
"outputs": [
@ -4506,7 +4507,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"written 3357 bytes to ulab.c\n"
"written 3457 bytes to ulab.c\n"
]
}
],
@ -4545,13 +4546,17 @@
"\n",
"STATIC MP_DEFINE_STR_OBJ(ulab_version_obj, \"0.33.2\");\n",
"\n",
"MP_DEFINE_CONST_FUN_OBJ_KW(ndarray_flatten_obj, 1, ndarray_flatten);\n",
"\n",
"STATIC const mp_rom_map_elem_t ulab_ndarray_locals_dict_table[] = {\n",
" { MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) },\n",
" { MP_ROM_QSTR(MP_QSTR_reshape), MP_ROM_PTR(&ndarray_reshape_obj) },\n",
" { MP_ROM_QSTR(MP_QSTR_transpose), MP_ROM_PTR(&ndarray_transpose_obj) },\n",
" { MP_ROM_QSTR(MP_QSTR_flatten), MP_ROM_PTR(&ndarray_flatten_obj) },\n",
" #if !CIRCUITPY\n",
" { MP_ROM_QSTR(MP_QSTR_shape), MP_ROM_PTR(&ndarray_shape_obj) },\n",
" { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&ndarray_size_obj) },\n",
" { MP_ROM_QSTR(MP_QSTR_itemsize), MP_ROM_PTR(&ndarray_itemsize_obj) },\n",
" #endif\n",
"// { MP_ROM_QSTR(MP_QSTR_sort), MP_ROM_PTR(&numerical_sort_inplace_obj) },\n",
"};\n",
"\n",
@ -4680,11 +4685,11 @@
},
{
"cell_type": "code",
"execution_count": 625,
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:23:04.229825Z",
"start_time": "2020-02-16T18:23:04.223300Z"
"end_time": "2020-02-26T05:53:58.238857Z",
"start_time": "2020-02-26T05:53:58.230416Z"
}
},
"outputs": [
@ -4702,11 +4707,11 @@
},
{
"cell_type": "code",
"execution_count": 626,
"execution_count": 51,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:23:06.402259Z",
"start_time": "2020-02-16T18:23:06.107914Z"
"end_time": "2020-02-26T06:21:03.988272Z",
"start_time": "2020-02-26T06:21:03.736322Z"
}
},
"outputs": [
@ -4727,31 +4732,15 @@
},
{
"cell_type": "code",
"execution_count": 634,
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:26:46.401859Z",
"start_time": "2020-02-16T18:26:45.564198Z"
"end_time": "2020-02-26T06:21:59.627556Z",
"start_time": "2020-02-26T06:21:55.037569Z"
},
"scrolled": false
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.\n",
"Including User C Module from ../../../ulab/code\n",
"GEN build-standard/genhdr/qstrdefs.collected.h\n",
"QSTR not updated\n",
"CC ../../../ulab/code/vectorise.c\n",
"CC ../../../ulab/code/ulab.c\n",
"LINK micropython\n",
" text\t data\t bss\t dec\t hex\tfilename\n",
" 505230\t 59016\t 2120\t 566366\t 8a45e\tmicropython\n"
]
}
],
"outputs": [],
"source": [
"!make USER_C_MODULES=../../../ulab all"
]
@ -4810,11 +4799,11 @@
},
{
"cell_type": "code",
"execution_count": 638,
"execution_count": 58,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-16T18:41:56.849892Z",
"start_time": "2020-02-16T18:41:56.844122Z"
"end_time": "2020-02-26T06:23:10.596014Z",
"start_time": "2020-02-26T06:23:10.583737Z"
}
},
"outputs": [

12
tests/constructors.py Normal file
View file

@ -0,0 +1,12 @@
from ulab import linalg
import ulab
print(linalg.ones(3))
print(linalg.ones((2,3)))
print(linalg.zeros(3))
print(linalg.zeros((2,3)))
print(linalg.eye(3))
print(linalg.ones(1, dtype=ulab.int8))
print(linalg.ones(2, dtype=ulab.uint8))
print(linalg.ones(3, dtype=ulab.int16))
print(linalg.ones(4, dtype=ulab.uint16))
print(linalg.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 @@
from ulab import linalg
a = linalg.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]