linear-algebra-toolkit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. linear_algebra_toolkit-0.1.0/LICENSE +21 -0
  2. linear_algebra_toolkit-0.1.0/MANIFEST.in +4 -0
  3. linear_algebra_toolkit-0.1.0/PKG-INFO +140 -0
  4. linear_algebra_toolkit-0.1.0/README.md +117 -0
  5. linear_algebra_toolkit-0.1.0/examples/matrix_dot_vector.py +10 -0
  6. linear_algebra_toolkit-0.1.0/examples/matrix_plus_matrix.py +21 -0
  7. linear_algebra_toolkit-0.1.0/examples/matrix_plus_vector.py +14 -0
  8. linear_algebra_toolkit-0.1.0/examples/vector_as_matrix.py +10 -0
  9. linear_algebra_toolkit-0.1.0/examples/vector_dot_matrix.py +9 -0
  10. linear_algebra_toolkit-0.1.0/examples/vector_dot_product.py +11 -0
  11. linear_algebra_toolkit-0.1.0/examples/vector_minus_vector.py +11 -0
  12. linear_algebra_toolkit-0.1.0/examples/vector_plus_matrix.py +14 -0
  13. linear_algebra_toolkit-0.1.0/examples/vector_plus_vector.py +11 -0
  14. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit/__init__.py +1 -0
  15. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit/objects.py +393 -0
  16. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit/utils.py +26 -0
  17. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit.egg-info/PKG-INFO +140 -0
  18. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit.egg-info/SOURCES.txt +22 -0
  19. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit.egg-info/dependency_links.txt +1 -0
  20. linear_algebra_toolkit-0.1.0/linear_algebra_toolkit.egg-info/top_level.txt +1 -0
  21. linear_algebra_toolkit-0.1.0/pyproject.toml +35 -0
  22. linear_algebra_toolkit-0.1.0/setup.cfg +4 -0
  23. linear_algebra_toolkit-0.1.0/tests/test_objects.py +205 -0
  24. linear_algebra_toolkit-0.1.0/tests/test_utils.py +19 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Max B.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include examples *.py
