from __future__ import annotations from collections.abc import Sequence import math Matrix = Sequence[Sequence[float]] def matmul(left: Matrix, right: Matrix) -> list[list[float]]: """Multiply two matrices and return a new matrix. Args: left: Matrix of shape (rows_left, cols_left). right: Matrix of shape (rows_right, cols_right). Returns: A new matrix of shape (rows_left, cols_right). Raises: ValueError: If either input is empty, ragged, or if cols_left != rows_right. """ # Validate first matrix (rows and columns), raise if invalid. _validate(left, "left") # Validate second matrix for similar conditions. _validate(right, "right") # Number of columns in left matrix. cols_left = len(left[0]) # Number of rows in right matrix. rows_right = len(right) # Ensure the inner dimensions are compatible. if cols_left != rows_right: raise ValueError( f"dimension mismatch: left has {cols_left} columns, " f"right has {rows_right} rows" ) # Number of columns in right matrix. cols_right = len(right[0]) # Prepare result container: rows of left × columns of right. result: list[list[float]] = [] # Iterate over rows of left matrix. for i in range(len(left)): row: list[float] = [] # Iterate over columns of right matrix. for j in range(cols_right): total = 0.0 # Accumulate dot product of row i of left and column j of right. for k in range(cols_left): total += left[i][k] * right[k][j] row.append(total) result.append(row) return result # Helper to validate a matrix is non‑empty and rectangular. def rot_2D(theta: float) -> list[list[float]]: """Return the 2‑D rotation matrix for a given angle. Parameters ---------- theta Rotation angle in radians. Returns ------- list[list[float]] 2×2 rotation matrix ``[[cosθ, -sinθ], [sinθ, cosθ]]``. """ c = math.cos(theta) s = math.sin(theta) return [[c, -s], [s, c]] def _validate(matrix: Matrix, name: str) -> None: """Check that *matrix* is a non‑empty, rectangular 2D sequence. Validation rules: * The outer sequence must contain at least one row. * Every row must have the same number of columns. * No empty rows are allowed. Raises: ValueError: If the matrix is empty, has zero columns, or is ragged. """ if len(matrix) == 0: raise ValueError(f"{name} matrix is empty") width = len(matrix[0]) if width == 0: raise ValueError(f"{name} matrix has no columns") for index, row in enumerate(matrix): if len(row) != width: raise ValueError( f"{name} matrix is ragged: row 0 has {width} columns, " f"row {index} has {len(row)}" ) def transpose(matrix: Matrix) -> list[list[float]]: """Transponiere die gegebene Matrix. Alle Zeilen werden zu Spalten und alle Spalten zu Zeilen. Parameters ---------- matrix Matrix in Form ``Sequence[Sequence[float]]``. Returns ------- list[list[float]] Die transponierte Matrix. Raises ------ ValueError Wenn ``matrix`` leer ist oder nicht gleichmäßig viele Spalten in den Zeilen hat. """ _validate(matrix, "matrix") rows = len(matrix) cols = len(matrix[0]) transposed: list[list[float]] = [] for c in range(cols): new_row: list[float] = [] for r in range(rows): new_row.append(matrix[r][c]) transposed.append(new_row) if __name__ == "__main__": # Example 1: standard 2x3 * 3x2 multiplication. a = [ [1, 2, 3], [4, 5, 6], ] b = [ [7, 8], [9, 10], [11, 12], ] expected = [ [58, 64], [139, 154], ] assert matmul(a, b) == expected, f"example 1 failed: {matmul(a, b)}" print("example 1 passed:", matmul(a, b)) # Example 2: multiplication with the identity matrix changes nothing. identity = [ [1, 0, 0], [0, 1, 0], [0, 0, 1], ] c = [ [2, -1, 0], [0, 3, 4], [5, 6, -2], ] assert matmul(c, identity) == c, "example 2 failed" print("example 2 passed:", matmul(c, identity)) # Example 3: mismatched dimensions must raise ValueError. try: matmul([[1, 2, 3]], [[1, 2]]) except ValueError as error: print("example 3 passed:", error) # end of main guard