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

213 lines
5.9 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.
"""Matrix multiplication with explicit dimension checks.
This module provides a pure‑Python implementation that
- validates input dimensions and structure
- performs generic matrix multiplication without external libs
- raises informative ``ValueError`` when inputs are inconsistent.
The implementation is small but fully typed so it can be
imported into other projects or used as a teaching example.
"""
from __future__ import annotations
from collections.abc import Sequence
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 _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)
else:
raise AssertionError("example 3 failed: no ValueError raised")
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)
else:
raise AssertionError("example 3 failed: no ValueError raised")
# Example 4: Demonstration of the transpose function.
example_matrix = [[1, 2, 3], [4, 5, 6]]
transposed = transpose(example_matrix)
print("Example 4: transpose([[1,2,3],[4,5,6]]) =", transposed)
assert transposed == [[1, 4], [2, 5], [3, 6]], "Example 4 failed"
print("example 4 passed")