Version 2
mit Ruff überarbeitet
This commit is contained in:
1 parent
74bb1cf469
commit
b2a5866b91
1 file changed
+4
-3
+236
@@ -0,0 +1,236 @@
|
||||
""" Rechenhilfen 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
|
||||
from tabulate import tabulate
|
||||
|
||||
import math
|
||||
|
||||
__all__ = ["matmul", "rot_2D", "rotate", "transpose"]
|
||||
|
||||
|
||||
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))
|
||||
print(tabulate(rot_2D(1.0)))
|
||||
# Git - Test
|
||||
Reference in new issue
Block a user