4
+ recursive-include tests *.py
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: linear-algebra-toolkit
3
+ Version: 0.1.0
4
+ Summary: Educational pure-Python vector and matrix objects for linear algebra experiments.
5
+ Author: Max B.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/maxboro/linear-algebra-toolkit
8
+ Project-URL: Repository, https://github.com/maxboro/linear-algebra-toolkit
9
+ Project-URL: Issues, https://github.com/maxboro/linear-algebra-toolkit/issues
10
+ Keywords: linear algebra,matrix,vector,education,math
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Education
18
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # Linear Algebra Toolkit
25
+
26
+ `linear-algebra-toolkit` is a small pure-Python package for experimenting with
27
+ core linear algebra operations without NumPy or other numerical libraries.
28
+
29
+ The codebase is intentionally simple and explicit. It is best suited for
30
+ learning, reading through the implementation, and running small examples rather
31
+ than for high-performance scientific computing.
32
+
33
+ ## Installation
34
+
35
+ Install the published package from PyPI with:
36
+
37
+ ```bash
38
+ python -m pip install linear-algebra-toolkit
39
+ ```
40
+
41
+ If you are working from a local checkout, install the project in editable mode
42
+ with:
43
+
44
+ ```bash
45
+ python -m pip install -e .
46
+ ```
47
+
48
+ ## What the project provides
49
+
50
+ - A `Vector` type for one-dimensional numeric data.
51
+ - A `Matrix` type for two-dimensional numeric data.
52
+ - Operator overloads for addition, subtraction, element-wise multiplication and
53
+ division, and linear algebra products through `@`.
54
+ - A couple of small utility helpers used by the core objects.
55
+ - No runtime dependencies outside the Python standard library.
56
+
57
+ ## Design goals
58
+
59
+ - Keep the implementation readable and easy to inspect.
60
+ - Avoid hidden abstractions so the math stays visible in plain Python.
61
+ - Provide a compact codebase that is easy to test and extend.
62
+
63
+ ## Quick start
64
+
65
+ Import the public objects directly from the package:
66
+
67
+ ```python
68
+ from linear_algebra_toolkit import Matrix, Vector
69
+
70
+ vector = Vector([3, 4])
71
+ other = Vector([1, 2])
72
+ matrix = Matrix([[1, 2], [3, 4]])
73
+
74
+ print(vector + other) # Vector([4, 6], n elements=2)
75
+ print(vector @ other) # 11
76
+ print(matrix @ vector) # Vector([11, 25], n elements=2)
77
+ print(vector.normalized) # Vector([0.6, 0.8], n elements=2)
78
+ print(vector.as_row_matrix())
79
+ ```
80
+
81
+ ## Supported operations
82
+
83
+ ### Vector
84
+
85
+ - Construction from a non-empty list of `int` and `float` values.
86
+ - `+` and `-` with vectors of the same shape.
87
+ - `+` and `-` with compatible row matrices (`1 x n`) and column matrices (`n x 1`).
88
+ - `*` and `/` with scalars.
89
+ - Element-wise `*` and `/` with another vector of the same shape.
90
+ - `@` with another vector for the dot product.
91
+ - `@` with a compatible matrix.
92
+ - Norms through `norm(mode="euclidean" | "manhattan" | "max")`.
93
+ - `normalized`, `abs(vector)`, `round(vector, ndigits)`,
94
+ `as_row_matrix()`, and `as_col_matrix()`.
95
+
96
+ ### Matrix
97
+
98
+ - Construction from a non-empty rectangular list of rows.
99
+ - `+` and `-` with matrices of the same shape.
100
+ - `+` and `-` with compatible vectors represented as a row or column.
101
+ - `*` and `/` with scalars.
102
+ - Element-wise `*` and `/` with another matrix of the same shape.
103
+ - `@` with another compatible matrix.
104
+ - `@` with a compatible vector.
105
+ - `T` for transpose, `norm()` for the Frobenius norm,
106
+ `get_row_elements()`, `get_col_elements()`, and `get_flatten_elements()`.
107
+
108
+ ## Behavior notes
109
+
110
+ - Only plain Python `int` and `float` values are accepted.
111
+ - Division goes through `zero_aware_division()`. With the current default
112
+ configuration, dividing by zero returns `0` instead of raising an exception.
113
+ - The project is educational in scope and prioritizes clarity over performance.
114
+
115
+ ## Running tests
116
+
117
+ From a repository checkout:
118
+
119
+ ```bash
120
+ python -m unittest discover -s tests
121
+ ```
122
+
123
+ ## Repository examples
124
+
125
+ The repository includes a few small scripts in `examples/` that demonstrate the
126
+ API. From the repository root you can run them like this:
127
+
128
+ ```bash
129
+ python -m examples.vector_plus_vector
130
+ python -m examples.vector_dot_product
131
+ python -m examples.matrix_dot_vector
132
+ python -m examples.vector_as_matrix
133
+ ```
134
+
135
+ ## Repository layout
136
+
137
+ - `linear_algebra_toolkit/objects.py` contains the `Vector` and `Matrix` implementations.
138
+ - `linear_algebra_toolkit/utils.py` contains small shared helpers.
139
+ - `tests/` contains the unit tests for the public behavior.
140
+ - `examples/` contains runnable usage examples.
@@ -0,0 +1,117 @@
1
+ # Linear Algebra Toolkit
2
+
3
+ `linear-algebra-toolkit` is a small pure-Python package for experimenting with
4
+ core linear algebra operations without NumPy or other numerical libraries.
5
+
6
+ The codebase is intentionally simple and explicit. It is best suited for
7
+ learning, reading through the implementation, and running small examples rather
8
+ than for high-performance scientific computing.
9
+
10
+ ## Installation
11
+
12
+ Install the published package from PyPI with:
13
+
14
+ ```bash
15
+ python -m pip install linear-algebra-toolkit
16
+ ```
17
+
18
+ If you are working from a local checkout, install the project in editable mode
19
+ with:
20
+
21
+ ```bash
22
+ python -m pip install -e .
23
+ ```
24
+
25
+ ## What the project provides
26
+
27
+ - A `Vector` type for one-dimensional numeric data.
28
+ - A `Matrix` type for two-dimensional numeric data.
29
+ - Operator overloads for addition, subtraction, element-wise multiplication and
30
+ division, and linear algebra products through `@`.
31
+ - A couple of small utility helpers used by the core objects.
32
+ - No runtime dependencies outside the Python standard library.
33
+
34
+ ## Design goals
35
+
36
+ - Keep the implementation readable and easy to inspect.
37
+ - Avoid hidden abstractions so the math stays visible in plain Python.
38
+ - Provide a compact codebase that is easy to test and extend.
39
+
40
+ ## Quick start
41
+
42
+ Import the public objects directly from the package:
43
+
44
+ ```python
45
+ from linear_algebra_toolkit import Matrix, Vector
46
+
47
+ vector = Vector([3, 4])
48
+ other = Vector([1, 2])
49
+ matrix = Matrix([[1, 2], [3, 4]])
50
+
51
+ print(vector + other) # Vector([4, 6], n elements=2)
52
+ print(vector @ other) # 11
53
+ print(matrix @ vector) # Vector([11, 25], n elements=2)
54
+ print(vector.normalized) # Vector([0.6, 0.8], n elements=2)
55
+ print(vector.as_row_matrix())
56
+ ```
57
+
58
+ ## Supported operations
59
+
60
+ ### Vector
61
+
62
+ - Construction from a non-empty list of `int` and `float` values.
63
+ - `+` and `-` with vectors of the same shape.
64
+ - `+` and `-` with compatible row matrices (`1 x n`) and column matrices (`n x 1`).
65
+ - `*` and `/` with scalars.
66
+ - Element-wise `*` and `/` with another vector of the same shape.
67
+ - `@` with another vector for the dot product.
68
+ - `@` with a compatible matrix.
69
+ - Norms through `norm(mode="euclidean" | "manhattan" | "max")`.
70
+ - `normalized`, `abs(vector)`, `round(vector, ndigits)`,
71
+ `as_row_matrix()`, and `as_col_matrix()`.
72
+
73
+ ### Matrix
74
+
75
+ - Construction from a non-empty rectangular list of rows.
76
+ - `+` and `-` with matrices of the same shape.
77
+ - `+` and `-` with compatible vectors represented as a row or column.
78
+ - `*` and `/` with scalars.
79
+ - Element-wise `*` and `/` with another matrix of the same shape.
80
+ - `@` with another compatible matrix.
81
+ - `@` with a compatible vector.
82
+ - `T` for transpose, `norm()` for the Frobenius norm,
83
+ `get_row_elements()`, `get_col_elements()`, and `get_flatten_elements()`.
84
+
85
+ ## Behavior notes
86
+
87
+ - Only plain Python `int` and `float` values are accepted.
88
+ - Division goes through `zero_aware_division()`. With the current default
89
+ configuration, dividing by zero returns `0` instead of raising an exception.
90
+ - The project is educational in scope and prioritizes clarity over performance.
91
+
92
+ ## Running tests
93
+
94
+ From a repository checkout:
95
+
96
+ ```bash
97
+ python -m unittest discover -s tests
98
+ ```
99
+
100
+ ## Repository examples
101
+
102
+ The repository includes a few small scripts in `examples/` that demonstrate the
103
+ API. From the repository root you can run them like this:
104
+
105
+ ```bash
106
+ python -m examples.vector_plus_vector
107
+ python -m examples.vector_dot_product
108
+ python -m examples.matrix_dot_vector
109
+ python -m examples.vector_as_matrix
110
+ ```
111
+
112
+ ## Repository layout
113
+
114
+ - `linear_algebra_toolkit/objects.py` contains the `Vector` and `Matrix` implementations.
115
+ - `linear_algebra_toolkit/utils.py` contains small shared helpers.
116
+ - `tests/` contains the unit tests for the public behavior.
117
+ - `examples/` contains runnable usage examples.
@@ -0,0 +1,10 @@
1
+ """
2
+ python -m examples.matrix_dot_vector
3
+ """
4
+ from linear_algebra_toolkit import Vector, Matrix
5
+
6
+ vec1 = Vector([5, 6])
7
+ mat1 = Matrix([[1, 2], [3, 4]])
8
+
9
+ print(vec1)
10
+ print("mat1 @ vec1", mat1 @ vec1)
@@ -0,0 +1,21 @@
1
+ """
2
+ python -m examples.matrix_plus_matrix
3
+ """
4
+ from linear_algebra_toolkit import Matrix
5
+
6
+
7
+ mat1 = Matrix([
8
+ [1, 2, 3],
9
+ [1, 2, 3]
10
+ ])
11
+ mat2 = Matrix([
12
+ [1, 0, 3],
13
+ [1, 0, 3]
14
+ ])
15
+
16
+ print("vec1 + mat1", mat1 + mat2)
17
+ print("vec1 + mat2", mat1 + mat2)
18
+
19
+ print("vec1 - mat1", mat1 - mat2)
20
+ print("vec1 - mat2", mat1 - mat2)
21
+
@@ -0,0 +1,14 @@
1
+ """
2
+ python -m examples.matrix_plus_vector
3
+ """
4
+ from linear_algebra_toolkit import Vector, Matrix
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+ mat1 = Matrix([[1, 2, 3]])
8
+ mat2 = Matrix([[1], [2], [3]])
9
+
10
+ print("mat1 + vec1", mat1 + vec1)
11
+ print("mat2 + vec1", mat2 + vec1)
12
+
13
+ print("mat1 - vec1", mat1 - vec1)
14
+ print("mat2 - vec1", mat2 - vec1)
@@ -0,0 +1,10 @@
1
+ """
2
+ python -m examples.vector_as_matrix
3
+ """
4
+ from linear_algebra_toolkit import Vector, Matrix
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+
8
+ print("vec1", vec1)
9
+ print("vec1 as_row_matrix", vec1.as_row_matrix())
10
+ print("vec1 as_col_matrix", vec1.as_col_matrix())
@@ -0,0 +1,9 @@
1
+ """
2
+ python -m examples.vector_dot_matrix
3
+ """
4
+ from linear_algebra_toolkit import Vector, Matrix
5
+
6
+ vec1 = Vector([5, 6])
7
+ mat1 = Matrix([[1, 2], [3, 4]])
8
+
9
+ print("vec1 @ mat1", vec1 @ mat1)
@@ -0,0 +1,11 @@
1
+ """
2
+ python -m examples.vector_dot_product
3
+ """
4
+ from linear_algebra_toolkit import Vector
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+ vec2 = Vector([1, 0, 3])
8
+
9
+ print("vec1", vec1)
10
+ print("vec1", vec2)
11
+ print("vec1 @ vec2", vec1 @ vec2)
@@ -0,0 +1,11 @@
1
+ """
2
+ python -m examples.vector_minus_vector
3
+ """
4
+ from linear_algebra_toolkit import Vector
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+ vec2 = Vector([1, 0, 3])
8
+
9
+ print("vec1", vec1)
10
+ print("vec1", vec2)
11
+ print("vec1 - vec2", vec1 - vec2)
@@ -0,0 +1,14 @@
1
+ """
2
+ python -m examples.vector_plus_matrix
3
+ """
4
+ from linear_algebra_toolkit import Vector, Matrix
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+ mat1 = Matrix([[1, 2, 3]])
8
+ mat2 = Matrix([[1], [2], [3]])
9
+
10
+ print("vec1 + mat1", vec1 + mat1)
11
+ print("vec1 + mat2", vec1 + mat2)
12
+
13
+ print("vec1 - mat1", vec1 - mat1)
14
+ print("vec1 - mat2", vec1 - mat2)
@@ -0,0 +1,11 @@
1
+ """
2
+ python -m examples.vector_plus_vector
3
+ """
4
+ from linear_algebra_toolkit import Vector
5
+
6
+ vec1 = Vector([1, 2, 3])
7
+ vec2 = Vector([1, 0, 3])
8
+
9
+ print("vec1", vec1)
10
+ print("vec1", vec2)
11
+ print("vec1 + vec2", vec1 + vec2)
@@ -0,0 +1 @@
1
+ from .objects import Vector, Matrix
@@ -0,0 +1,393 @@
1
+ """Core linear algebra objects used throughout the toolkit.
2
+
3
+ The module exposes lightweight ``Vector`` and ``Matrix`` classes designed for
4
+ educational use and implemented without third-party numerical libraries.
5
+ """
6
+
7
+ import math
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, List
10
+
11
+ from .utils import zero_aware_division, vector_dot_product
12
+
13
+
14
+ class LAObject(ABC):
15
+ """Base class for objects that support additive arithmetic."""
16
+
17
+ @abstractmethod
18
+ def additive_operation(self, other, sign):
19
+ """Implement addition or subtraction against another object.
20
+
21
+ Args:
22
+ other: The value to combine with ``self``.
23
+ sign: ``1`` for addition and ``-1`` for subtraction.
24
+ """
25
+ pass
26
+
27
+ def __add__(self, other):
28
+ return self.additive_operation(other, sign = 1)
29
+
30
+ def __sub__(self, other):
31
+ return self.additive_operation(other, sign = -1)
32
+
33
+
34
+ class Vector(LAObject):
35
+ """One-dimensional numeric object with basic linear algebra operations.
36
+
37
+ Vectors support:
38
+ - addition and subtraction with vectors of the same length
39
+ - addition and subtraction with ``1 x n`` or ``n x 1`` matrices
40
+ - scalar and element-wise multiplication and division
41
+ - dot products with vectors and multiplication by compatible matrices
42
+ - common norms, normalization, and conversion to matrix form
43
+ """
44
+
45
+ def __init__(self, vector_elements: List[Any]):
46
+ """Create a vector from a non-empty list of numeric values."""
47
+ if not isinstance(vector_elements, list):
48
+ raise TypeError("Should be a list")
49
+ if len(vector_elements) == 0:
50
+ raise ValueError("Empty vectors are not allowed")
51
+
52
+ for element in vector_elements:
53
+ if not isinstance(element, (int, float)):
54
+ raise TypeError("Should be an int or float")
55
+
56
+ self.n = len(vector_elements)
57
+ self._vector_elements = vector_elements
58
+
59
+ @property
60
+ def vector_elements(self):
61
+ """Return the stored vector elements."""
62
+ return self._vector_elements
63
+
64
+ def __repr__(self):
65
+ return f"Vector({self.vector_elements}, n elements={self.n})"
66
+
67
+ def _require_other_vector(self, other):
68
+ if not isinstance(other, Vector):
69
+ raise RuntimeError("Operation is possible only among Vectors")
70
+
71
+ def _require_similar_shape(self, other):
72
+ if self.n != other.n:
73
+ raise RuntimeError(f"Need to be with similar shapes {self.n} != {other.n}")
74
+
75
+ @staticmethod
76
+ def _create_none_list(n):
77
+ new_list = [None for _ in range(n)]
78
+ return new_list
79
+
80
+ @property
81
+ def shape(self):
82
+ """Return the vector shape as a one-item tuple."""
83
+ return (self.n,)
84
+
85
+ def __eq__(self, other):
86
+ if not isinstance(other, Vector):
87
+ return NotImplemented
88
+ return self.vector_elements == other.vector_elements
89
+
90
+ def __ne__(self, other):
91
+ if not isinstance(other, Vector):
92
+ return NotImplemented
93
+ return self.vector_elements != other.vector_elements
94
+
95
+ def __round__(self, ndigits: int = None):
96
+ result = self._create_none_list(self.n)
97
+ for n_ind in range(self.n):
98
+ result[n_ind] = round(self.vector_elements[n_ind], ndigits)
99
+ return Vector(result)
100
+
101
+ def additive_operation(self, other, sign):
102
+ if isinstance(other, Vector):
103
+ self._require_similar_shape(other)
104
+
105
+ result = self._create_none_list(self.n)
106
+ for n_ind in range(self.n):
107
+ result[n_ind] = self.vector_elements[n_ind] + sign * other.vector_elements[n_ind]
108
+ return Vector(result)
109
+ elif isinstance(other, Matrix):
110
+ if other.shape[0] == 1:
111
+ return self.as_row_matrix() + sign * other
112
+ elif other.shape[1] == 1:
113
+ return self.as_col_matrix() + sign * other
114
+ else:
115
+ raise ValueError(f"Operation cannot be performed for shape of other {other.shape}, must be mx1, 1xn")
116
+ else:
117
+ raise TypeError("Other should be Vector or Matrix")
118
+
119
+ def __mul__(self, other):
120
+ result = self._create_none_list(self.n)
121
+ if isinstance(other, (int, float)):
122
+ for n_ind in range(self.n):
123
+ result[n_ind] = self.vector_elements[n_ind] * other
124
+ elif isinstance(other, Vector):
125
+ # Hadamard product
126
+ self._require_similar_shape(other)
127
+ for n_ind in range(self.n):
128
+ result[n_ind] = self.vector_elements[n_ind] * other.vector_elements[n_ind]
129
+ else:
130
+ raise TypeError("Not supported")
131
+ return Vector(result)
132
+
133
+ def __truediv__(self, other):
134
+ result = self._create_none_list(self.n)
135
+ if isinstance(other, (int, float)):
136
+ for n_ind in range(self.n):
137
+ result[n_ind] = zero_aware_division(self.vector_elements[n_ind], other)
138
+ elif isinstance(other, Vector):
139
+ # element wise
140
+ self._require_similar_shape(other)
141
+ for n_ind in range(self.n):
142
+ result[n_ind] = zero_aware_division(self.vector_elements[n_ind], other.vector_elements[n_ind])
143
+ else:
144
+ raise TypeError("Not supported")
145
+ return Vector(result)
146
+
147
+ def __rmul__(self, other):
148
+ return self.__mul__(other)
149
+
150
+ def __matmul__(self, other):
151
+ if isinstance(other, Vector):
152
+ return vector_dot_product(self.vector_elements, other.vector_elements)
153
+ elif isinstance(other, Matrix):
154
+ if self.n != other.m:
155
+ raise ValueError(f"{self.n} != {other.m}")
156
+ result_elements = []
157
+ for ind in range(other.n):
158
+ matrix_row_elements = other.get_col_elements(ind)
159
+ row_dot_product = vector_dot_product(self.vector_elements, matrix_row_elements)
160
+ result_elements.append(row_dot_product)
161
+ return Vector(result_elements)
162
+ else:
163
+ raise TypeError("Other should be Matrix or Vector")
164
+
165
+ def norm(self, mode="euclidean"):
166
+ """Return the vector norm for the requested mode.
167
+
168
+ Supported modes are ``"euclidean"``, ``"manhattan"``, and ``"max"``.
169
+ """
170
+ if mode == "euclidean":
171
+ return math.sqrt(sum([x**2 for x in self.vector_elements]))
172
+ elif mode == "manhattan":
173
+ return sum([abs(x) for x in self.vector_elements])
174
+ elif mode == "max":
175
+ return max([abs(x) for x in self.vector_elements])
176
+ else:
177
+ raise ValueError("Mode can be 'euclidean' or 'manhattan' or 'max'")
178
+
179
+
180
+ def __len__(self):
181
+ return len(self.vector_elements)
182
+
183
+ @property
184
+ def normalized(self):
185
+ """Return a copy scaled to have Euclidean norm equal to ``1``."""
186
+ current_norm = self.norm(mode="euclidean")
187
+ if current_norm == 0:
188
+ raise ValueError("Cannot normalize zero vector")
189
+ norm_coef = 1 / current_norm
190
+ return self.__mul__(norm_coef)
191
+
192
+ def __abs__(self):
193
+ new_vector_elements = [abs(x) for x in self.vector_elements]
194
+ return Vector(new_vector_elements)
195
+
196
+ def as_row_matrix(self) -> "Matrix":
197
+ """Return the vector as a ``1 x n`` matrix."""
198
+ return Matrix([self.vector_elements.copy()])
199
+
200
+ def as_col_matrix(self) -> "Matrix":
201
+ """Return the vector as a ``n x 1`` matrix."""
202
+ matrix_elements = [[element] for element in self.vector_elements]
203
+ return Matrix(matrix_elements)
204
+
205
+
206
+ class Matrix(LAObject):
207
+ """Two-dimensional numeric object with basic matrix operations.
208
+
209
+ Matrices support:
210
+ - addition and subtraction with matrices of the same shape
211
+ - addition and subtraction with compatible row or column vectors
212
+ - scalar and element-wise multiplication and division
213
+ - matrix-matrix and matrix-vector multiplication
214
+ - transpose, flattening, absolute value, and the Frobenius norm
215
+ """
216
+
217
+ def __init__(self, matrix_elements: List[List[Any]]):
218
+ """Create a matrix from a non-empty rectangular list of rows."""
219
+ if not isinstance(matrix_elements, list):
220
+ raise TypeError("Should be a list")
221
+ if len(matrix_elements) == 0:
222
+ raise ValueError("Empty matrixes are not allowed")
223
+
224
+ n = None
225
+ for row in matrix_elements:
226
+ if not isinstance(row, list):
227
+ raise TypeError("Should be a list")
228
+ if len(row) == 0:
229
+ raise ValueError("Empty matrix are not allowed")
230
+ for element in row:
231
+ if not isinstance(element, (int, float)):
232
+ raise TypeError("Should be an int or float")
233
+ if n is None:
234
+ n = len(row)
235
+ elif n != len(row):
236
+ raise ValueError("incorrect shape")
237
+
238
+ self.m = len(matrix_elements)
239
+ self.n = n
240
+ self._matrix_elements = matrix_elements
241
+
242
+ @property
243
+ def matrix_elements(self):
244
+ """Return the stored matrix elements."""
245
+ return self._matrix_elements
246
+
247
+ def __repr__(self):
248
+ return f"Matrix({self.matrix_elements}, shape={self.m}x{self.n})"
249
+
250
+ def _require_other_matrix(self, other):
251
+ if not isinstance(other, Matrix):
252
+ raise RuntimeError("Operation is possible only among Matrixes")
253
+
254
+ def _require_similar_shape(self, other):
255
+ if self.shape != other.shape:
256
+ raise RuntimeError(f"Need to be with similar shapes {self.shape} != {other.shape}")
257
+
258
+ @staticmethod
259
+ def _create_none_list(m, n):
260
+ new_list = [[None for _ in range(n)] for _ in range(m)]
261
+ return new_list
262
+
263
+ @property
264
+ def shape(self):
265
+ """Return the matrix shape as ``(rows, columns)``."""
266
+ return (self.m, self.n)
267
+
268
+ def __eq__(self, other):
269
+ if not isinstance(other, Matrix):
270
+ return NotImplemented
271
+ return self.matrix_elements == other.matrix_elements
272
+
273
+ def __ne__(self, other):
274
+ if not isinstance(other, Matrix):
275
+ return NotImplemented
276
+ return self.matrix_elements != other.matrix_elements
277
+
278
+ def __round__(self, ndigits: int = None):
279
+ result = self._create_none_list(self.m, self.n)
280
+ for m_ind in range(self.m):
281
+ for n_ind in range(self.n):
282
+ result[m_ind][n_ind] = round(self.matrix_elements[m_ind][n_ind], ndigits)
283
+ return Matrix(result)
284
+
285
+ def additive_operation(self, other, sign):
286
+ if isinstance(other, Vector):
287
+ return sign * other + self
288
+ elif isinstance(other, Matrix):
289
+ self._require_similar_shape(other)
290
+
291
+ result = self._create_none_list(self.m, self.n)
292
+ for m_ind in range(self.m):
293
+ for n_ind in range(self.n):
294
+ result[m_ind][n_ind] = self.matrix_elements[m_ind][n_ind] + sign * other.matrix_elements[m_ind][n_ind]
295
+ return Matrix(result)
296
+ else:
297
+ raise TypeError("Other should be Vector or Matrix")
298
+
299
+ def __mul__(self, other):
300
+ result = self._create_none_list(self.m, self.n)
301
+ if isinstance(other, (int, float)):
302
+ for m_ind in range(self.m):
303
+ for n_ind in range(self.n):
304
+ result[m_ind][n_ind] = self.matrix_elements[m_ind][n_ind] * other
305
+ elif isinstance(other, Matrix):
306
+ # Hadamard product
307
+ self._require_similar_shape(other)
308
+ for m_ind in range(self.m):
309
+ for n_ind in range(self.n):
310
+ result[m_ind][n_ind] = self.matrix_elements[m_ind][n_ind] * other.matrix_elements[m_ind][n_ind]
311
+ else:
312
+ raise TypeError("Not supported")
313
+ return Matrix(result)
314
+
315
+ def __truediv__(self, other):
316
+ result = self._create_none_list(self.m, self.n)
317
+ if isinstance(other, (int, float)):
318
+ for m_ind in range(self.m):
319
+ for n_ind in range(self.n):
320
+ result[m_ind][n_ind] = zero_aware_division(self.matrix_elements[m_ind][n_ind], other)
321
+ elif isinstance(other, Matrix):
322
+ self._require_similar_shape(other)
323
+ for m_ind in range(self.m):
324
+ for n_ind in range(self.n):
325
+ result[m_ind][n_ind] = zero_aware_division(self.matrix_elements[m_ind][n_ind], other.matrix_elements[m_ind][n_ind])
326
+ else:
327
+ raise TypeError("Not supported")
328
+ return Matrix(result)
329
+
330
+ def __rmul__(self, other):
331
+ return self.__mul__(other)
332
+
333
+ def get_row_elements(self, index: int) -> list:
334
+ """Return a copy of the row at ``index``."""
335
+ return self.matrix_elements[index].copy()
336
+
337
+ def get_col_elements(self, index: int) -> list:
338
+ """Return a copy of the column at ``index``."""
339
+ col_elements = []
340
+ for row in self.matrix_elements:
341
+ col_elements.append(row[index])
342
+ return col_elements
343
+
344
+ def __matmul__(self, other):
345
+ if isinstance(other, Matrix):
346
+ if self.n != other.m:
347
+ raise RuntimeError(f"Incompatible shape {self.n} != {other.m}")
348
+
349
+ result = self._create_none_list(self.m, other.n)
350
+ for m_ind in range(self.m):
351
+ for n_ind in range(other.n):
352
+ self_row = self.get_row_elements(m_ind)
353
+ other_col = other.get_col_elements(n_ind)
354
+ dot_product = vector_dot_product(self_row, other_col)
355
+ result[m_ind][n_ind] = dot_product
356
+ return Matrix(result)
357
+ elif isinstance(other, Vector):
358
+ if self.n != other.n:
359
+ raise ValueError(f"{self.n} != {other.n}")
360
+ result_elements = []
361
+ for self_row in self.matrix_elements:
362
+ row_dot_product = vector_dot_product(self_row, other.vector_elements)
363
+ result_elements.append(row_dot_product)
364
+ return Vector(result_elements)
365
+ else:
366
+ raise TypeError("Other should be Matrix or Vector")
367
+
368
+ def get_flatten_elements(self) -> list:
369
+ """Return the matrix elements flattened in row-major order."""
370
+ elements = []
371
+ for row in self.matrix_elements:
372
+ for value in row:
373
+ elements.append(value)
374
+ return elements
375
+
376
+ def norm(self):
377
+ """Return the Frobenius norm of the matrix."""
378
+ flatten = self.get_flatten_elements()
379
+ result = math.sqrt(sum([x**2 for x in flatten]))
380
+ return result
381
+
382
+ @property
383
+ def T(self):
384
+ """Return the transpose of the matrix."""
385
+ result = self._create_none_list(self.n, self.m)
386
+ for m_ind in range(self.m):
387
+ for n_ind in range(self.n):
388
+ result[n_ind][m_ind] = self.matrix_elements[m_ind][n_ind]
389
+ return Matrix(result)
390
+
391
+ def __abs__(self):
392
+ new_vector_elements = [[abs(x) for x in row] for row in self.matrix_elements]
393
+ return Matrix(new_vector_elements)
@@ -0,0 +1,26 @@
1
+ """Utility helpers shared by the vector and matrix implementations."""
2
+
3
+ ZERO_DIVISION_TO_ZERO = True
4
+
5
+
6
+ def zero_aware_division(dividend, divisor):
7
+ """Divide two numbers while handling zero divisors consistently.
8
+
9
+ When ``ZERO_DIVISION_TO_ZERO`` is enabled, dividing by zero returns ``0``
10
+ instead of raising ``ZeroDivisionError``.
11
+ """
12
+ if divisor == 0 and ZERO_DIVISION_TO_ZERO:
13
+ return 0
14
+ else:
15
+ return dividend / divisor
16
+
17
+
18
+ def vector_dot_product(vec1: list, vec2: list):
19
+ """Return the dot product of two equally sized numeric sequences."""
20
+ if len(vec1) != len(vec2):
21
+ raise RuntimeError(f"Vector len should be equal {len(vec1)} != {len(vec2)}")
22
+
23
+ result = 0
24
+ for ind in range(len(vec1)):
25
+ result += vec1[ind] * vec2[ind]
26
+ return result
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.4
2
+ Name: linear-algebra-toolkit
3
+ Version: 0.1.0
4
+ Summary: Educational pure-Python vector and matrix objects for linear algebra experiments.
5
+ Author: Max B.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/maxboro/linear-algebra-toolkit
8
+ Project-URL: Repository, https://github.com/maxboro/linear-algebra-toolkit
9
+ Project-URL: Issues, https://github.com/maxboro/linear-algebra-toolkit/issues
10
+ Keywords: linear algebra,matrix,vector,education,math
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Topic :: Education
18
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Dynamic: license-file
23
+
24
+ # Linear Algebra Toolkit
25
+
26
+ `linear-algebra-toolkit` is a small pure-Python package for experimenting with
27
+ core linear algebra operations without NumPy or other numerical libraries.
28
+
29
+ The codebase is intentionally simple and explicit. It is best suited for
30
+ learning, reading through the implementation, and running small examples rather
31
+ than for high-performance scientific computing.
32
+
33
+ ## Installation
34
+
35
+ Install the published package from PyPI with:
36
+
37
+ ```bash
38
+ python -m pip install linear-algebra-toolkit
39
+ ```
40
+
41
+ If you are working from a local checkout, install the project in editable mode
42
+ with:
43
+
44
+ ```bash
45
+ python -m pip install -e .
46
+ ```
47
+
48
+ ## What the project provides
49
+
50
+ - A `Vector` type for one-dimensional numeric data.
51
+ - A `Matrix` type for two-dimensional numeric data.
52
+ - Operator overloads for addition, subtraction, element-wise multiplication and
53
+ division, and linear algebra products through `@`.
54
+ - A couple of small utility helpers used by the core objects.
55
+ - No runtime dependencies outside the Python standard library.
56
+
57
+ ## Design goals
58
+
59
+ - Keep the implementation readable and easy to inspect.
60
+ - Avoid hidden abstractions so the math stays visible in plain Python.
61
+ - Provide a compact codebase that is easy to test and extend.
62
+
63
+ ## Quick start
64
+
65
+ Import the public objects directly from the package:
66
+
67
+ ```python
68
+ from linear_algebra_toolkit import Matrix, Vector
69
+
70
+ vector = Vector([3, 4])
71
+ other = Vector([1, 2])
72
+ matrix = Matrix([[1, 2], [3, 4]])
73
+
74
+ print(vector + other) # Vector([4, 6], n elements=2)
75
+ print(vector @ other) # 11
76
+ print(matrix @ vector) # Vector([11, 25], n elements=2)
77
+ print(vector.normalized) # Vector([0.6, 0.8], n elements=2)
78
+ print(vector.as_row_matrix())
79
+ ```
80
+
81
+ ## Supported operations
82
+
83
+ ### Vector
84
+
85
+ - Construction from a non-empty list of `int` and `float` values.
86
+ - `+` and `-` with vectors of the same shape.
87
+ - `+` and `-` with compatible row matrices (`1 x n`) and column matrices (`n x 1`).
88
+ - `*` and `/` with scalars.
89
+ - Element-wise `*` and `/` with another vector of the same shape.
90
+ - `@` with another vector for the dot product.
91
+ - `@` with a compatible matrix.
92
+ - Norms through `norm(mode="euclidean" | "manhattan" | "max")`.
93
+ - `normalized`, `abs(vector)`, `round(vector, ndigits)`,
94
+ `as_row_matrix()`, and `as_col_matrix()`.
95
+
96
+ ### Matrix
97
+
98
+ - Construction from a non-empty rectangular list of rows.
99
+ - `+` and `-` with matrices of the same shape.
100
+ - `+` and `-` with compatible vectors represented as a row or column.
101
+ - `*` and `/` with scalars.
102
+ - Element-wise `*` and `/` with another matrix of the same shape.
103
+ - `@` with another compatible matrix.
104
+ - `@` with a compatible vector.
105
+ - `T` for transpose, `norm()` for the Frobenius norm,
106
+ `get_row_elements()`, `get_col_elements()`, and `get_flatten_elements()`.
107
+
108
+ ## Behavior notes
109
+
110
+ - Only plain Python `int` and `float` values are accepted.
111
+ - Division goes through `zero_aware_division()`. With the current default
112
+ configuration, dividing by zero returns `0` instead of raising an exception.
113
+ - The project is educational in scope and prioritizes clarity over performance.
114
+
115
+ ## Running tests
116
+
117
+ From a repository checkout:
118
+
119
+ ```bash
120
+ python -m unittest discover -s tests
121
+ ```
122
+
123
+ ## Repository examples
124
+
125
+ The repository includes a few small scripts in `examples/` that demonstrate the
126
+ API. From the repository root you can run them like this:
127
+
128
+ ```bash
129
+ python -m examples.vector_plus_vector
130
+ python -m examples.vector_dot_product
131
+ python -m examples.matrix_dot_vector
132
+ python -m examples.vector_as_matrix
133
+ ```
134
+
135
+ ## Repository layout
136
+
137
+ - `linear_algebra_toolkit/objects.py` contains the `Vector` and `Matrix` implementations.
138
+ - `linear_algebra_toolkit/utils.py` contains small shared helpers.
139
+ - `tests/` contains the unit tests for the public behavior.
140
+ - `examples/` contains runnable usage examples.
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ examples/matrix_dot_vector.py
6
+ examples/matrix_plus_matrix.py
7
+ examples/matrix_plus_vector.py
8
+ examples/vector_as_matrix.py
9
+ examples/vector_dot_matrix.py
10
+ examples/vector_dot_product.py
11
+ examples/vector_minus_vector.py
12
+ examples/vector_plus_matrix.py
13
+ examples/vector_plus_vector.py
14
+ linear_algebra_toolkit/__init__.py
15
+ linear_algebra_toolkit/objects.py
16
+ linear_algebra_toolkit/utils.py
17
+ linear_algebra_toolkit.egg-info/PKG-INFO
18
+ linear_algebra_toolkit.egg-info/SOURCES.txt
19
+ linear_algebra_toolkit.egg-info/dependency_links.txt
20
+ linear_algebra_toolkit.egg-info/top_level.txt
21
+ tests/test_objects.py
22
+ tests/test_utils.py
@@ -0,0 +1 @@
1
+ linear_algebra_toolkit
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "linear-algebra-toolkit"
7
+ version = "0.1.0"
8
+ description = "Educational pure-Python vector and matrix objects for linear algebra experiments."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ {name = "Max B."}
15
+ ]
16
+ keywords = ["linear algebra", "matrix", "vector", "education", "math"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Education",
20
+ "Intended Audience :: Developers",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ "Topic :: Education",
25
+ "Topic :: Scientific/Engineering :: Mathematics",
26
+ ]
27
+ dependencies = []
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/maxboro/linear-algebra-toolkit"
31
+ Repository = "https://github.com/maxboro/linear-algebra-toolkit"
32
+ Issues = "https://github.com/maxboro/linear-algebra-toolkit/issues"
33
+
34
+ [tool.setuptools]
35
+ packages = ["linear_algebra_toolkit"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,205 @@
1
+ import math
2
+ import unittest
3
+
4
+ from linear_algebra_toolkit import Matrix, Vector
5
+
6
+
7
+ class VectorTests(unittest.TestCase):
8
+ def test_vector_constructor_validates_input(self):
9
+ with self.assertRaises(TypeError):
10
+ Vector((1, 2, 3))
11
+
12
+ with self.assertRaises(ValueError):
13
+ Vector([])
14
+
15
+ with self.assertRaises(TypeError):
16
+ Vector([1, "x"])
17
+
18
+ def test_vector_shape_len_round_abs_and_matrix_views(self):
19
+ vector = Vector([1.234, -2.345, 3.456])
20
+
21
+ self.assertEqual(vector.shape, (3,))
22
+ self.assertEqual(len(vector), 3)
23
+ self.assertEqual(round(vector, 2), Vector([1.23, -2.35, 3.46]))
24
+ self.assertEqual(abs(vector), Vector([1.234, 2.345, 3.456]))
25
+ self.assertEqual(vector.as_row_matrix(), Matrix([[1.234, -2.345, 3.456]]))
26
+ self.assertEqual(vector.as_col_matrix(), Matrix([[1.234], [-2.345], [3.456]]))
27
+
28
+ def test_vector_add_and_subtract_vectors(self):
29
+ left = Vector([1, 2, 3])
30
+ right = Vector([4, 5, 6])
31
+
32
+ self.assertEqual(left + right, Vector([5, 7, 9]))
33
+ self.assertEqual(left - right, Vector([-3, -3, -3]))
34
+
35
+ def test_vector_add_and_subtract_row_and_column_matrices(self):
36
+ vector = Vector([1, 2, 3])
37
+ row_matrix = Matrix([[4, 5, 6]])
38
+ col_matrix = Matrix([[4], [5], [6]])
39
+
40
+ self.assertEqual(vector + row_matrix, Matrix([[5, 7, 9]]))
41
+ self.assertEqual(vector - row_matrix, Matrix([[-3, -3, -3]]))
42
+ self.assertEqual(vector + col_matrix, Matrix([[5], [7], [9]]))
43
+ self.assertEqual(vector - col_matrix, Matrix([[-3], [-3], [-3]]))
44
+
45
+ def test_vector_add_rejects_invalid_operands(self):
46
+ with self.assertRaises(RuntimeError):
47
+ Vector([1, 2]) + Vector([1])
48
+
49
+ with self.assertRaises(ValueError):
50
+ Vector([1, 2]) + Matrix([[1, 2], [3, 4]])
51
+
52
+ with self.assertRaises(TypeError):
53
+ Vector([1, 2]) + 1
54
+
55
+ def test_vector_multiply_and_divide(self):
56
+ left = Vector([2, 4, 6])
57
+ right = Vector([1, 2, 3])
58
+ divisor = Vector([2, 0, 3])
59
+
60
+ self.assertEqual(left * 2, Vector([4, 8, 12]))
61
+ self.assertEqual(3 * right, Vector([3, 6, 9]))
62
+ self.assertEqual(left * right, Vector([2, 8, 18]))
63
+ self.assertEqual(left / 2, Vector([1.0, 2.0, 3.0]))
64
+ self.assertEqual(left / divisor, Vector([1.0, 0, 2.0]))
65
+
66
+ with self.assertRaises(TypeError):
67
+ left * Matrix([[1, 2, 3]])
68
+
69
+ def test_vector_dot_products_and_matrix_product(self):
70
+ vector = Vector([5, 6])
71
+ other_vector = Vector([1, 0])
72
+ matrix = Matrix([[1, 2], [3, 4]])
73
+
74
+ self.assertEqual(vector @ other_vector, 5)
75
+ self.assertEqual(vector @ matrix, Vector([23, 34]))
76
+
77
+ def test_vector_matmul_rejects_incompatible_shapes(self):
78
+ with self.assertRaises(ValueError):
79
+ Vector([1, 2]) @ Matrix([[1, 2, 3]])
80
+
81
+ with self.assertRaises(TypeError):
82
+ Vector([1, 2]) @ 1
83
+
84
+ def test_vector_norms_and_normalized(self):
85
+ vector = Vector([3, 4])
86
+ normalized = vector.normalized
87
+
88
+ self.assertEqual(vector.norm(), 5.0)
89
+ self.assertEqual(vector.norm(mode="manhattan"), 7)
90
+ self.assertEqual(vector.norm(mode="max"), 4)
91
+ self.assertAlmostEqual(normalized.vector_elements[0], 0.6)
92
+ self.assertAlmostEqual(normalized.vector_elements[1], 0.8)
93
+ self.assertAlmostEqual(normalized.norm(), 1.0)
94
+
95
+ with self.assertRaises(ValueError):
96
+ vector.norm(mode="unknown")
97
+
98
+ with self.assertRaises(ValueError):
99
+ Vector([0, 0]).normalized
100
+
101
+
102
+ class MatrixTests(unittest.TestCase):
103
+ def test_matrix_constructor_validates_shape(self):
104
+ with self.assertRaises(TypeError):
105
+ Matrix((1, 2))
106
+
107
+ with self.assertRaises(ValueError):
108
+ Matrix([])
109
+
110
+ with self.assertRaises(TypeError):
111
+ Matrix([1, 2])
112
+
113
+ with self.assertRaises(ValueError):
114
+ Matrix([[1, 2], []])
115
+
116
+ with self.assertRaises(ValueError):
117
+ Matrix([[1, 2], [3]])
118
+
119
+ def test_matrix_shape_round_abs_transpose_and_flatten(self):
120
+ matrix = Matrix([[1.234, -2.345], [-3.456, 4.567]])
121
+
122
+ self.assertEqual(matrix.shape, (2, 2))
123
+ self.assertEqual(round(matrix, 2), Matrix([[1.23, -2.35], [-3.46, 4.57]]))
124
+ self.assertEqual(abs(matrix), Matrix([[1.234, 2.345], [3.456, 4.567]]))
125
+ self.assertEqual(matrix.T, Matrix([[1.234, -3.456], [-2.345, 4.567]]))
126
+ self.assertEqual(matrix.get_flatten_elements(), [1.234, -2.345, -3.456, 4.567])
127
+
128
+ def test_matrix_add_and_subtract_matrices(self):
129
+ left = Matrix([[1, 2], [3, 4]])
130
+ right = Matrix([[5, 6], [7, 8]])
131
+
132
+ self.assertEqual(left + right, Matrix([[6, 8], [10, 12]]))
133
+ self.assertEqual(left - right, Matrix([[-4, -4], [-4, -4]]))
134
+
135
+ def test_matrix_add_and_subtract_vectors(self):
136
+ row_matrix = Matrix([[1, 2, 3]])
137
+ col_matrix = Matrix([[1], [2], [3]])
138
+ vector = Vector([4, 5, 6])
139
+
140
+ self.assertEqual(row_matrix + vector, Matrix([[5, 7, 9]]))
141
+ self.assertEqual(row_matrix - vector, Matrix([[-3, -3, -3]]))
142
+ self.assertEqual(col_matrix + vector, Matrix([[5], [7], [9]]))
143
+ self.assertEqual(col_matrix - vector, Matrix([[-3], [-3], [-3]]))
144
+
145
+ def test_matrix_add_rejects_invalid_shapes(self):
146
+ with self.assertRaises(RuntimeError):
147
+ Matrix([[1, 2]]) + Matrix([[1], [2]])
148
+
149
+ with self.assertRaises(ValueError):
150
+ Matrix([[1, 2], [3, 4]]) + Vector([1, 2])
151
+
152
+ with self.assertRaises(TypeError):
153
+ Matrix([[1, 2]]) + 1
154
+
155
+ def test_matrix_multiply_and_divide(self):
156
+ left = Matrix([[2, 4], [6, 8]])
157
+ right = Matrix([[1, 2], [3, 4]])
158
+ divisor = Matrix([[2, 0], [3, 4]])
159
+
160
+ self.assertEqual(left * 2, Matrix([[4, 8], [12, 16]]))
161
+ self.assertEqual(3 * right, Matrix([[3, 6], [9, 12]]))
162
+ self.assertEqual(left * right, Matrix([[2, 8], [18, 32]]))
163
+ self.assertEqual(left / 2, Matrix([[1.0, 2.0], [3.0, 4.0]]))
164
+ self.assertEqual(left / divisor, Matrix([[1.0, 0], [2.0, 2.0]]))
165
+
166
+ with self.assertRaises(TypeError):
167
+ left / Vector([1, 2])
168
+
169
+ def test_matrix_products(self):
170
+ left = Matrix([[1, 2], [3, 4]])
171
+ right = Matrix([[5, 6], [7, 8]])
172
+ vector = Vector([5, 6])
173
+
174
+ self.assertEqual(left @ right, Matrix([[19, 22], [43, 50]]))
175
+ self.assertEqual(left @ vector, Vector([17, 39]))
176
+
177
+ def test_matrix_matmul_rejects_incompatible_shapes(self):
178
+ with self.assertRaises(RuntimeError):
179
+ Matrix([[1, 2]]) @ Matrix([[1, 2]])
180
+
181
+ with self.assertRaises(ValueError):
182
+ Matrix([[1, 2, 3]]) @ Vector([1, 2])
183
+
184
+ with self.assertRaises(TypeError):
185
+ Matrix([[1, 2]]) @ 1
186
+
187
+ def test_matrix_row_and_column_access_return_copies(self):
188
+ matrix = Matrix([[1, 2], [3, 4]])
189
+
190
+ row = matrix.get_row_elements(0)
191
+ column = matrix.get_col_elements(1)
192
+
193
+ row[0] = 99
194
+ column[0] = 88
195
+
196
+ self.assertEqual(matrix, Matrix([[1, 2], [3, 4]]))
197
+
198
+ def test_matrix_norm(self):
199
+ matrix = Matrix([[1, 2], [3, 4]])
200
+
201
+ self.assertAlmostEqual(matrix.norm(), math.sqrt(30))
202
+
203
+
204
+ if __name__ == "__main__":
205
+ unittest.main()
@@ -0,0 +1,19 @@
1
+ import unittest
2
+
3
+ from linear_algebra_toolkit.utils import zero_aware_division, vector_dot_product
4
+
5
+
6
+ class UtilsTests(unittest.TestCase):
7
+ def test_zero_aware_division_returns_zero_for_zero_divisor(self):
8
+ self.assertEqual(zero_aware_division(5, 0), 0)
9
+ self.assertEqual(zero_aware_division(9, 3), 3)
10
+
11
+ def test_vector_dot_product(self):
12
+ self.assertEqual(vector_dot_product([1, 2, 3], [4, 5, 6]), 32)
13
+
14
+ with self.assertRaises(RuntimeError):
15
+ vector_dot_product([1, 2], [1])
16
+
17
+
18
+ if __name__ == "__main__":
19
+ unittest.main()