Module umatrix, ulinalg
These are intended to be relatively small modules for use with MicroPython (Python3) which provide a minimal matrix class for representation, manipulation and a few linear algebra routines.
The matrix module is designed to offer close functional compatibility with 2-D Numpy arrays.
These routines are not designed to be particularly fast.
(eg. 3x3 matrix inversion takes about 350ms on a PyBoard.)
- umatrix.py - matrix class.
- ulinalg.py - supporting linear algebra routines (requires
- ulinalg_tests.py - testing file to check most of the features.
Currently supported: (see following sections and Properties, Methods and Functions for descriptions)
- slicing (the third step argument is not supported)
- matrix/scaler element wise arithmetic operations
- Note: scaler OP matrix operations fail under MicroPython as reflected operations are not yet fully supported. Operations need to be arranged in a matrix OP scaler form. See the Implementation Notes section.
- iteration support
- sub-matrix assignment
- determinant and inverse
- pseudo inverse (may need work)
- dot product
- cross product
Supported matrix element types default to bool and int. Support for float and complex are determined by the module upon importing
and will depend on MicroPython compilation options and platform.
All elements will be converted to the highest indexed type in the order of the above list (ie. if there is a float in the data, all elements will be converted to float).
The result is held in
umatrix.dtype. The kwarg
dtype= may also be used to force the type.
A matrix can be constructed using a list of lists representation, where each list member is a row in the matrix.
import umatrix X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]])
Or by supplying a list of elements and the distance (strides) to move to get to the next column (
cstride) and row (
X = umatrix.matrix([0,1,2,3,4,5,6,7,8,9,10,11], cstride=1, rstride=3)
Both the above will result in:
X = mat([[0 , 1 , 2 ], [3 , 4 , 5 ], [6 , 7 , 8 ], [9 , 10, 11]])
X = umatrix.matrix([0,1,2,3,4,5,6,7,8,9,10,11], cstride=1, rstride=3, dtype=float)
X = mat([[0.0 , 1.0 , 2.0 ], [3.0 , 4.0 , 5.0 ], [6.0 , 7.0 , 8.0 ], [9.0 , 10.0, 11.0]])
>>> import umatrix >>> X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) >>> X mat([[0 ,1 ,2 ], [3 ,4 ,5 ], [6 ,7 ,8 ], [9 ,10,11]]) >>>
>>> X[1,3] # Returns a single element as int, float, complex >>> 6 >>>
>>> X # Returns a row matrix (Numpy returns a vector) mat([[3, 4, 5]]) >>>
>>> X[1,:] # Returns a row matrix (Numpy returns a vector) mat([[3, 4, 5]]) >>>
>>> X[:,1] # Returns a column matrix (Numpy returns a vector) mat([[1 ], [4 ], [7 ], ]) >>>
>>> X[:,1:3] # Returns a submatrix of every row by columns 1 and 2 mat([[1 , 2 ], [4 , 5 ], [7 , 8 ], [10, 11]]) >>>
>>> X[1:3,2:4] # Returns a submatrix mat([[5, 6], [8, 9]]) >>>
Matrix elements can be assigned to using bool, int, float, complex, list or from a matrix. A list will assign elements in order from the source and wrap around if required.
>>>X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) >>>X[1,:] = [20, 21, 22] X mat([[0 , 1 , 2 ], [20, 21, 22], [6 , 7 , 8 ], [9 , 10, 11]])
>>>X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) >>>X[:,1] = [20, 21, 22] X mat([[0 , 20, 2 ], [3 , 21, 5 ], [6 , 22, 8 ], [9 , 20, 11]]) # wraps around to the first element of the source list
>>>X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) >>>X[1:3,1:3] = [20, 21, 22, 23] X mat([[0 , 1 , 2 ], [3 , 20, 21], [6 , 22, 23], [9 , 10, 11]])
Iterating over a matrix will return the rows as a list of 1xn matrices (Numpy returns a list of vectors).
>>>X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) >>>[i for i in X] [mat([[0, 1, 2]]), mat([[3, 4, 5]]), mat([[6, 7, 8]]), mat([[9 , 10, 11]])]
Iterating over a slice of a matrix will return a list of elements.
>>>X = umatrix.matrix([[0,1,2],[3,4,5],[6,7,8],[9,10,11]]) [i for i in X[1,:]] [3, 4, 5]
Differences from Numpy Arrays
- Slices are always a view in Numpy, in umatrix they are currently not a view
- Scaler as left hand side argument for [+,-,*,,\] operations are not supported in umatrix (see below) but a matrix as left hand side is.
- Single row/col slices are 1-D arrays in Numpy and a 1xn (row) or nx1 (column) matrix in umatrix
- Numpy has a 0-d array,
numpy.array(2)a special vector that acts like a scaler.
- umatrix doesn't support NaN, Inf, -Inf
umatrix module attempts to determine the supported types and floating point epsilon if float is supported.
The results are held in
umatrix.dtype holds the default type used by some
ulinalg routines (float if supported) .
For example flt_eps, stypes under a few different platforms:
# PyBoard = 1.19E-7 , [<class 'bool'>, <class 'int'>, <class 'float'>, <class 'complex'>] # WiPy = 1 , [<class 'bool'>, <class 'int'>] # Linux (uPy) = 1.19E-7 , [<class 'bool'>, <class 'int'>, <class 'float'>, <class 'complex'>] # Linux (CPython) = 2.22E-16, [<class 'bool'>, <class 'int'>, <class 'float'>, <class 'complex'>]
- uPy under Linux forces
flt_eps = 1.19E-7since it appears that the underlying math functions use doubles but uPy operates with single precision.
flt_epsis kind of irrelevant when all the work is done on one platform but the
ulinalg_testfile uses it to determine matrix equality.
+,-,*,\,\\ Scaler operations
Matrices used as the LH argument of a scaler operation will work for element wise operation. Using a scaler as the RH argument does not work as reflected operations are not yet supported by MicroPython.
Negation of a matrix does work.
2+X # does not work X+2 # matrix plus element-wise addition works 2-X # does not work -X+2 # works 2*X # does not work X*2 # works
Scaler / matrix can be accomplished using the
2.0/X # does not work X.reciprocal()*2.0 # does work (1/x * 2.0)
The reason seems to be that the MicroPython int class __add__ method (for example) does not raise NotImplementedError and therefore the
umatrix __radd__ method is not invoked.
X == Y returns
True if all elements of X and Y are equal and the they have the same shape.
The following functions are available:
umatrix.matrix_equal(X, Y)- boolean indicating same data and shape.
umatrix.matrix_equv(X, Y, tol=0)- boolean indication same data (within tol) and broadcastable.
umatrix.matrix.isclose(X, Y, rtol=1.0E-5, atol=flt_eps)- boolean matrix indicating element wise equality (within tol).
Float and complex default to determining equality within
X == Y returns a boolean matrix indicating element equality.
To get a similar result to Numpy, use
Properties of umatrix.matrix
Returns the shape as a tuple (m, n).
shape = (p, q)
In place shape change (not a copy)
Returns True if a square matrix
Convenience property to return a transposed view
Methods of umatrix.matrix
Returns a copy of the matrix
axis=0 size (n*m) for Numpy compatibility
Returns a copy of the matrix with a the shape (m, n)
Returns a view of the matrix transpose
Returns a matrix with element wise reciprocals by default (or n/element). For use in scaler division. See Implementation Notes.
apply(func, *args, **kwargs)
Call a scalar function on each element, returns a new matrix passes *args and **kwargs to func unmodified note: this is not useful for matrix-matrix operations
y = x.apply(math.sin) y = x.apply(lambda a,b: a>b, 5) # equivalent to y = x > 5 y = x.apply(operators.gt, 5) # equivalent to y = x > 5 (not in MicroPython)
Functions provided by umatrix module
isclose(X, Y, rtol=1.0E-5, atol=flt_eps)
Returns True if matrices X and Y have the same shape and the elements are within
umatrix.flt_epsfor use with
matrix_equal(X, Y, tol=0)
Returns a boolean matrix indicating element equality. X and Y must be the same shape. Similar to Numpys
Returns a boolean indicating if X and Y share the same data and are broadcastable
Functions provided by ulinalg module
Returns the closest floating point number within machine tolerance considered to be different from 'a'.
Ref. numpy.spacing(), MATLAB/Octave eps() functions
zeros(m, n, dtype=umatrix.ddtype)
Returns a m x n matrix filled with zeros.
ones(m, n, dtype=umatrix.ddtype)
Returns a m x n matrix filled with ones.
Returns a m x m matrix with the diagonal of ones.
Returns the determinant and inverse
(d, i)of X if it exists. Uses Gaussian Elimination to get an upper triangular matrix.
Returns (0, ) if the matrix is singular.
Numpy.linalgprovides separate functions for
Returns the results of the pseudo inverse operation of X.
Not sure how robust this is but it works for at least one example.
The dot product operation.
cross(X, Y, axis=1)
The cross product operation for 2x2 and 3x3 matrices.
axis = 1 (default) is for Numpy compatibility.
axis = 0 is for MATLAB, Octave, SciLab compatibility.