From d123b9c19665c80a57dc585433ec180c54bb2d0a Mon Sep 17 00:00:00 2001 From: Yusur Princeps Date: Fri, 12 Dec 2025 11:03:10 +0100 Subject: [PATCH] 0.12.0a1 add Matrix() --- CHANGELOG.md | 4 ++ src/suou/__init__.py | 2 +- src/suou/mat.py | 121 +++++++++++++++++++++++++++++++++++++++++++ tests/test_mat.py | 47 +++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/suou/mat.py create mode 100644 tests/test_mat.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ba99a..f449db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.12.0 + +* New module `mat` adds a shallow reimplementation of `Matrix()` in order to implement matrix multiplication + ## 0.11.2 + increase test coverage of `validators` diff --git a/src/suou/__init__.py b/src/suou/__init__.py index c3c8724..2d79b4b 100644 --- a/src/suou/__init__.py +++ b/src/suou/__init__.py @@ -37,7 +37,7 @@ from .redact import redact_url_password from .http import WantsContentType from .color import chalk, WebColor -__version__ = "0.11.2" +__version__ = "0.12.0a1" __all__ = ( 'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue', diff --git a/src/suou/mat.py b/src/suou/mat.py new file mode 100644 index 0000000..fa60f08 --- /dev/null +++ b/src/suou/mat.py @@ -0,0 +1,121 @@ +""" +Matrix (not the movie...) + +*New in 0.12.0* + +--- + +Copyright (c) 2025 Sakuragasaki46. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +See LICENSE for the specific language governing permissions and +limitations under the License. + +This software is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" + +from __future__ import annotations +from typing import Collection, Iterable, TypeVar +from .functools import deprecated + +_T = TypeVar('_T') + +class Matrix(Collection[_T]): + """ + Shallow reimplementation of numpy's matrices in pure Python. + + *New in 0.12.0* + """ + _shape: tuple[int, int] + _elements: list[_T] + + def shape(self): + return self._shape + + def __init__(self, iterable: Iterable[_T] | Iterable[Collection[_T]], shape: tuple[int, int] | None = None): + elements = [] + boundary_x = boundary_y = 0 + for row in iterable: + if isinstance(row, Collection): + if not boundary_y: + boundary_y = len(row) + elements.extend(row) + boundary_x += 1 + elif boundary_y != len(row): + raise ValueError('row length mismatch') + else: + elements.extend(row) + boundary_x += 1 + elif shape: + if not boundary_x: + boundary_x, boundary_y = shape + elements.append(row) + self._shape = boundary_x, boundary_y + self._elements = elements + assert len(self._elements) == boundary_x * boundary_y + + def __getitem__(self, key: tuple[int, int]) -> _T: + (x, y), (_, sy) = key, self.shape() + + return self._elements[x * sy + y] + + @property + def T(self): + sx, sy = self.shape() + return Matrix( + [ + [ + self[j, i] for j in range(sx) + ] for i in range(sy) + ] + ) + + def __matmul__(self, other: Matrix) -> Matrix: + (ax, ay), (bx, by) = self.shape(), other.shape() + + if ay != bx: + raise ValueError('cannot multiply matrices with incompatible shape') + + return Matrix([ + [ + sum(self[i, k] * other[k, j] for k in range(ay)) for j in range(by) + ] for i in range(ax) + ]) + + def __eq__(self, other: Matrix): + try: + return self._elements == other._elements and self._shape == other._shape + except Exception: + return False + + def __len__(self): + ax, ay = self.shape() + return ax * ay + + @deprecated('please use .rows() or .columns() instead') + def __iter__(self): + return iter(self._elements) + + def __contains__(self, x: object, /) -> bool: + return x in self._elements + + def __repr__(self): + return f'{self.__class__.__name__}({list(self.rows())})' + + def rows(self): + sx, sy = self.shape() + return ( + [self[j, i] for j in range(sy)] for i in range(sx) + ) + + def columns(self): + sx, sy = self.shape() + return ( + [self[j, i] for j in range(sx)] for i in range(sy) + ) + +## TODO write tests! + + diff --git a/tests/test_mat.py b/tests/test_mat.py new file mode 100644 index 0000000..ac1e00c --- /dev/null +++ b/tests/test_mat.py @@ -0,0 +1,47 @@ + + +import unittest + +from suou.mat import Matrix + + +class TestMat(unittest.TestCase): + def setUp(self): + self.m_a = Matrix([ + [2, 2], + [1, 3] + ]) + self.m_b = Matrix([ + [1], [-4] + ]) + def tearDown(self) -> None: + ... + def test_transpose(self): + self.assertEqual( + self.m_a.T, + Matrix([ + [2, 1], + [2, 3] + ]) + ) + self.assertEqual( + self.m_b.T, + Matrix([[1, -4]]) + ) + def test_mul(self): + self.assertEqual( + self.m_b.T @ self.m_a, + Matrix([ + [-2, -10] + ]) + ) + self.assertEqual( + self.m_a @ self.m_b, + Matrix([ + [-6], [-11] + ]) + ) + def test_shape(self): + self.assertEqual(self.m_a.shape(), (2, 2)) + self.assertEqual(self.m_b.shape(), (2, 1)) + self.assertEqual(self.m_b.T.shape(), (1, 2)) \ No newline at end of file