Untitled

 avatar
unknown
python
a year ago
9.4 kB
3
Indexable
from typing import Callable, TypeVar, Tuple, Optional, Generic, Union, List
from common.utils import randomize_int, argmax
from matrix.Vector import Vector
import numbers

T = TypeVar('T')


class Matrix(Generic[T]):

    def __init__(self, content):
        assert len(content) > 0
        assert all(len(row) > 0 for row in content)
        self.rows = len(content)
        self.cols = len(content[0])
        self.content = tuple(tuple(row) for row in content)
        assert all(self.cols == len(row) for row in content)
        if self.rows == self.cols:
            from matrix.SqMatrix import SqMatrix
            self.__class__ = SqMatrix
            self.dim = self.rows

    def __repr__(self):
        return "Matrix(" + ",\n".join(
            ["(".ljust(6) + ", ".join(map(repr, row)).ljust(6) + ")" for row in self.content]) + ")"

    def format(self, file=True):
        strs = [[e.__repr__() for e in row] for row in self.content]
        max_len = max(len(s) for row in strs for s in row)
        eq_lens = [[" " * (max_len - len(s)) + s for s in row] for row in strs]
        str_repr = "[" + "\n ".join([', '.join(row) for row in eq_lens]) + " ]"
        if file is False:
            return str_repr
        elif file is True:
            print(str_repr)
        else:
            print(str_repr, file=file)

    @staticmethod
    def tabulate(rows: int, cols: int, f: Callable[[int, int], T]) -> 'Matrix':
        return Matrix([[f(i, j) for j in range(cols)] for i in range(rows)])

    @staticmethod
    def random(rows: int, cols: int, randomizer: Callable[[], T] = randomize_int()) -> 'Matrix[T]':
        return Matrix([[randomizer() for _ in range(cols)] for _ in range(rows)])

    @staticmethod
    def zero(rows: int, cols: int) -> 'Matrix[T]':
        return Matrix([[0] * cols for _ in range(rows)])

    def __getitem__(self, items):
        return self.content[items]

    def __eq__(self, other) -> bool:
        if not isinstance(other, Matrix) or self.rows != other.rows or self.cols != other.cols:
            return False
        return all(self.content[i][j] == other[i][j] for i in range(self.rows) for j in range(self.cols))

    def __add__(self, other: 'Matrix') -> 'Matrix':
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("Matrix dimensions do not match for addition.")
        return Matrix([[self[i][j] + other[i][j] for j in range(self.cols)] for i in range(self.rows)])

    def __sub__(self, other: 'Matrix') -> 'Matrix':
        if self.rows != other.rows or self.cols != other.cols:
            raise ValueError("Matrix dimensions do not match for subtraction.")
        return Matrix([[self[i][j] - other[i][j] for j in range(self.cols)] for i in range(self.rows)])

    def suppress_rc(self, remove_row: int, remove_col: int) -> 'Matrix':
        return Matrix(
            [[self[i][j] for j in range(self.cols) if j != remove_col] for i in range(self.rows) if i != remove_row])

    def replace_col(self, k: int, v: Vector) -> 'Matrix':
        if v.dim != self.rows:
            raise ValueError("Vector dimension must be equal to the number of rows in the matrix.")
        return Matrix([[v[i] if j == k else self[i][j] for j in range(self.cols)] for i in range(self.rows)])

    def scale(self, num: T) -> 'Matrix':
        return Matrix([[num * self[i][j] for j in range(self.cols)] for i in range(self.rows)])

    def __mul__(self, other: 'Matrix') -> Union['Matrix', Vector]:
        if isinstance(other, numbers.Number):
            return self.scale(other)
        elif isinstance(other, Matrix) and self.cols == other.rows:
            return Matrix([[sum(self[i][k] * other[k][j] for k in range(self.cols)) for j in range(other.cols)] for i in
                           range(self.rows)])
        elif isinstance(other, Vector) and self.cols == other.dim:
            return Vector([sum(self[i][j] * other[j] for j in range(self.cols)) for i in range(self.rows)])
        else:
            raise RuntimeError(f"Cannot multiply {self} by {other}")

    def row_operation(self, s1: T, r1: int, s2: T, r2: int) -> 'Matrix':
        return Matrix(
            [self[i] if i != r2 else [s1 * self[r1][j] + s2 * self[r2][j] for j in range(self.cols)] for i in
             range(self.rows)])

    def scale_row(self, s: T, r: int) -> 'Matrix':
        return Matrix([[s * self[r][j] if i == r else self[i][j] for j in range(self.cols)] for i in range(self.rows)])

    def swap_cols(self, c1: int, c2: int) -> 'Matrix':
        return Matrix(
            [[self[i][c2] if j == c1 else (self[i][c1] if j == c2 else self[i][j]) for j in range(self.cols)] for i in
             range(self.rows)])

    def swap_rows(self, r1: int, r2: int) -> 'Matrix':
        return Matrix(
            [[self[r2][j] if i == r1 else (self[r1][j] if i == r2 else self[i][j]) for j in range(self.cols)] for i in
             range(self.rows)])

    def transpose(self) -> 'Matrix':
        return Matrix([[self[j][i] for j in range(self.rows)] for i in range(self.cols)])

    def adjoin_col(self, v: Vector) -> 'Matrix':
        if v.dim != self.rows:
            raise ValueError("Vector dimension must be equal to the number of rows in the matrix.")
        return Matrix([[self[i][j] for j in range(self.cols)] + [v[i]] for i in range(self.rows)])

    def adjoin_cols(self, m: 'Matrix') -> 'Matrix':
        if self.rows != m.rows:
            raise ValueError("Matrices must have the same number of rows to be adjoined.")
        return Matrix(
            [[self[i][j] for j in range(self.cols)] + [m[i][k] for k in range(m.cols)] for i in range(self.rows)])

    def extract_rows(self, keep) -> 'Matrix':
        return Matrix([self[i] for i in keep])

    def extract_cols(self, keep) -> 'Matrix':
        return Matrix([[self[i][j] for j in keep] for i in range(self.rows)])

    def adjoin_rows(self, m: 'Matrix') -> 'Matrix':
        if self.cols != m.cols:
            raise ValueError("Matrices must have the same number of columns to be adjoined.")
        return Matrix(self.content + m.content)

    def adjoin_row(self, v: Vector) -> 'Matrix':
        if v.dim != self.cols:
            raise ValueError("Vector dimension must be equal to the number of columns in the matrix.")
        return Matrix(self.content + [v.content])

    def row_vec(self, r: int) -> Vector:
        return Vector(self.content[r])

    def col_vec(self, c: int) -> Vector:
        return Vector([self[i][c] for i in range(self.rows)])

    @staticmethod
    def cols_to_matrix(vectors: List[Vector]) -> 'Matrix':
        if not vectors:
            raise ValueError("List of vectors cannot be empty.")
        if not all(len(vectors[0]) == len(v) for v in vectors):
            raise ValueError("All vectors must have the same dimension.")
        return Matrix([[v[i] for v in vectors] for i in range(len(vectors[0]))])

    @staticmethod
    def rows_to_matrix(vectors: List[Vector]) -> 'Matrix':
        if not vectors:
            raise ValueError("List of vectors cannot be empty.")
        if not all(len(vectors[0]) == len(v) for v in vectors):
            raise ValueError("All vectors must have the same dimension.")
        return Matrix([v.content for v in vectors])

    def back_substitution(self) -> Vector:
        if self.cols != self.rows + 1:
            raise ValueError("Matrix must have k rows and k+1 columns for back substitution.")
        dim = self.rows
        x = [0] * dim
        for k in range(dim - 1, -1, -1):
            x[k] = (self[k][-1] - sum(self[k][j] * x[j] for j in range(k + 1, dim))) / self[k][k]
        return Vector(x)

    def make_row_echelon(self) -> 'Matrix':
        m = self
        for k in range(0, self.rows - 1):
            pivot = m.find_pivot_row(k)
            if pivot is None:
                return None
            if pivot != k:
                m = m.swap_rows(k, pivot)
            for r in range(k + 1, self.rows):
                m = m.row_operation(1, k, -m[r][k] / m[k][k], r)
        return m

    def make_unit_diagonal(self) -> Tuple[Optional['Matrix'], T]:
        m = self
        det = 1
        for k in range(0, self.rows):
            pivot = m.find_pivot_row(k)
            if pivot is None:
                return None, 0
            if pivot != k:
                m = m.swap_rows(k, pivot)
                det *= -1
            det *= m[k][k]
            m = m.scale_row(1 / m[k][k], k)
            for r in range(0, self.rows):
                if r != k:
                    det *= -1 / m[k][k]
                    m = m.row_operation(1, k, -m[r][k] / m[k][k], r)
            m = m.replace_col(k, Vector([1 if k == c else 0 for c in range(m.rows)]))
        return m, det

    def find_pivot_row(self, c: int, epsilon: float = 0.00001):
        p = argmax(lambda r: abs(self[r][c]), range(c, self.rows))
        return p if abs(self[p][c]) > epsilon else None

    def norm(self) -> T:
        return max(abs(self[i][j]) for i in range(self.rows) for j in range(self.cols))

    def distance(self, other) -> T:
        return (self - other).norm() if isinstance(other, Matrix) else NotImplemented
Editor is loading...
Leave a Comment