255 lines
5.9 KiB
Python
255 lines
5.9 KiB
Python
from math import cos, sin
|
|
from typing import TypeAlias
|
|
|
|
|
|
Number: TypeAlias = int | float
|
|
Matrix: TypeAlias = list[list[Number]]
|
|
|
|
|
|
def matmul(A: Matrix, B: Matrix) -> Matrix:
|
|
"""Multiply two matrices.
|
|
|
|
Args:
|
|
A: The first matrix.
|
|
B: The second matrix.
|
|
|
|
Returns:
|
|
The resulting matrix product.
|
|
|
|
Raises:
|
|
ValueError: If either matrix is empty, not rectangular,
|
|
or if their dimensions are incompatible.
|
|
"""
|
|
if not A or not B:
|
|
raise ValueError("Matrices must not be empty.")
|
|
|
|
if not A[0] or not B[0]:
|
|
raise ValueError("Matrices must have at least one column.")
|
|
|
|
cols_A = len(A[0])
|
|
cols_B = len(B[0])
|
|
|
|
if any(len(row) != cols_A for row in A):
|
|
raise ValueError("Matrix A is not rectangular.")
|
|
|
|
if any(len(row) != cols_B for row in B):
|
|
raise ValueError("Matrix B is not rectangular.")
|
|
|
|
if cols_A != len(B):
|
|
raise ValueError(
|
|
"The number of columns in A must equal the number of rows in B."
|
|
)
|
|
|
|
rows_A = len(A)
|
|
rows_B = len(B)
|
|
|
|
result: Matrix = [
|
|
[0 for _ in range(cols_B)]
|
|
for _ in range(rows_A)
|
|
]
|
|
|
|
for i in range(rows_A):
|
|
for j in range(cols_B):
|
|
for k in range(rows_B):
|
|
result[i][j] += A[i][k] * B[k][j]
|
|
|
|
return result
|
|
|
|
|
|
def transpose(matrix: Matrix) -> Matrix:
|
|
"""Return the transpose of a matrix.
|
|
|
|
Parameters
|
|
----------
|
|
matrix : List[List[Number]]
|
|
Matrix to transpose.
|
|
|
|
Returns
|
|
-------
|
|
List[List[Number]]
|
|
Transposed matrix where rows become columns.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If the input matrix is empty or not rectangular.
|
|
"""
|
|
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("Input matrix is not rectangular.")
|
|
return [[matrix[i][j] for i in range(n_rows)] for j in range(n_cols)]
|
|
|
|
|
|
def rotate_matrix(matrix: Matrix) -> None:
|
|
"""Rotate a square matrix 90 degrees clockwise in-place.
|
|
|
|
The algorithm combines transposition and row reversal,
|
|
running in O(n²) time with O(1) additional space.
|
|
|
|
Parameters
|
|
----------
|
|
matrix : Matrix
|
|
Square matrix to be modified.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If the matrix is empty or not square.
|
|
"""
|
|
n = len(matrix)
|
|
|
|
if n == 0:
|
|
raise ValueError("Matrix must not be empty.")
|
|
|
|
for row in matrix:
|
|
if len(row) != n:
|
|
raise ValueError("Matrix must be square.")
|
|
|
|
# Transpose the matrix
|
|
for i in range(n):
|
|
for j in range(i + 1, n):
|
|
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
|
|
|
|
# Reverse each row
|
|
for row in matrix:
|
|
row.reverse()
|
|
|
|
|
|
def rot_2D(theta: float) -> Matrix:
|
|
"""Create a rotation matrix for a 2D rotation.
|
|
|
|
Parameters
|
|
----------
|
|
theta : float
|
|
Rotation angle in radians.
|
|
|
|
Returns
|
|
-------
|
|
Matrix
|
|
A 2x2 rotation matrix.
|
|
"""
|
|
return [
|
|
[cos(theta), -sin(theta)],
|
|
[sin(theta), cos(theta)],
|
|
]
|
|
|
|
def rot_3D(angle: float, axis: str) -> Matrix:
|
|
"""Create a 3D rotation matrix around the specified axis.
|
|
|
|
Args:
|
|
angle: Rotation angle in radians.
|
|
axis: Axis of rotation ('x', 'y', or 'z').
|
|
|
|
Returns:
|
|
A 3x3 rotation matrix.
|
|
|
|
Raises:
|
|
ValueError: If the specified axis is invalid.
|
|
"""
|
|
c = cos(angle)
|
|
s = sin(angle)
|
|
|
|
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("Axis must be 'x', 'y', or 'z'.")
|
|
|
|
def project_ortho(
|
|
point: tuple[float, float, float],
|
|
scale: float = 1,
|
|
) -> tuple[float, float]:
|
|
"""Project a 3D point orthogonally onto the xy-plane.
|
|
|
|
Args:
|
|
point: A 3D point containing x, y, and z coordinates.
|
|
scale: Scaling factor for the projected coordinates.
|
|
|
|
Returns:
|
|
The scaled 2D coordinates of the projected point.
|
|
"""
|
|
x, y, z = point
|
|
|
|
return (x * scale, y * scale)
|
|
|
|
def project_perspective(
|
|
point: tuple[float, float, float],
|
|
focal_length: float = 1.0,
|
|
scale: float = 1.0,
|
|
camera_distance: float = 4.0,
|
|
) -> tuple[float, float]:
|
|
"""Project a 3D point onto a 2D plane using perspective.
|
|
|
|
Args:
|
|
point: The 3D point containing x, y, and z coordinates.
|
|
focal_length: Controls the strength of the perspective.
|
|
scale: Scaling factor for the projected coordinates.
|
|
camera_distance: Distance offset of the camera.
|
|
|
|
Returns:
|
|
The projected 2D coordinates.
|
|
|
|
Raises:
|
|
ValueError: If the projection denominator is zero.
|
|
"""
|
|
x, y, z = point
|
|
|
|
depth = z + camera_distance
|
|
|
|
if depth == 0:
|
|
raise ValueError("Projection depth must not be zero.")
|
|
|
|
projected_x = x * focal_length / depth * scale
|
|
projected_y = y * focal_length / depth * scale
|
|
|
|
return (projected_x, projected_y)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Beispiel Multiplikation
|
|
A1 = [[1, 2, 3], [4, 5, 6]]
|
|
B1 = [[7, 8], [9, 10], [11, 12]]
|
|
expected1 = [[58, 64], [139, 154]]
|
|
|
|
assert matmul(A1, B1) == expected1
|
|
print("Beispiel 1 erfolgreich:", matmul(A1, B1))
|
|
|
|
# Beispiel Rotation
|
|
matrix = [[1, 2], [3, 4]]
|
|
expected = [[3, 1], [4, 2]]
|
|
|
|
rotate_matrix(matrix)
|
|
assert matrix == expected
|
|
print("Rotation erfolgreich:", matrix)
|
|
|
|
# Beispiel Transponierung
|
|
matrix = [[1, 2, 3], [4, 5, 6]]
|
|
expected = [[1, 4], [2, 5], [3, 6]]
|
|
|
|
assert transpose(matrix) == expected
|
|
print("Transponierung erfolgreich:", transpose(matrix))
|
|
|
|
|
|
|