Matrix API Reference#
The Matrix Type#
- class pyevspace.Matrix#
- class pyevspace.Matrix(row1: Iterable, row2: Iterable, row3: Iterable)
- class pyevspace.Matrix(container: Iterable)
The Matrix type represents a matrix of a 3-dimensional vector space over the real numbers. Matrices represent linear transformations between vector spaces. In PyEVSpace, each reference frame can be thought of as a unique vector space, and matrices transform vectors between them. For users comfortable working with matrices, the Matrix type overloads most of the arithmetic operators and, provides several methods useful for working with matrices such as computing determinates, inverses, and transposing. For those not familiar with matrices, PyEVSpace has several functions for handling rotations for you, and it’s possible to leverage full use of the library without even handling matrices directly!
A matrix can be initialized by specifying each row directly, or wrapped in a container. All containers must have a length of exactly 3, where the row containers have components that are compatible with
SupportsFloat, meaning they can be converted to afloatby defining a__float__()method.If no arguments are provided the matrix components are initialized to zero.
>>> row0 = (1, 2, 3) >>> row1 = (4, 5, 6) >>> row2 = (7, 8, 9) >>> matrix = Matrix(row0, row1, row2) >>> # or via a top-level container >>> container = (row0, row1, row2) >>> matrix = Matrix(container)
Changed in version 0.16.0: Matrix is now subclassable, and is no longer an immutable type, meaning class members can be directly modified or added.
- Parameters:
row0 – an iterable object of length 3 whose values are or can be converted to a
floatrow1 – an iterable object of length 3 whose values are or can be converted to a
floatrow2 – an iterable object of length 3 whose values are or can be converted to a
floatcontainer – a container of length 3 whose values would otherwise be identical to row0, row1, and row2. If this parameter is provided it must be the only one.
- Raises:
TypeError – if any initializer is not an iterable
TypeError – if any value of an initializer is not or cannot be converted to a
floatValueError – if container or any of the sub-iterables of container are not exactly length 3
Other Constructors#
- classmethod Matrix.__new__(cls: type) Self#
Create a new, uninitialized cls object if cls is a subclass of
Matrix. All components of the matrix default to zero.Changed in version 0.16.0: type can be a subtype of
Matrix, and an object of that type will be created and returned.
- Matrix.__init__()#
- Matrix.__init__(row0: Iterable, row1: Iterable, row2: Iterable)
- Matrix.__init__(container: Iterable)
Initialize a
Matrixdepending on the parameters. This method and parameters behave identrically to theMatrixconstructor.- Parameters:
row0 – an iterable object of length 3 whose values are or can be converted to a
floatrow1 – an iterable object of length 3 whose values are or can be converted to a
floatrow2 – an iterable object of length 3 whose values are or can be converted to a
floatcontainer – a container of length 3 whose values would otherwise be identical to row0, row1, and row2. If this parameter is provided it must be the only one.
- Raises:
TypeError – if any initializer is not an iterable
TypeError – if any value of an initializer is not or cannot be converted to a
floatValueError – if container or any of the sub-iterables of container are not exactly length 3
Subclassing Matrix#
As of version 0.16.0 the Matrix class supports subclassing. As the
Matrix class is implemented as a C struct, it has a specific memory
layout, and as a consequence of that it is a @distjoint-base. This
means Matrix cannot be combined with any other types whose layout
is defined as a C struct when inheriting, which most Python built-in types
such as list or tuple are. For example combining Matrix
and list as base classes will raise a TypeError:
>>> class Foo(Matrix, list):
... pass
...
TypeError: multiple bases have instance lay-out conflict
This limitation does not apply when combining a @distjoin-base with a
pure python class, and of course is not an issue if Matrix is the only
base class for a derived type.
While Matrix supports inheritance, there is no way for the
constructor to know how to initialize an inherited type. Because of this,
any function (except Matrix.__new__()) that returns a Matrix
type will always return a Matrix instance, even on inherited types.
To change this, you will need to override the function you want to return your
type, and wrap the returned value in the constructor of your type. For example:
>>> class MyType(Matrix):
... def __init__(self, container, foo, bar):
... super().__init__(self, container)
... self.foo = foo
... self.bar = bar
... def __add__(self, other: MyType) -> MyType:
... super().__add__(other)
... return MyType(result, self.foo, self.bar)
In this example the result of the Matrix.__add__() call is used to
create a MyType instance. Any method you want to have this behavior must
be implemented yourself, including module level functions that normally
return a Matrix type.
Instance Methods#
General Protocols#
- Matrix.__len__() int#
Returns the length of the matrix object. This always returns 3.
- Returns:
the method always returns 3
- Matrix.__getitem__(row_index: int | slice, col_index: int | slice) float | memoryview#
Retrieves a portion of the matrix using the mapping protocol. If row_index and col_index are of type
int(or provide an__index__()method) the value at that index is returned. If either (or both) indices are aslicetype, amemoryviewobject is returned with attributes that map to the specified indices.- Parameters:
row_index – the row(s) of the matrix to retrieve
col_index – the column(s) of the matrix to retrieve
- Returns:
either a component of the matrix or a view of the indexed memory
- Raises:
TypeError – if row_index or col_index are not
intorslicetypesIndexError – if row_index or col_index are integers outside the range [0-2] (after adjusting for negative indexing)
- Matrix.__setitem__(row_index: int, col_index: int, value: SupportsFloat) float#
Sets a components of the matrix to value. This version of PyEVSpace currently only supports map assignment of single components, and therefore
slicearguments will raise aTypeError.- Parameters:
row_index – the row of the component to set
col_index – the col of the component to set
value – the value to assign the component at (row, col)
- Returns:
value
- Raises:
TypeError – if row_index or col_index are not
inttypes and do not provide__index__()TypeError – if value is not or cannot be converted to a
floatIndexError – if row_index or col_index are not in the range [0-2] (after adjusting for negative indexing)
- Matrix..__repr__() str#
Returns a string representation of self, representative of a constructor call with the component values of the matrix. The format of the components follows the same as
Matrix.__str__().- Returns:
a string representation of self
Changed in version 0.16.0: The method now prepends the output with the module name for better scope resolution support when using the output with
exec().
- Matrix.__str__() str#
Returns a string representation of self, formatted similarly to a
listoflists. The format of the components are either decimal of scientific notation, whichever requires fewer bytes to store their string representation.- Returns:
a string representation of self
Changed in version 0.16.0: The last two rows are padded for left alignment.
- Matrix.__reduce__()#
Allows support for pickling and deep copying. This function returns a 2-tuple containing a reference to the type constructor and a tuple of arguments to initialize an equivalent instance. For types inheriting from
Matrix, this function may beed to be overloaded and modified to return your own type.
Iterability#
Added in version 0.16.0: Matrix now supports iterability.
While the Matrix class does not define an __iter__() method
directly, the class does support iterability via the sequence protocol. Attempting
to access Matrix.__iter__() will raise an AttributeError, however
a Matrix can be used anywhere an iterable is expected. Note that when
iterating on a Matrix, a memoryview object is returned, which
itself is iterable. When trying to convert a Matrix to a different container
type, each row of the matrix will also need converting. The map() function is
useful for this:
>>> m = Matrix((1, 2, 3), (4, 5, 6), (7, 8, 9))
>>> container = list(map(lambda o: list(o), m))
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]
Arithmetic Operators#
- Matrix.__matmul__(other: Matrix) Matrix#
- Matrix.__matmul__(vector: Vector) Vector
Matrix-matrix or matrix-vector multiplcation.
- Parameters:
other – the matrix to multiply self by
vector – the vector to multiply self by
- Returns:
the result of the multiplication, the return type is the same type as the right-hand operand
Logical Operators#
- Matrix.__eq__(other: Matrix) bool#
Compares each component of two matrices for equality.
See also
Checkout
Matrix.compare_to_tol()andMatrix.compare_to_ulp()for finer control over evaluating equality.
Note
All other logic operators are not implemented and will raise a
TypeError.
Matrix Operators#
- Matrix.compare_to_tol(other: Vector, rel_tol: float = 1e-9, abs_tol: float = 1e-15) bool#
Compares self to other using tolerance based mechanics. This is the same way
Matrix.__eq__()andMatrix.__ne__()compare vectors. This function allows customization of the relative and absolute tolerance values. using the default tolerance values, this function is equivalent toMatrix.__eq__(). For comparing any two components of self and other, the components are considered equal if, fordiff = abs(a - b),diff <= abs_tol + rel_tol * max((abs(a), abs(b))).- Parameters:
other – the matrix to compare to self
rel_tol – the relative tolerance of the comparison
abs_tol – the absolute tolerance of the comparison
- Returns:
True if all components are equal, False otherwise
- Raises:
TypeError – if other is not a
Matrixtype or rel_tol or abs_tol are notfloattypes.
- Matrix.compare_to_ulp(other: Matrix, max_ulps: int) bool#
Compares self to other using ULP (Unit in the Last Place) based mechanics. The number of ULPs between two floating-point numbers is the number of representable floating-point value between the two numbers. This can be a valid way of comparing two floating-point numbers in certan domains, however for the types of projects PyEVSpace was built to support, it is not sufficient, which is why
Matrix.__eq__()uses tolerance based comparisons.The most obvious example of why ULP based comparison does not suffice for our uses is comparing two values that would be very common to encounter. The values
cos(math.pi / 4)andsin(math.pi / 4)should be identical, however because of the binary representation of floating-point values, the number of ULPs between these values is quite large. In fact these values evaluate as False using the built-in Python equality operator:>>> print(cos(pi / 4)) 0.7071067811865476 >>> print(sin(pi / 4)) 0.7071067811865475 >>> # ^ notice the rounding differences >>> print(cos(pi / 4) == sin(pi / 4)) False
Using a default number of ULPs that accommodates this equality would also allow other, less equivalent values to evaluate as equal which should not be accepted.
This method still allows users who would like an ULP based comparison to be able to use it. In some cases, a very high level of matrix equality may be waranted. If tolerance based comparison is still ideal for you but you want finer controls over the tolerance values, see
Matrix.compare_to_tol().
- Matrix.inverse() Matrix#
Computes the inverse of a matrix. A matrix whose determinate is equal to 0 is said to be singular. A singular matrix is not invertible, therefore be aware this method may not succeed. A purely rotational matrix, by definition, has a determinate of 1, so if you are calling this method on a matrix returned by
compute_rotation_matrix()you won’t have any problems with invertibility. While much more can be said about invertibile matrices, in the scope of this package the inverse of a matrix can be used to apply the opposite rotation a matrix represents.>>> v = Vector(1, 2, 3) >>> m = compute_rotation_matrix(1.5, X_AXIS) # rotation 1.5 radians around the x-axis >>> rotated_vector = v @ m >>> print(rotated_vector) [1, 3.13396, -1.78278] >>> unrotated_vector = rotated_vector @ m.inverse() # 'reverse' the rotation >>> print(unrotated_vector) [1, 2, 3] >>> print(unrotated_vector == v) True
- Returns:
the inverse of self if self is a non-singular matrix
- Raises:
ValueError – if self is a singular matrix, i.e.
self.determinate() == 0.0
- Matrix.transpose() Matrix#
Creates a new matrix equal to the transpose of self. The ith row and jth column component of the resulting matrix will be the jth row and ith column component of self.
- Returns:
the transpose of self
- Matrix.transpose_inplace() None#
Same as
Matrix.transpose()except the calling matrix is modified in place. This would be equivalent tom = m.transpose().- Returns:
None
Attributes#
- Matrix.IDENTITY#
Identity matrix with ones on the diagonal and zeros everwhere else. This value should not be edited.
- Value:
Matrix((1, 0, 0), (0, 1, 0), (0, 0, 1))- Type:
- Matrix.id#
Removed in version 0.15.0: Use
Matrix.IDENTITY
Module Methods#
- pyevspace.det(matrix)#
Removed in version 0.15.0: Use
Matrix.determinate()
- pyevspace.transpose(matrix)#
Removed in version 0.15.0: Use
Matrix.transpose()
Buffer Protocol#
- Matrix.__buffer__(flags: int) memoryview#
Returns a
memoryviewobject with self as the reference object.- Parameters:
flags – a combined enumerated value from
inspect.BufferFlagsto control the exportedmemoryviewobject- Returns:
the exported
memoryviewobject
The Matrix class supports the buffer protocol and can be used
by other objects which also support the buffer interface. For example
it can be used to instantiate a memoryview object
>>> matrix = Matrix((1, 2, 3), (4, 5, 6), (7, 8, 9))
>>> view = memoryview(matrix)
>>> view[1, 1] = 1.69
>>> print(matrix)
[[1, 2, 3]
[4, 1.69, 6]
[7, 8, 9]]
This can also be used for interfacing with buffer supporting libraries like NumPy
>>> import numpy as np
>>> from pyevspace import Matrix
>>>
>>> matrix = Matrix()
>>> print(matrix)
[[0, 0, 0]
[0, 0, 0]
[0, 0, 0]]
>>> # give arr access to the underlying data buffer of matrix
>>> arr = np.ndarray((3, 3), buffer=matrix)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
>>> # changes to arr also modify matrix as they share the same memory
>>> arr[0, 0] = 10
>>> print(arr)
[[10. 0. 0.]
[ 0. 0. 0.]
[ 0. 0. 0.]]
>>> print(matrix)
[[10, 0, 0]
[0, 0, 0]
[0, 0, 0]]
MatrixView Type#
- class pyevspace._MatrixView#
Note
This type is not part of the public API and should not be instantiated directly. It is returned internally by
Matrix.__getitem__()when asliceis used and exposes amemoryviewof the underlying matrix data.