commit 397ab450183b7924a1cd19748f314b67ebd209f9 Author: erik detzel Date: Thu Oct 15 15:23:25 2026 +0200 Erster Stand diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1eb6e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv/ +__pycache__/ +.env \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/matrixmania.iml b/.idea/matrixmania.iml new file mode 100644 index 0000000..c5f5bf2 --- /dev/null +++ b/.idea/matrixmania.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ff55ae9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7b1daae --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..79a2eba --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,4 @@ +# Regeln für dieses Projekt +- Code nach PEP 8, Zeilenlänge maximal 88 Zeichen. +- Jede Funktion und Methode hat einen Docstring und Type Hints. +- Vor jedem Commit: `ruff check` und `ruff format`. \ No newline at end of file diff --git a/compute.py b/compute.py new file mode 100644 index 0000000..ee0b193 --- /dev/null +++ b/compute.py @@ -0,0 +1,260 @@ +"""Provides matmul, transpose, and rotate functions for 2‑D numeric matrices.""" + +from __future__ import annotations + +import math +from collections.abc import Iterable +from typing import Literal + +__all__ = ["matmul", "rot_2d", "rotate", "transpose"] + +_NUMERIC_TYPES = (int, float) + + +def _validate_matrix( + matrix: Iterable[Iterable[object]], name: str +) -> list[list[float]]: + """Validate that *matrix* is a rectangular list of lists of numeric values. + + Returns the matrix converted to ``list[list[float]]`` if valid. + Raises ``TypeError`` for non‑numeric values or if any element is a ``bool``. + Raises ``ValueError`` for empty matrices, empty rows, or ragged matrices. + """ + try: + rows = list(map(list, matrix)) + except TypeError as exc: + raise TypeError(f"{name} must be a sequence of sequences") from exc + + if not rows: + raise ValueError(f"{name} must not be empty") + + if not rows[0]: + raise ValueError(f"{name} must not contain empty rows") + + row_len = len(rows[0]) + for row in rows: + if len(row) != row_len: + raise ValueError(f"{name} must be rectangular (row lengths differ)") + for value in row: + if isinstance(value, bool): + raise TypeError(f"{name} contains boolean value {value!r}") + if not isinstance(value, _NUMERIC_TYPES): + raise TypeError(f"{name} contains non‑numeric value {value!r}") + return [[float(value) for value in row] for row in rows] + + +def matmul(a: list[list[float]], b: list[list[float]]) -> list[list[float]]: + """Multiply two matrices ``a`` and ``b``. + + Parameters + ---------- + a: list[list[float]] + Left operand matrix. + b: list[list[float]] + Right operand matrix. + + Returns + ------- + list[list[float]] + The product matrix. + + Raises + ------ + ValueError + If matrix dimensions are incompatible or if a matrix is ragged. + TypeError + If inputs are not sequences of numeric values. + """ + a_matrix = _validate_matrix(a, "a") + b_matrix = _validate_matrix(b, "b") + + num_rows_a = len(a_matrix) + num_cols_a = len(a_matrix[0]) + num_rows_b = len(b_matrix) + num_cols_b = len(b_matrix[0]) + + if num_cols_a != num_rows_b: + raise ValueError( + f"Incompatible dimensions: a has {num_cols_a} columns; " + f"b has {num_rows_b} rows" + ) + + result: list[list[float]] = [ + [0.0 for _ in range(num_cols_b)] for _ in range(num_rows_a) + ] + + for i in range(num_rows_a): + for j in range(num_cols_b): + dot_product = 0.0 + for k in range(num_cols_a): + dot_product += a_matrix[i][k] * b_matrix[k][j] + result[i][j] = dot_product + return result + + +def transpose(matrix: list[list[float]]) -> list[list[float]]: + """Return the transpose of *matrix*. + + Parameters + ---------- + matrix: list[list[float]] + Input matrix as a list of rows. + + Returns + ------- + list[list[float]] + Transposed matrix. + + Raises + ------ + TypeError, ValueError + Propagated from :func:`_validate_matrix` when the input is not a + rectangular numeric matrix. + """ + m_matrix = _validate_matrix(matrix, "matrix") + rows, cols = len(m_matrix), len(m_matrix[0]) + return [[m_matrix[i][j] for i in range(rows)] for j in range(cols)] + + +def rotate(matrix: list[list[float]]) -> list[list[float]]: + """Return the matrix rotated 90° clockwise. + + Parameters + ---------- + matrix: list[list[float]] + Input matrix as a list of rows. + + Returns + ------- + list[list[float]] + Rotated matrix. + + Raises + ------ + TypeError, ValueError + Propagated from :func:`_validate_matrix` when the input is not a + rectangular numeric matrix. + """ + m_matrix = _validate_matrix(matrix, "matrix") + return [list(row) for row in zip(*reversed(m_matrix))] + + +def rot_2d(angle: float, unit: Literal["deg", "rad"] = "deg") -> list[list[float]]: + """Return a 2×2 rotation matrix for a given angle. + + Parameters + ---------- + angle : float + Rotation angle. + unit : {"deg", "rad"}, default "deg" + Whether *angle* is given in degrees or radians. + + Returns + ------- + list[list[float]] + 2×2 rotation matrix. + """ + theta = math.radians(angle) if unit == "deg" else angle + c = math.cos(theta) + s = math.sin(theta) + return [[c, -s], [s, c]] + + +def print_matrix(title: str, matrix: list[list[float]]) -> None: + """Print a title followed by each matrix row on its own line.""" + print(title) + for row in matrix: + print(" ", row) + + +if __name__ == "__main__": + # Matrix multiplication tests + a1 = [[1, 2, 3], [4, 5, 6]] + b1 = [[7, 8], [9, 10], [11, 12]] + print_matrix("Matrix A:", a1) + print_matrix("Matrix B:", b1) + product = matmul(a1, b1) + print_matrix("A x B:", product) + assert product == [[58, 64], [139, 154]], "matmul first test failed" + print() + + a2 = [[1, 2], [3, 4]] + b2 = [[5, 6], [7, 8]] + print_matrix("Matrix A:", a2) + print_matrix("Matrix B:", b2) + product = matmul(a2, b2) + print_matrix("A x B:", product) + assert product == [[19, 22], [43, 50]], "matmul second test failed" + print() + + identity = [[1, 0], [0, 1]] + print_matrix("Matrix A:", a2) + print_matrix("Identity matrix:", identity) + product = matmul(a2, identity) + print_matrix("A x Identity:", product) + assert product == a2, "matmul identity test failed" + print() + + # Dimension mismatch must raise ValueError + try: + matmul([[1, 2]], [[1, 2]]) + except ValueError as error: + print(f"Expected error for mismatched dimensions: {error}") + else: + raise AssertionError("matmul did not raise ValueError") + print() + + # Transpose tests + print_matrix("Matrix:", a1) + transposed = transpose(a1) + print_matrix("Transposed:", transposed) + assert transposed == [[1, 4], [2, 5], [3, 6]], "transpose first test failed" + assert transpose(transposed) == a1, "double transpose failed" + print() + + column = [[1], [2], [3]] + print_matrix("Matrix:", column) + transposed = transpose(column) + print_matrix("Transposed:", transposed) + assert transposed == [[1, 2, 3]], "transpose column test failed" + print() + + # Rotation tests + print_matrix("Matrix:", a1) + rotated = rotate(a1) + print_matrix("Rotated 90 degrees clockwise:", rotated) + assert rotated == [[4, 1], [5, 2], [6, 3]], "rotate 2x3 test failed" + print() + + square = [[1, 2], [3, 4]] + print_matrix("Matrix:", square) + rotated = rotate(square) + print_matrix("Rotated 90 degrees clockwise:", rotated) + assert rotated == [[3, 1], [4, 2]], "rotate square test failed" + print() + + # Four rotations must return the original matrix + rotated = square + for _ in range(4): + rotated = rotate(rotated) + print_matrix("Square matrix rotated four times:", rotated) + assert rotated == square, "four rotations did not return original" + print() + + # 2D rotation matrix tests + rotation_0 = rot_2d(0) + print_matrix("Rotation matrix for 0 degrees:", rotation_0) + assert rotation_0 == [[1.0, -0.0], [0.0, 1.0]], "rot_2d 0 degrees failed" + print() + + rotation_90 = rot_2d(90) + print_matrix("Rotation matrix for 90 degrees:", rotation_90) + point = [[1], [0]] + rotated_point = matmul(rotation_90, point) + print_matrix("Point (1, 0) rotated by 90 degrees:", rotated_point) + assert math.isclose(rotated_point[0][0], 0.0, abs_tol=1e-9), "rot_2d x failed" + assert math.isclose(rotated_point[1][0], 1.0, abs_tol=1e-9), "rot_2d y failed" + print() + + print("All tests passed.") + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..46e3b47 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[project] +name = "matrixmania" +version = "0.1.0" \ No newline at end of file