Files
matrixmania/compute.py
T
2026-10-22 12:55:07 +02:00

175 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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