From d065d4d17d34b1232eedfdcdf48714362b27961c Mon Sep 17 00:00:00 2001 From: Talya Washington Date: Tue, 20 Oct 2026 21:06:16 +0200 Subject: [PATCH] Add rot_3D and tests for rot_2D/rot_3D --- diff.py | 2 - opencode.json | 0 pyproject.toml | 11 ++++- src/matrixmania/__init__.py | 0 compute.py => src/matrixmania/compute.py | 38 ++++++++++++++-- test/test_matrixmania/__init__.py | 0 test/test_matrixmania/test_rot.py | 58 ++++++++++++++++++++++++ 7 files changed, 102 insertions(+), 7 deletions(-) delete mode 100644 diff.py create mode 100644 opencode.json create mode 100644 src/matrixmania/__init__.py rename compute.py => src/matrixmania/compute.py (80%) create mode 100644 test/test_matrixmania/__init__.py create mode 100644 test/test_matrixmania/test_rot.py diff --git a/diff.py b/diff.py deleted file mode 100644 index e6d7df5..0000000 --- a/diff.py +++ /dev/null @@ -1,2 +0,0 @@ -print("APOLLO") -print("hallo") diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index 1f30642..8250aa1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,4 +2,13 @@ name = "matrixmania" version = "0.1.0" -dependencies = ["tabulate", "ruff"] \ No newline at end of file +dependencies = ["tabulate", "ruff"] + +[project.optional-dependencies] +dev = ["pytest", "ruff", "pytest-cov"] + +[tool.pytest.ini_options] +pythonpath = "src" +testpaths = "test" + + diff --git a/src/matrixmania/__init__.py b/src/matrixmania/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/compute.py b/src/matrixmania/compute.py similarity index 80% rename from compute.py rename to src/matrixmania/compute.py index efe68b7..f49ce2d 100644 --- a/compute.py +++ b/src/matrixmania/compute.py @@ -1,5 +1,5 @@ import math -from tabulate import tabulate + def matmul(matrix_a: list[list[float | int]], matrix_b: list[list[float | int]]) -> list[list[float | int]]: """Multipliziert zwei Matrizen, die als verschachtelte Listen dargestellt sind. @@ -91,6 +91,37 @@ def rot_2D(theta: float) -> list[list[float]]: [sin_theta, cos_theta] ] +def rot_3D(angle: float, axis: str) -> list[list[float]]: + """Gibt die 3D-Rotationsmatrix um eine Koordinatenachse zurück. + + Formeln (θ = angle, im Bogenmaß): + R_x(θ) = [[1, 0, 0], [0, cos θ, -sin θ], [0, sin θ, cos θ]] + R_y(θ) = [[cos θ, 0, sin θ], [0, 1, 0], [-sin θ, 0, cos θ]] + R_z(θ) = [[cos θ, -sin θ, 0], [sin θ, cos θ, 0], [0, 0, 1]] + + Args: + angle: Der Rotationswinkel in Bogenmaß (Radians). + axis: Die Rotationsachse: "x", "y" oder "z". + + Returns: + Die 3x3-Rotationsmatrix als 2D-Liste. + + Raises: + ValueError: Wenn axis nicht "x", "y" oder "z" ist. + """ + c = math.cos(angle) + s = math.sin(angle) + + if axis == "x": + return [[1.0, 0.0, 0.0], [0.0, c, -s], [0.0, s, c]] + if axis == "y": + return [[c, 0.0, s], [0.0, 1.0, 0.0], [-s, 0.0, c]] + if axis == "z": + return [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]] + + raise ValueError(f"Ungültige Achse '{axis}': erlaubt sind 'x', 'y' oder 'z'.") + + if __name__ == "__main__": @@ -154,8 +185,7 @@ if __name__ == "__main__": theta = math.pi / 2 #1,57... print(f"Winkel: {math.degrees(theta)} Grad (pi / 2)") print(rot_2D(theta)) - print(tabulate(rot_2D(1.0))) + print(rot_2D(1.0)) + -print("hallo") -print("apollo") diff --git a/test/test_matrixmania/__init__.py b/test/test_matrixmania/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_matrixmania/test_rot.py b/test/test_matrixmania/test_rot.py new file mode 100644 index 0000000..3eba5c5 --- /dev/null +++ b/test/test_matrixmania/test_rot.py @@ -0,0 +1,58 @@ +import math + +import pytest + +from matrixmania.compute import matmul, rot_2D, rot_3D, transpose + + +def assert_matrix_close(actual, expected): + """Vergleicht zwei Matrizen elementweise mit Toleranz.""" + assert len(actual) == len(expected) + for row_a, row_e in zip(actual, expected): + assert row_a == pytest.approx(row_e, abs=1e-9) + + +# ---------- rot_2D ---------- + +def test_rot_2D_zero(): + assert_matrix_close(rot_2D(0), [[1, 0], [0, 1]]) + + +def test_rot_2D_90_degree(): + assert_matrix_close(rot_2D(math.pi / 2), [[0, -1], [1, 0]]) + + +def test_rot_2D_invalid_input(): + with pytest.raises(TypeError): + rot_2D("abc") + + +# ---------- rot_3D ---------- + +@pytest.mark.parametrize("axis", ["x", "y", "z"]) +def test_rot_3D_zero_is_identity(axis): + assert_matrix_close(rot_3D(0, axis), [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + +@pytest.mark.parametrize( + "axis, expected", + [ + ("x", [[1, 0, 0], [0, 0, -1], [0, 1, 0]]), + ("y", [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]), + ("z", [[0, -1, 0], [1, 0, 0], [0, 0, 1]]), + ], +) +def test_rot_3D_90_degree(axis, expected): + assert_matrix_close(rot_3D(math.pi / 2, axis), expected) + + +@pytest.mark.parametrize("axis", ["x", "y", "z"]) +def test_rot_3D_is_orthogonal(axis): + r = rot_3D(1.0, axis) + assert_matrix_close(matmul(r, transpose(r)), [[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + + +@pytest.mark.parametrize("axis", ["w", "X", "", "xy"]) +def test_rot_3D_invalid_axis(axis): + with pytest.raises(ValueError): + rot_3D(1.0, axis) \ No newline at end of file