Files
matrixmania/Compute.py
T
2026-10-19 17:44:56 +02:00

235 lines
6.5 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.
"""Rechen-Hilfen für Matrizen.
Dieses Modul stellt vier Funktionen bereit:
* :func:`matmul` – multipliziert zwei (auch nicht-quadratische)
Matrizen.
* :func:`transpose` – transponiert eine Matrix.
* :func:`rotate` – rotiert eine Matrix um 90 Grad im Uhrzeigersinn.
* :func:`rot_2D` – erzeugt die 2x2-Rotationsmatrix für einen Winkel.
Die Implementierung validiert alle Eingaben:
* Alle Zeilen einer Matrix müssen gleich lang sein.
* Bei :func:`matmul` muss die Spaltenzahl der ersten Matrix der
Zeilenzahl der zweiten Matrix entsprechen – die klassische Regel
der Matrixmultiplikation.
* Alle Elemente müssen ``int`` oder ``float`` sein – andernfalls wird
ein :class:`TypeError` ausgelöst.
Nicht kompatible Matrizen führen zu einem :class:`ValueError`.
Unter ``__main__`` befindet sich ein kleiner Testrahmen zum schnellen
manuellen Ausprobieren.
Für dieses Rechenmodul werden keine externen Abhängigkeiten benötigt.
"""
from __future__ import annotations
import math
__all__ = ["matmul", "transpose", "rotate", "rot_2D"]
def _validate(matrix: list[list[float]], name: str) -> tuple[int, int]:
"""Prüfe eine Matrix und gib ihre Form ``(Zeilen, Spalten)`` zurück.
Akzeptiert auch die leere Matrix ``[]`` (Form ``(0, 0)``). Alle
Zeilen müssen gleich lang und alle Elemente müssen ``int`` oder
``float`` sein.
Raises
------
ValueError
Wenn die Zeilen ungleich lang sind.
TypeError
Wenn ein Element keine Zahl ist.
"""
if not matrix:
return (0, 0)
n_rows = len(matrix)
n_cols = len(matrix[0])
for row in matrix:
if len(row) != n_cols:
raise ValueError(
f"Alle Zeilen von {name} müssen gleich lang sein "
f"(erwartet {n_cols}, erhalten {len(row)})"
)
for x in row:
if not isinstance(x, (int, float)):
raise TypeError(
f"Elemente von {name} müssen Zahlen sein, "
f"aber {x!r} ist {type(x).__name__}"
)
return (n_rows, n_cols)
# ================= Matrixmultiplikation =================
def matmul(
a: list[list[float]], b: list[list[float]]
) -> list[list[float]]:
"""Berechne das Produkt ``a @ b`` zweier Matrizen.
Die Matrizen dürfen auch rechteckig sein. Beide Eingaben werden
nicht verändert.
Parameters
----------
a, b : list[list[float]]
Zweidimensionale Listen mit Zahlen. Die Spaltenzahl von
``a`` muss der Zeilenzahl von ``b`` entsprechen. Die leere
Matrix (``[]``) ist als Operand nicht erlaubt.
Returns
-------
list[list[float]]
Das Matrixprodukt ``a @ b``.
Raises
------
ValueError
Wenn eine der Matrizen leer ist, Zeilen ungleich lang sind oder
die Dimensionen nicht zusammenpassen.
TypeError
Wenn ein Element keine Zahl ist.
"""
rows_a, cols_a = _validate(a, "'a'")
rows_b, cols_b = _validate(b, "'b'")
if rows_a == 0 or rows_b == 0:
raise ValueError(
"Leere Matrizen sind als Operanden der Multiplikation "
"nicht erlaubt"
)
if cols_a != rows_b:
raise ValueError(
f"Spaltenzahl von 'a' ({cols_a}) und Zeilenzahl von 'b' "
f"({rows_b}) müssen übereinstimmen"
)
# Initialisiere die Ergebnismatrix (rows_a x cols_b) mit Nullen.
result: list[list[float]] = [[0] * cols_b for _ in range(rows_a)]
# Zeilenweise durchlaufen (i), dann das gemeinsame Element (k):
# ikj-Reihenfolge ist cache-freundlicher als das klassische ijk.
for i in range(rows_a):
row_result = result[i]
for k in range(cols_a):
aik = a[i][k]
if aik == 0:
continue
b_row = b[k]
for j in range(cols_b):
row_result[j] += aik * b_row[j]
return result
# ================= Transposition =================
def transpose(matrix: list[list[float]]) -> list[list[float]]:
"""Gib die Transponierte einer Matrix zurück.
Parameters
----------
matrix : list[list[float]]
Zweidimensionale Liste mit Zahlen. Die leere Matrix ``[]``
wird als ``[]`` zurückgegeben.
Returns
-------
list[list[float]]
Die transponierte Matrix.
Raises
------
ValueError
Wenn die Zeilen ungleich lang sind.
TypeError
Wenn ein Element keine Zahl ist.
"""
n_rows, n_cols = _validate(matrix, "matrix")
return [[matrix[i][j] for i in range(n_rows)] for j in range(n_cols)]
# ================= Rotation um 90 Grad =================
def rotate(matrix: list[list[float]]) -> list[list[float]]:
"""Rotiere eine Matrix um 90 Grad im Uhrzeigersinn.
Die erste Zeile wird zur letzten Spalte, die letzte Zeile zur
ersten Spalte. Die Eingabe wird nicht verändert.
Parameters
----------
matrix : list[list[float]]
Zweidimensionale Liste mit Zahlen. Die leere Matrix ``[]``
wird als ``[]`` zurückgegeben.
Returns
-------
list[list[float]]
Die um 90 Grad im Uhrzeigersinn rotierte Matrix.
Raises
------
ValueError
Wenn die Zeilen ungleich lang sind.
TypeError
Wenn ein Element keine Zahl ist.
"""
return [row[::-1] for row in transpose(matrix)]
# ================= 2D-Rotationsmatrix =================
def rot_2D(theta: float) -> list[list[float]]:
"""Erzeuge die Rotationsmatrix für eine Drehung um ``theta``.
Die Matrix dreht einen Punkt in der Ebene um den Winkel ``theta``
um den Ursprung:
.. math::
\\begin{pmatrix} \\cos\\theta & -\\sin\\theta \\\\
\\sin\\theta & \\cos\\theta \\end{pmatrix}
Parameters
----------
theta : float
Drehwinkel im Bogenmaß (Radiant).
Returns
-------
list[list[float]]
Die 2x2-Rotationsmatrix.
"""
cos_t = math.cos(theta)
sin_t = math.sin(theta)
return [[cos_t, -sin_t], [sin_t, cos_t]]
# ================= Testrahmen =================
if __name__ == "__main__":
# matmul mit rechteckigen Matrizen
matrix_a = [
[3, 4, -1, 4],
[-2, 2, 5, 1],
]
matrix_b = [
[1, 3, -2],
[2, 5, 1],
[-1, 4, -4],
[2, 3, 6],
]
matrix_c = matmul(matrix_a, matrix_b)
print("Ergebnis C = A * B:")
for row in matrix_c:
print(row)
# rot_2D: Drehung um 90 Grad gegen den Uhrzeigersinn
rotation = rot_2D(math.pi / 2)
point = [[1.0], [0.0]]
print("rot_2D-Ergebnis:", matmul(rotation, point))
# Git - Test