lintrans.matrices package

Module contents

This package supplies classes and functions to parse, evaluate, and wrap matrices.

Expression syntax

Note

All whitespace is ignored, except the whitespace between numbers in anonymous matrices.

Documentation on correct expression syntax is given here. This is a basic summary:

  • A single matrix is written as a capital letter like A, rot(x), where x is a real number, or as an anonymous matrix

  • An anonymous matrix is of the form [a b; c d] where a, b, c, and d are real numbers

  • When a matrix is given as rot(x), this means that it represents an anticlockwise rotation by x degrees

  • Matrix A multiplied by matrix B is written as AB

  • Matrix A plus matrix B is written as A+B

  • Matrix A minus matrix B is written as A-B

  • Matrix A to the power of n is written as A^n or A^{n} where n is a positive or negative integer

  • The transpose of matrix A is written as A^T or A^{T}

  • Any matrix may be multiplied by a real constant, like 3A, or 1.2B

Note

A^2 3B will be interpreted as A^{23}B, not A^{2}3B. Braces are needed to clarify this. However, A^2 + 3B will be interpreted as expected.

Here is the technical BNF schema used by lintrans.matrices.parse.parse_matrix_expression() and lintrans.matrices.parse.validate_matrix_expression():

expression        ::=  [ "-" ] matrices { ( "+" | "-" ) matrices };
matrices          ::=  matrix { matrix };
matrix            ::=  [ real_number ] matrix_identifier [ index ] | "(" expression ")" | anonymous_matrix;
matrix_identifier ::=  "A" .. "Z" | "rot(" [ "-" ] real_number ")";
index             ::=  "^{" index_content "}" | "^" index_content;
index_content     ::=  [ "-" ] integer_not_zero | "T";

anonymous_matrix  ::= "[" real_number " " real_number ";" real_number " " real_number "]";

digit_no_zero     ::=  "1" .. "9";
digit             ::=  "0" | digit_no_zero;
digits            ::=  digit | digits digit;
integer_not_zero  ::=  digit_no_zero [ digits ];
real_number       ::=  ( integer_not_zero [ "." digits ] | "0" "." digits );

Note

In the GUI, commas are also acceptable in an input expression, as long as everything between commas is valid on its own. These commas are used exclusively in the animation to animate an expression in steps.

Submodules

lintrans.matrices.parse module

This module provides functions to parse and validate matrix expressions.

class lintrans.matrices.parse.ExpressionParser[source]

Bases: object

A class to hold state during parsing.

Most of the methods in this class are class-internal and should not be used from outside.

This class should be used like this:

>>> ExpressionParser('3A^-1B').parse()
[[('3', 'A', '-1'), ('', 'B', '')]]
>>> ExpressionParser('4(M^TA^2)^-2').parse()
[[('4', 'M^{T}A^{2}', '-2')]]
__init__(expression: str)[source]

Create an instance of the parser with the given expression and initialise variables to use during parsing.

__repr__() str[source]

Return a simple repr containing the expression.

property _char: str

Return the character pointed to by the pointer.

_parse_anonymous_identifer() None[source]

Parse an anonymous matrix, including the square brackets.

_parse_exponent() None[source]

Parse a matrix exponent from the expression and pointer.

The exponent must be an integer or T for transpose.

Raises

MatrixParseError – If we fail to parse this part of the token

_parse_matrix() bool[source]

Parse a full matrix using _parse_matrix_part().

This method will parse an optional multiplier, an identifier, and an optional exponent. If we do this successfully, we return True. If we fail to parse a matrix (maybe we’ve reached the end of the current multiplication group and the next char is +), then we return False.

Returns bool

Success or failure

_parse_matrix_part() bool[source]

Parse part of a matrix (multiplier, identifier, or exponent).

Which part of the matrix we parse is dependent on the current value of the pointer and the expression. This method will parse whichever part of matrix token that it can. If it can’t parse a part of a matrix, or it’s reached the next matrix, then we just return False. If we succeeded to parse a matrix part, then we return True.

Returns bool

Success or failure

Raises

MatrixParseError – If we fail to parse this part of the matrix

_parse_multiplication_group() None[source]

Parse a group of matrices to be multiplied together.

This method just parses matrices until we get to a +.

_parse_multiplier() None[source]

Parse a multiplier from the expression and pointer.

This method just parses a numerical multiplier, which can include zero or one . character and optionally a - at the start.

Raises

MatrixParseError – If we fail to parse this part of the matrix

_parse_rot_identifier() None[source]

Parse a rot()-style identifier from the expression and pointer.

This method will just parse something like rot(12.5). The angle number must be a real number.

Raises

MatrixParseError – If we fail to parse this part of the matrix

_parse_sub_expression() None[source]

Parse a parenthesized sub-expression as the identifier.

This method will also validate the expression in the parentheses.

Raises

MatrixParseError – If we fail to parse this part of the matrix

parse() MatrixParseList[source]

Fully parse the instance’s matrix expression and return the MatrixParseList.

This method uses all the private methods of this class to parse the expression in parts. All private methods mutate the instance variables.

