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 a float by 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 float

  • row1 – an iterable object of length 3 whose values are or can be converted to a float

  • row2 – an iterable object of length 3 whose values are or can be converted to a float

  • container – 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 float

  • ValueError – 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.

Parameters:

cls – must be a subtype of Matrix

Returns:

an uninitialized Matrix object

Raises:

TypeError – if cls is not a subtype of Matrix

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 Matrix depending on the parameters. This method and parameters behave identrically to the Matrix constructor.

Parameters:
  • row0 – an iterable object of length 3 whose values are or can be converted to a float

  • row1 – an iterable object of length 3 whose values are or can be converted to a float

  • row2 – an iterable object of length 3 whose values are or can be converted to a float

  • container – 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 float

  • ValueError – 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 a slice type, a memoryview object 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 int or slice types

  • IndexError – 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 slice arguments will raise a TypeError.

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 int types and do not provide __index__()

  • TypeError – if value is not or cannot be converted to a float

  • IndexError – 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 list of list s. 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.

Returns:

a tuple used for reconstructing self’s state

Return type:

tuple[type, tuple[tulpe[float, …], …]]

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.__add__(other: Matrix) Matrix#

Standard matrix addition of two matrices.

Parameters:

other – the matrix to be added to self

Returns:

the matrix sum of self and other

Raises:

TypeError – if other is not a Matrix type

Matrix.__sub__(other: Matrix) Matrix#

Standard matrix subtraction of two matrices.

Parameters:

other – the matrix to be subtracted from self

Returns:

the matrix difference of self and other

Raises:

TypeError – if other is not a Matrix type

Matrix.__mul__(scalar: SupportsFloat) Matrix#

Scalar matrix multiplication.

Parameters:

scalar – the scalar to multiply the components of self by

Returns:

the scalar product of self and scalar

Raises:

TypeError – if scalar is not or cannot be converted to a float

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

Matrix.__truediv__(scalar: SupportsFloat) Matrix#

Standard scalar division.

Parameters:

scalar – the scalar to divide each component of self by

Returns:

the scalar quotient

Raises:

TypeError – if scalar is not or cannot be converted to a float

Matrix.__neg__() Matrix#

Negates each component of self.

Returns:

the negative of self

Matrix.__iadd__(other: Matrix) Matrix#

Inplace standard vector addition.

Parameters:

other – the matrix to add to self

Raises:

TypeError – if other is not a Matrix type

Matrix.__isub__(other: Matrix) Matrix#

Inplace standard vector subtraction.

Parameters:

other – the matrix to subtract from self

Raises:

TypeError – if other is not a Matrix type

Matrix.__imul__(scalar: SupportsFloat) Matrix#

Inplace standard scalar multiplication.

Parameters:

scalar – the scalar to multiply each element of self by

Raises:

TypeError – if scalar is not or cannot be converted to a float

Matrix.__imatmul__(other: Matrix) Matrix#

Inplace matrix multiplication.

Parameters:

other – the other matrix to multiply self inplace by

Raises:

TypeError – if other is not a Matrix type

Matrix.__itruediv__(scalar: SupportsFloat) Matrix#

Inplace standard scalar division.

Parameters:

scalar – the scalar to divide each component of self by

Raises:

TypeError – if scalar is not or cannot be converted to a float

Logical Operators#

Matrix.__eq__(other: Matrix) bool#

Compares each component of two matrices for equality.

See also

Checkout Matrix.compare_to_tol() and Matrix.compare_to_ulp() for finer control over evaluating equality.

Parameters:

other – The matrix to compare to self

Returns:

True if each component of other and self are equivalent, False otherwise

Raises:

TypeError – if other is not a Matrix type

Matrix.__ne__(other: Matrix) bool#

Compares each component of two matrices for an inequality.

Returns:

True if any component of other and self are not equivalent, False otherwise

Raises:

TypeError – if other is not a Matrix type

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__() and Matrix.__ne__() compare vectors. This function allows customization of the relative and absolute tolerance values. using the default tolerance values, this function is equivalent to Matrix.__eq__(). For comparing any two components of self and other, the components are considered equal if, for diff = 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 Matrix type or rel_tol or abs_tol are not float types.

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) and sin(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().

Parameters:
  • other – the matrix to compare to self

  • max_ulps – the maximum amount of ULPs two components can differ by and still evaluate as equal

Returns:

True if all components are equal, False otherwise

Raises:

TypeError – if other is not a Matrix type or max_ulps is not an int type

Matrix.determinate() float#

Computes the determinate of a matrix.

Returns:

the detminate of self

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 to m = 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

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 memoryview object with self as the reference object.

Parameters:

flags – a combined enumerated value from inspect.BufferFlags to control the exported memoryview object

Returns:

the exported memoryview object

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 a slice is used and exposes a memoryview of the underlying matrix data.