"""Functions for matrix calculations.""" def _validate_matrix(matrix: list[list[int | float]], name: str) -> tuple[int, int]: """Validate a non-empty, rectangular matrix with numeric entries. Args: matrix: Matrix represented as a nested list. name: Name of the matrix for error messages. Returns: A tuple containing the number of rows and columns. Raises: TypeError: If the matrix or its entries have invalid types. ValueError: If the matrix is empty, has empty rows, or contains rows with different lengths. """ if not isinstance(matrix, list): raise TypeError(f"{name} must be a list of lists") if not matrix: raise ValueError(f"{name} must not be empty") for row_index, row in enumerate(matrix): if not isinstance(row, list): raise TypeError(f"Row {row_index} of {name} must be a list") column_count = len(matrix[0]) if column_count == 0: raise ValueError(f"{name} must not contain empty rows") for row_index, row in enumerate(matrix): if len(row) != column_count: raise ValueError(f"Rows of {name} must have equal lengths") for value in row: if isinstance(value, bool) or not isinstance(value, (int, float)): raise TypeError(f"All entries of {name} must be numbers") return len(matrix), column_count def matmul(a: list[list[int | float]], b: list[list[int | float]]) -> list[list[int | float]]: """Multiply two matrices and return their product. Args: a: Left matrix with m rows and n columns. b: Right matrix with n rows and p columns. Returns: The product matrix with m rows and p columns. Raises: TypeError: If a matrix or an entry has an invalid type. ValueError: If a matrix is empty, is not rectangular, or the dimensions do not match. """ rows_a, columns_a = _validate_matrix(a, "a") rows_b, columns_b = _validate_matrix(b, "b") if columns_a != rows_b: raise ValueError( f"Cannot multiply matrices with shapes " f"{rows_a}x{columns_a} and {rows_b}x{columns_b}" ) result = [] for i in range(rows_a): result_row = [] for j in range(columns_b): total = 0 for k in range(columns_a): total += a[i][k] * b[k][j] result_row.append(total) result.append(result_row) return result def transpose(matrix: list[list[int | float]]) -> list[list[int | float]]: """Transpose a matrix by exchanging its rows and columns. Args: matrix: A non-empty rectangular matrix. Returns: The transposed matrix. Raises: TypeError: If the matrix or its entries have invalid types. ValueError: If the matrix is empty or not rectangular. """ rows, columns = _validate_matrix(matrix, "matrix") result = [] for j in range(columns): new_row = [] for i in range(rows): new_row.append(matrix[i][j]) result.append(new_row) return result def rotate(matrix: list[list[int | float]]) -> list[list[int | float]]: """Rotate a matrix 90 degrees clockwise. Args: matrix: A non-empty rectangular matrix. Returns: The matrix rotated 90 degrees clockwise. Raises: TypeError: If the matrix or its entries have invalid types. ValueError: If the matrix is empty or not rectangular. """ transposed = transpose(matrix) return [row[::-1] for row in transposed] import math def rot_2D(theta: float) -> list[list[float]]: """Return the 2D rotation matrix for an angle in radians. Args: theta: Rotation angle in radians. Returns: A 2x2 rotation matrix. """ cos_theta = math.cos(theta) sin_theta = math.sin(theta) return [ [cos_theta, -sin_theta], [sin_theta, cos_theta] ] if __name__ == "__main__": # Test 1: Multiplication of two 2x2 matrices result1 = matmul([[1, 2], [3, 4]], [[5, 6], [7, 8]]) expected1 = [[19, 22], [43, 50]] assert result1 == expected1 print("Test 1 passed:", result1) # Test 2: Multiplication of a 2x3 matrix and a 3x2 matrix result2 = matmul([[1, 2, 3], [4, 5, 6]], [[7, 8], [9, 10], [11, 12]]) expected2 = [[58, 64], [139, 154]] assert result2 == expected2 print("Test 2 passed:", result2) # Test 3: Incompatible dimensions try: matmul([[1, 2], [3, 4]], [[1, 2]]) except ValueError: print("Test 3 passed: Invalid dimensions detected") else: raise AssertionError("Test 3 failed: ValueError was expected") # Test 4: Transpose a 2x3 matrix original = [[1, 2, 3], [4, 5, 6]] transposed = transpose(original) expected = [[1, 4], [2, 5], [3, 6]] assert transposed == expected print("Test 4 passed:", transposed) # Test 5: Transpose a square matrix original_square = [[1, 2], [3, 4]] expected_square = [[1, 3], [2, 4]] assert transpose(original_square) == expected_square print("Test 5 passed:", expected_square) # Test 6: Rotate a 2x3 matrix clockwise original = [[1, 2, 3], [4, 5, 6]] expected = [[4, 1], [5, 2], [6, 3]] assert rotate(original) == expected print("Test 6 passed:", rotate(original)) # Test 7: Rotate a square matrix clockwise original_square = [[1, 2], [3, 4]] expected_square = [[3, 1], [4, 2]] assert rotate(original_square) == expected_square print("Test 7 passed:", rotate(original_square)) # Test 8: Rotation by 0 degrees (identity matrix) rotation_0 = rot_2D(0) expected_0 = [[1.0, 0.0], [0.0, 1.0]] for i in range(2): for j in range(2): assert math.isclose( rotation_0[i][j], expected_0[i][j], abs_tol=1e-9 ) print("Test 8 passed:", rotation_0) # Test 9: Rotation by 90 degrees counterclockwise rotation_90 = rot_2D(math.pi / 2) expected_90 = [[0.0, -1.0], [1.0, 0.0]] for i in range(2): for j in range(2): assert math.isclose( rotation_90[i][j], expected_90[i][j], abs_tol=1e-9 ) print("Test 9 passed:", rotation_90)