Returns

The parsed expression

Return type

MatrixParseList

exception lintrans.matrices.parse.MatrixParseError[source]

Bases: Exception

A simple exception to be raised when an error is found when parsing.

class lintrans.matrices.parse.MatrixToken[source]

Bases: object

A simple dataclass to hold information about a matrix token being parsed.

__eq__(other)

Return self==value.

__hash__ = None
__init__(multiplier: str = '', identifier: str = '', exponent: str = '') None
__repr__()

Return repr(self).

exponent: str = ''
identifier: str = ''
multiplier: str = ''
property tuple: Tuple[str, str, str]

Create a tuple of the token for parsing.

lintrans.matrices.parse.NAIVE_CHARACTER_CLASS = '[-+\\sA-Z0-9.rot()^{}\\[\\];]'

This is a RegEx character class that just holds all the valid characters for an expression.

See validate_matrix_expression() to actually validate matrix expressions.

lintrans.matrices.parse.compile_naive_expression_pattern() Pattern[str][source]

Compile the single RegEx pattern that will match a valid matrix expression.

lintrans.matrices.parse.find_sub_expressions(expression: str) List[str][source]

Find all the sub-expressions in the given expression.

This function only goes one level deep, so may return strings like 'A(BC)D'.

Raises

MatrixParseError – If there are unbalanced parentheses

lintrans.matrices.parse.get_matrix_identifiers(expression: str) Set[str][source]

Return all the matrix identifiers used in the given expression.

This method works recursively with sub-expressions.

lintrans.matrices.parse.parse_matrix_expression(expression: str) MatrixParseList[source]

Parse the matrix expression and return a MatrixParseList.

Example

>>> parse_matrix_expression('A')
[[('', 'A', '')]]
>>> parse_matrix_expression('-3M^2')
[[('-3', 'M', '2')]]
>>> parse_matrix_expression('1.2rot(12)^{3}2B^T')
[[('1.2', 'rot(12)', '3'), ('2', 'B', 'T')]]
>>> parse_matrix_expression('A^2 + 3B')
[[('', 'A', '2')], [('3', 'B', '')]]
>>> parse_matrix_expression('-3A^{-1}3B^T - 45M^2')
[[('-3', 'A', '-1'), ('3', 'B', 'T')], [('-45', 'M', '2')]]
>>> parse_matrix_expression('5.3A^{4} 2.6B^{-2} + 4.6D^T 8.9E^{-1}')
[[('5.3', 'A', '4'), ('2.6', 'B', '-2')], [('4.6', 'D', 'T'), ('8.9', 'E', '-1')]]
>>> parse_matrix_expression('2(A+B^TC)^2D')
[[('2', 'A+B^{T}C', '2'), ('', 'D', '')]]
Parameters

expression (str) – The expression to be parsed

Returns

A list of parsed components

Return type

MatrixParseList

lintrans.matrices.parse.strip_whitespace(expression: str) str[source]

Strip the whitespace from the given expression, preserving whitespace in anonymous matrices.

Whitespace in anonymous matrices is preserved such that there is exactly one space in the middle of each pair of numbers, but no space after the semi-colon, like so: [1 -2;3.4 5].

lintrans.matrices.parse.validate_matrix_expression(expression: str) bool[source]

Validate the given matrix expression.

This function simply checks the expression against the BNF schema documented in Expression syntax. It is not aware of which matrices are actually defined in a wrapper. For an aware version of this function, use the is_valid_expression() method on MatrixWrapper.

Parameters

expression (str) – The expression to be validated

Returns bool

Whether the expression is valid according to the schema

lintrans.matrices.utility module

This module provides simple utility methods for matrix and vector manipulation.

lintrans.matrices.utility.create_rotation_matrix(angle: float, *, degrees: bool = True) MatrixType[source]

Create a matrix representing a rotation (anticlockwise) by the given angle.

Example

>>> create_rotation_matrix(30)
array([[ 0.8660254, -0.5      ],
       [ 0.5      ,  0.8660254]])
>>> create_rotation_matrix(45)
array([[ 0.70710678, -0.70710678],
       [ 0.70710678,  0.70710678]])
>>> create_rotation_matrix(np.pi / 3, degrees=False)
array([[ 0.5      , -0.8660254],
       [ 0.8660254,  0.5      ]])
Parameters
  • angle (float) – The angle to rotate anticlockwise by

  • degrees (bool) – Whether to interpret the angle as degrees (True) or radians (False)

Returns MatrixType

The resultant matrix

lintrans.matrices.utility.is_valid_float(string: str) bool[source]

Check if the string is a valid float (or anything that can be cast to a float, such as an int).

This function simply checks that float(string) doesn’t raise an error.

Note

An empty string is not a valid float, so will return False.

Parameters

string (str) – The string to check

Returns bool

Whether the string is a valid float

lintrans.matrices.utility.polar_coords(x: float, y: float, *, degrees: bool = False) Tuple[float, float][source]

Return the polar coordinates of a given (x, y) Cartesian coordinate.

Note

We’re returning the angle in the range \([0, 2\pi)\)

