200 lines
6.0 KiB
Python
200 lines
6.0 KiB
Python
"""
|
||
Compute matrix multiplication of two matrices represented as nested lists.
|
||
|
||
The module provides a single function :func:`matmul` that performs the classic
|
||
O(n³) algorithm without any external dependencies.
|
||
|
||
Mathematics
|
||
-----------
|
||
|
||
If ``A`` has shape ``(m, k)`` and ``B`` has shape ``(k, n)``, the result
|
||
``C`` has shape ``(m, n)`` where::
|
||
|
||
C[i][j] = sum_{t=0}^{k-1} A[i][t] * B[t][j]
|
||
|
||
This implementation follows that definition directly.
|
||
|
||
Validations
|
||
------------
|
||
|
||
* Both matrices must be non‑empty and **rectangular** – every row of a
|
||
matrix must have the same length.
|
||
* The number of columns of ``matrix_a`` must equal the number of rows of
|
||
``matrix_b``.
|
||
|
||
If any of these conditions are violated a :class:`ValueError` is raised
|
||
with a descriptive message.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
|
||
__all__ = ["matmul", "rotation"]
|
||
|
||
|
||
def matmul(
|
||
matrix_a: list[list[float]], matrix_b: list[list[float]]
|
||
) -> list[list[float]]:
|
||
"""Return the matrix product of ``matrix_a`` and ``matrix_b``.
|
||
|
||
Parameters
|
||
----------
|
||
matrix_a:
|
||
Left‑hand matrix with shape ``(m, k)``.
|
||
matrix_b:
|
||
Right‑hand matrix with shape ``(k, n)``.
|
||
Returns
|
||
-------
|
||
List[List[float]]
|
||
The resulting matrix with shape ``(m, n)``.
|
||
Raises
|
||
------
|
||
ValueError
|
||
If either matrix is empty, not rectangular, or if the inner
|
||
dimensions (columns of ``matrix_a`` and rows of ``matrix_b``)
|
||
mismatch.
|
||
"""
|
||
# --------- Validation of input matrices ----------
|
||
# Check that the matrices are not empty
|
||
if not matrix_a or not matrix_b:
|
||
raise ValueError("Both matrices must be non‑empty")
|
||
# Determine row and column counts for each matrix
|
||
a_rows, a_cols = len(matrix_a), len(matrix_a[0])
|
||
b_rows, b_cols = len(matrix_b), len(matrix_b[0])
|
||
# Verify all rows have consistent length (rectangularity)
|
||
if not all(len(row) == a_cols for row in matrix_a):
|
||
raise ValueError("matrix_a is not rectangular")
|
||
if not all(len(row) == b_cols for row in matrix_b):
|
||
raise ValueError("matrix_b is not rectangular")
|
||
# Verify inner dimensions match for multiplication
|
||
if a_cols != b_rows:
|
||
raise ValueError(
|
||
f"Cannot multiply: matrix_a's columns ({a_cols}) "
|
||
f"do not match matrix_b's rows ({b_rows})"
|
||
)
|
||
# --------- Matrix multiplication -----------
|
||
result: list[list[float]] = []
|
||
for i in range(a_rows):
|
||
row_result: list[float] = []
|
||
for j in range(b_cols):
|
||
# Compute dot product of i‑th row of A and j‑th column of B
|
||
dot = 0.0
|
||
for k in range(a_cols):
|
||
dot += matrix_a[i][k] * matrix_b[k][j]
|
||
row_result.append(dot)
|
||
result.append(row_result)
|
||
return result
|
||
|
||
|
||
def transpose(matrix: list[list[float]]) -> list[list[float]]:
|
||
"""Return the transpose of a rectangular matrix.
|
||
|
||
Parameters
|
||
----------
|
||
matrix
|
||
A non‑empty list of lists of floats where each inner list has
|
||
the same length.
|
||
|
||
Returns
|
||
-------
|
||
List[List[float]]
|
||
The transposed matrix, where rows become columns and
|
||
columns become rows.
|
||
Raises
|
||
------
|
||
ValueError
|
||
If the matrix is empty or not rectangular.
|
||
"""
|
||
if not matrix:
|
||
raise ValueError("Matrix must be non‑empty")
|
||
rows, cols = len(matrix), len(matrix[0])
|
||
if not all(len(row) == cols for row in matrix):
|
||
raise ValueError("Matrix must be rectangular")
|
||
# Transpose by swapping indices
|
||
return [[matrix[r][c] for r in range(rows)] for c in range(cols)]
|
||
|
||
|
||
def rotation(angle: float) -> list[list[float]]:
|
||
"""Return a 2x2 rotation matrix for the given angle.
|
||
|
||
The angle is specified in radians.
|
||
|
||
Parameters
|
||
----------
|
||
angle : float
|
||
Rotation angle in radians.
|
||
|
||
Returns
|
||
-------
|
||
list[list[float]]
|
||
A 2x2 matrix
|
||
``[[cos(angle), -sin(angle)],
|
||
[sin(angle), cos(angle)]]``
|
||
"""
|
||
cos_theta = math.cos(angle)
|
||
sin_theta = math.sin(angle)
|
||
return [
|
||
[cos_theta, -sin_theta],
|
||
[sin_theta, cos_theta],
|
||
]
|
||
|
||
|
||
if __name__ == "__main__":
|
||
# Example 1: 2x3 * 3x2 = 2x2
|
||
A = [[1, 2, 3], [4, 5, 6]]
|
||
B = [[7, 8], [9, 10], [11, 12]]
|
||
expected_1 = [[58, 64], [139, 154]]
|
||
assert matmul(A, B) == expected_1, f"Test 1 failed: got {matmul(A, B)}"
|
||
|
||
# Example 2: 2x2 * 2x2
|
||
C = [[1, 2], [3, 4]]
|
||
D = [[5, 6], [7, 8]]
|
||
expected_2 = [[19, 22], [43, 50]]
|
||
assert matmul(C, D) == expected_2, f"Test 2 failed: got {matmul(C, D)}"
|
||
assert matmul(C, D) == expected_2, f"Test 2 failed: got {matmul(C, D)}"
|
||
# Example 3: Transpose a 2x2 matrix
|
||
M = [[1, 2], [3, 4]]
|
||
expected_transpose_3 = [[1, 3], [2, 4]]
|
||
assert transpose(M) == expected_transpose_3, (
|
||
f"Transposition failed, got {transpose(M)}"
|
||
)
|
||
|
||
# Example 4: Transpose a 3x1 matrix
|
||
N = [[5], [6], [7]]
|
||
expected_transpose_4 = [[5, 6, 7]]
|
||
assert transpose(N) == expected_transpose_4, (
|
||
f"Transposition failed, got {transpose(N)}"
|
||
)
|
||
|
||
# Rotation tests
|
||
angle_zero = 0.0
|
||
mat0 = rotation(angle_zero)
|
||
expected0 = [[1.0, 0.0], [0.0, 1.0]]
|
||
for i in range(2):
|
||
for j in range(2):
|
||
assert math.isclose(mat0[i][j], expected0[i][j]), (
|
||
f"rotation(0) mismatch at {i},{j}"
|
||
)
|
||
|
||
angle90 = math.pi / 2
|
||
mat90 = rotation(angle90)
|
||
# Expected: [[0, -1], [1, 0]] with float precision
|
||
assert math.isclose(mat90[0][0], 0.0, rel_tol=1e-9, abs_tol=1e-9), (
|
||
"rotation(pi/2) [0][0] mismatch"
|
||
)
|
||
assert math.isclose(mat90[0][1], -1.0, rel_tol=1e-9, abs_tol=1e-9), (
|
||
"rotation(pi/2) [0][1] mismatch"
|
||
)
|
||
assert math.isclose(mat90[1][0], 1.0, rel_tol=1e-9, abs_tol=1e-9), (
|
||
"rotation(pi/2) [1][0] mismatch"
|
||
)
|
||
assert math.isclose(mat90[1][1], 0.0, rel_tol=1e-9, abs_tol=1e-9), (
|
||
"rotation(pi/2) [1][1] mismatch"
|
||
)
|
||
|
||
print("All tests passed.")
|
||
|
||
|
||
# Zweiter Commit zum Testen der Versionskontrolle
|