Source code for flipper.kernel.matrix


''' A module for representing and manipulating matrices.

Provides one class: Matrix.

There are also helper functions: id_matrix and zero_matrix. '''

import numpy as np
import realalg

import flipper

def nonnegative(v):
    ''' Return if the given vector is non-negative. '''
    
    return all(x >= 0 for x in v)

def dot(a, b):
    ''' Return the dot product of the two given iterables. '''
    
    # Is it significantly faster to do:
    # c = 0
    # for x, y in zip(a, b):
    #     c += x * y
    # return c
    return sum(x * y for x, y in zip(a, b))

[docs]class Matrix: ''' This represents a matrix. ''' def __init__(self, data): assert isinstance(data, (list, tuple)) assert all(isinstance(row, (list, tuple)) for row in data) self.rows = [list(row) for row in data] self.height = len(self.rows) self.width = len(self.rows[0]) if self.height > 0 else 0 assert all(len(row) == self.width for row in self)
[docs] def copy(self): ''' Return a copy of this Matrix. ''' return Matrix([list(row) for row in self])
def __getitem__(self, index): return self.rows[index] def __repr__(self): return str(self) def __str__(self): return '[\n' + ',\n'.join('[' + ', '.join(str(entry) for entry in row) + ']' for row in self) + '\n]' # Suggestion: Make Matrices easier to read by omitting zeros. # return '[\n' + ',\n'.join('[' + ', '.join(str(entry) if entry != 0 else ' ' for entry in row) + ']' for row in self) + '\n]' def __hash__(self): return hash(tuple([self.width, self.height] + [tuple(row) for row in self])) def __len__(self): return self.height def __iter__(self): return iter(self.rows) def __eq__(self, other): return self.width == other.width and self.height == other.height and all(row1 == row2 for row1, row2 in zip(self.rows, other.rows)) def __neg__(self): return Matrix([[-x for x in row] for row in self]) def __add__(self, other): if isinstance(other, Matrix): assert self.width == other.width and self.height == other.height return Matrix([[a+b for a, b in zip(r1, r2)] for r1, r2 in zip(self, other)]) else: return self + (id_matrix(self.width) * other) def __radd__(self, other): return self + other def __sub__(self, other): if isinstance(other, Matrix): assert self.width == other.width and self.height == other.height return Matrix([[self[i][j] - other[i][j] for j in range(self.width)] for i in range(self.height)]) else: return self - (id_matrix(self.width) * other) def __rsub__(self, other): return -(self - other) def __call__(self, other): assert isinstance(other, (list, tuple)) assert self.width == 0 or self.width == len(other) return [dot(row, other) for row in self] def __mul__(self, other): if isinstance(other, Matrix): assert self.width == 0 or self.width == len(other) other_transpose = other.transpose() return Matrix([[dot(a, b) for b in other_transpose] for a in self]) else: # Multiply entry wise. return Matrix([[entry * other for entry in row] for row in self]) def __rmul__(self, other): return self * other def __pow__(self, power): assert self.is_square() if power == 0: return id_matrix(self.width) elif power == 1: return self elif power > 1: sqrt = self**(power//2) square = sqrt * sqrt if power % 2 == 1: return self * square else: return square else: # power < 0. raise ValueError('Can only raise matrices to non-negative powers.')
[docs] def is_square(self): ''' Return if this matrix is square. ''' return self.width == self.height
[docs] def flatten(self): ''' Return the entries of this matrix as a single flattened list. ''' return [entry for row in self for entry in row]
[docs] def transpose(self): ''' Return the transpose of this matrix. ''' return Matrix(list(zip(*self.rows)))
[docs] def join(self, other): ''' Return the matrix:: (self ) (-----) (other) This is the same as Sages Matrix.stack() function. ''' return Matrix(self.rows + other.rows)
[docs] def tweak(self, increment, decrement): ''' Return a copy of this matrix where each increment entry has been increased by 1 and each decrement entry has been decreased by 1. ''' rows = [list(row) for row in self] for (i, j) in increment: rows[i][j] += 1 for (i, j) in decrement: rows[i][j] -= 1 return Matrix(rows)
[docs] def elementary(self, i, j, k=1): ''' Return the matrix obtained by performing the elementary move: replace row i by row i + k * row j. ''' return Matrix([self[n] if n != i else [x+k*y for x, y in zip(self[i], self[j])] for n in range(self.height)])
[docs] def nonnegative_image(self, v): ''' Return if self * v >= 0. ''' return all(dot(row, v) >= 0 for row in self)
[docs] def directed_eigenvector(self, condition_matrix): ''' Return an `interesting` (eigenvalue, eigenvector) pair which lives inside of the cone C, defined by condition_matrix. See realalg for the definition of `interesting`. Raises a ComputationError if it cannot find an interesting vectors in C. Assumes that C contains at most one interesting eigenvector. ''' M = np.array(self.rows, dtype=object) for eigenvalue, eigenvector in realalg.eigenvectors(M): if condition_matrix.nonnegative_image(eigenvector): return eigenvalue, eigenvector raise flipper.ComputationError('No interesting eigenvalues in cell.')
############################################## # Some helper functions for building matrices. def id_matrix(dim): ''' Return the identity matrix of given dimension. ''' return Matrix([[1 if i == j else 0 for j in range(dim)] for i in range(dim)]) def zero_matrix(width, height=None): ''' Return the zero matrix of given dimensions. ''' if height is None: height = width return Matrix([[0] * width for _ in range(height)])