lintrans.matrices.utility.rect_coords(radius: float, angle: float, *, degrees: bool = False) Tuple[float, float][source]

Return the rectilinear coordinates of a given polar coordinate.

lintrans.matrices.utility.rotate_coord(x: float, y: float, angle: float, *, degrees: bool = False) Tuple[float, float][source]

Rotate a rectilinear coordinate by the given angle.

lintrans.matrices.utility.round_float(num: float, precision: int = 5) str[source]

Round a floating point number to a given number of decimal places for pretty printing.

Parameters
  • num (float) – The number to round

  • precision (int) – The number of decimal places to round to

Returns str

The rounded number for pretty printing

lintrans.matrices.wrapper module

This module contains the main MatrixWrapper class and a function to create a matrix from an angle.

class lintrans.matrices.wrapper.MatrixWrapper[source]

Bases: object

A wrapper class to hold all possible matrices and allow access to them.

Note

When defining a custom matrix, its name must be a capital letter and cannot be I.

The contained matrices can be accessed and assigned to using square bracket notation.

Example

>>> wrapper = MatrixWrapper()
>>> wrapper['I']
array([[1., 0.],
       [0., 1.]])
>>> wrapper['M']  # Returns None
>>> wrapper['M'] = np.array([[1, 2], [3, 4]])
>>> wrapper['M']
array([[1., 2.],
       [3., 4.]])
__eq__(other: Any) bool[source]

Check for equality in wrappers by comparing dictionaries.

Parameters

other (Any) – The object to compare this wrapper to

__getitem__(name: str) Optional[MatrixType][source]

Get the matrix with the given identifier.

If it is a simple name, it will just be fetched from the dictionary. If the identifier is rot(x), with a given angle in degrees, then we return a new matrix representing a rotation by that angle. If the identifier is something like [1 2;3 4], then we will evaluate this matrix (we assume it will have whitespace exactly like the example; see lintrans.matrices.parse.strip_whitespace()).

Note

If the named matrix is defined as an expression, then this method will return its evaluation. If you want the expression itself, use get_expression().

Parameters

name (str) – The name of the matrix to get

Returns Optional[MatrixType]

The value of the matrix (could be None)

Raises

NameError – If there is no matrix with the given name

__hash__() int[source]

Return the hash of the matrices dictionary.

__init__()[source]

Initialize a MatrixWrapper object with a dictionary of matrices which can be accessed.

__repr__() str[source]

Return a nice string repr of the MatrixWrapper for debugging.

__setitem__(name: str, new_matrix: Optional[Union[MatrixType, str]]) None[source]

Set the value of matrix name with the new_matrix.

The new matrix may be a simple 2x2 NumPy array, or it could be a string, representing an expression in terms of other, previously defined matrices.

Parameters
  • name (str) – The name of the matrix to set the value of

  • new_matrix (Optional[Union[MatrixType, str]]) – The value of the new matrix (could be None)

Raises
  • NameError – If the name isn’t a legal matrix name

  • TypeError – If the matrix isn’t a valid 2x2 NumPy array or expression in terms of other defined matrices

  • ValueError – If you attempt to define a matrix in terms of itself

evaluate_expression(expression: str) MatrixType[source]

Evaluate a given expression and return the matrix evaluation.

Parameters

expression (str) – The expression to be parsed

Returns MatrixType

The matrix result of the expression

Raises

ValueError – If the expression is invalid

get_defined_matrices() List[Tuple[str, Union[MatrixType, str]]][source]

Return a list of tuples containing the name and value of all defined matrices in the wrapper.

Returns

A list of tuples where the first element is the name, and the second element is the value

Return type

List[Tuple[str, Union[MatrixType, str]]]

get_expression(name: str) Optional[str][source]

If the named matrix is defined as an expression, return that expression, else return None.

Parameters

name (str) – The name of the matrix

Returns Optional[str]

The expression that the matrix is defined as, or None

Raises

NameError – If the name is invalid

get_expression_dependencies(expression: str) Set[str][source]

Return all the matrices that the given expression depends on.

This method just calls get_matrix_dependencies() on each matrix identifier in the expression. See that method for details.

If an expression contains a matrix that has no dependencies, then the expression is not considered to depend on that matrix. But it is considered to depend on any matrix that has its own dependencies.

get_matrix_dependencies(matrix_name: str) Set[str][source]

Return all the matrices (as identifiers) that the given matrix (indirectly) depends on.

If A depends on nothing, B directly depends on A, and C directly depends on B, then we say C depends on B and A.

is_valid_expression(expression: str) bool[source]

Check if the given expression is valid, using the context of the wrapper.

This method calls lintrans.matrices.parse.validate_matrix_expression(), but also ensures that all the matrices in the expression are defined in the wrapper.

Parameters

expression (str) – The expression to validate

Returns bool

Whether the expression is valid in this wrapper

Raises

LinAlgError – If a matrix is defined in terms of the inverse of a singular matrix

undefine_matrix(name: str) Set[str][source]

Safely undefine the given matrix by also undefining any matrices that depend on it.