145 lines
4.5 KiB
Python
145 lines
4.5 KiB
Python
"""Compute operations for matrices.
|
||
|
||
This module provides public functions for matrix multiplication, transposition,
|
||
matrix rotation, and generating 2D and 3D rotation matrices as well as projections.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import List
|
||
|
||
|
||
def matmul(
|
||
matrix_a: List[List[float]], matrix_b: List[List[float]]
|
||
) -> List[List[float]]:
|
||
"""Multiply two 2D matrices.
|
||
|
||
Both matrices are expected to be non‑empty and to have consistent row
|
||
lengths. The number of columns in ``matrix_a`` must equal the number of
|
||
rows in ``matrix_b``.
|
||
|
||
Args:
|
||
matrix_a: A matrix of shape (m, n) represented as a list of m rows.
|
||
matrix_b: A matrix of shape (n, p) represented as a list of n rows.
|
||
|
||
Returns:
|
||
A new matrix of shape (m, p) containing the product ``matrix_a @ matrix_b``.
|
||
|
||
Raises:
|
||
ValueError: If either matrix is empty or dimensions do not match.
|
||
"""
|
||
if not matrix_a or not matrix_b:
|
||
raise ValueError("Input matrices must not be empty.")
|
||
|
||
n_cols_a = len(matrix_a[0])
|
||
|
||
if any(len(row) != n_cols_a for row in matrix_a):
|
||
raise ValueError("All rows of matrix_a must have the same number of columns.")
|
||
|
||
n_rows_b = len(matrix_b)
|
||
|
||
if n_cols_a != n_rows_b:
|
||
raise ValueError(
|
||
f"Matrix dimension mismatch: matrix_a columns {n_cols_a} != matrix_b rows {n_rows_b}"
|
||
)
|
||
|
||
n_cols_b = len(matrix_b[0])
|
||
|
||
if any(len(row) != n_cols_b for row in matrix_b):
|
||
raise ValueError("All rows of matrix_b must have the same number of columns.")
|
||
|
||
result: List[List[float]] = []
|
||
for i in range(len(matrix_a)):
|
||
row: List[float] = []
|
||
for j in range(n_cols_b):
|
||
elem = sum(matrix_a[i][k] * matrix_b[k][j] for k in range(n_cols_a))
|
||
row.append(elem)
|
||
result.append(row)
|
||
|
||
return result
|
||
|
||
|
||
def transpose(matrix: List[List[float]]) -> List[List[float]]:
|
||
"""Return the transpose of a 2D matrix."""
|
||
if not matrix:
|
||
raise ValueError("Cannot transpose an empty matrix.")
|
||
|
||
n_rows = len(matrix)
|
||
n_cols = len(matrix[0])
|
||
|
||
if any(len(row) != n_cols for row in matrix):
|
||
raise ValueError("All rows of the matrix must have the same number of columns.")
|
||
|
||
return [[matrix[i][j] for i in range(n_rows)] for j in range(n_cols)]
|
||
|
||
|
||
def rotate_clockwise(matrix: List[List[float]]) -> List[List[float]]:
|
||
"""Rotate a square matrix 90 degrees clockwise."""
|
||
if not matrix:
|
||
raise ValueError("Cannot rotate an empty matrix.")
|
||
|
||
n_cols = len(matrix[0])
|
||
if any(len(row) != n_cols for row in matrix):
|
||
raise ValueError("All rows of the matrix must have the same number of columns.")
|
||
|
||
transposed = transpose(matrix)
|
||
return [row[::-1] for row in transposed]
|
||
|
||
|
||
def rot_2D(theta: float) -> List[List[float]]:
|
||
"""Return the 2D rotation matrix for a given angle in degrees."""
|
||
rad = math.radians(theta)
|
||
c = math.cos(rad)
|
||
s = math.sin(rad)
|
||
return [[c, -s], [s, c]]
|
||
|
||
|
||
def rot_3D(angle: float, axis: str) -> list[list[float]]:
|
||
"""Erzeugt eine 3D-Rotationsmatrix um die angegebene Achse ('x', 'y' oder 'z')."""
|
||
rad = math.radians(angle)
|
||
c, s = math.cos(rad), math.sin(rad)
|
||
|
||
axis = axis.lower()
|
||
if axis == 'x':
|
||
return [
|
||
[1.0, 0.0, 0.0],
|
||
[0.0, c, -s],
|
||
[0.0, s, c]
|
||
]
|
||
elif axis == 'y':
|
||
return [
|
||
[c, 0.0, s],
|
||
[0.0, 1.0, 0.0],
|
||
[-s, 0.0, c]
|
||
]
|
||
elif axis == 'z':
|
||
return [
|
||
[c, -s, 0.0],
|
||
[s, c, 0.0],
|
||
[0.0, 0.0, 1.0]
|
||
]
|
||
else:
|
||
raise ValueError("Achse muss 'x', 'y' oder 'z' sein.")
|
||
|
||
|
||
def project_ortho(point: tuple[float, float, float], scale: float = 1.0) -> tuple[float, float]:
|
||
"""Projiziert einen 3D-Punkt orthogonal auf eine 2D-Ebene und skaliert ihn."""
|
||
x, y, z = point
|
||
return (x * scale, y * scale)
|
||
|
||
def project_perspective(point: tuple[float, float, float], distance: float = 200.0, scale: float = 1.0) -> tuple[float, float]:
|
||
"""Projiziert einen 3D-Punkt perspektivisch auf eine 2D-Fläche."""
|
||
x, y, z = point
|
||
# Verhindert Division durch 0, falls z genau bei -distance liegt
|
||
factor = distance / (distance + z) if (distance + z) != 0 else 1.0
|
||
return (x * factor * scale, y * factor * scale)
|
||
|
||
if __name__ == "__main__":
|
||
a = [[1, 2, 3], [4, 5, 6]]
|
||
b = [[7, 8], [9, 10], [11, 12]]
|
||
|
||
print("matmul(a, b):", matmul(a, b))
|
||
print("transpose(a):", transpose(a))
|
||
|
||
# Teste deine Tests mit: python -m pytest |