diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 32b706a..de48f88 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,10 +27,6 @@ repos: hooks: - id: pyupgrade args: [--py310-plus] -- repo: https://github.com/hhatto/autopep8 - rev: v2.3.2 - hooks: - - id: autopep8 - repo: https://github.com/PyCQA/flake8 rev: 7.3.0 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b934d..be5a4ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [1.5.0] - 2026-04-15 + +### Added + +- 2nd and `p` matrix norms + +### Removed + +- `autopep8` from `pre-commit` config + ## [1.4.0] - 2026-03-31 ### Added diff --git a/README.md b/README.md index 40e16c7..4e99465 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,9 @@ To export given solution use: ### Matrix multiplication * [Traditional matrix multiplication algorithm](matrix_calculus/matrix_multiplier.py) + +### LU decomposition + +### Gaussian elimination + +### Matrix norms diff --git a/matrix_calculus/matrix_norms.py b/matrix_calculus/matrix_norms.py new file mode 100644 index 0000000..40d774b --- /dev/null +++ b/matrix_calculus/matrix_norms.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import numpy as np + + +def second_norm(matrix: np.array): + singular_values = np.linalg.svd(matrix, compute_uv=False) + s = np.linalg.svd(matrix, compute_uv=False) + m_2 = np.max(singular_values) + condition_number = s.max() / s.min() + + return m_2, condition_number + + +def p_norm(matrix: np.array, p: int, n_samples: int = 1000, cond_number=True): + + n = matrix.shape[1] + max_val = 0.0 + if cond_number: + condidion_number = np.inf + + # Search for max value of the norm calculated from matrix multiplied by a randomly generated vector + for _ in range(n_samples): + # create random vector + x = np.random.randn(n) + + # normalize vector in p-norm + norm_x = np.linalg.norm(x, ord=p) + if norm_x == 0: + continue + x = x / norm_x + + # calculate norm from vector equal to matrix * random vector + matrix_x = matrix @ x + val = np.linalg.norm(matrix_x, ord=p) + + if val > max_val: + max_val = val + + if cond_number: + try: + matrix_inv = np.linalg.inv(matrix) + norm_M_inv = p_norm(matrix_inv, p, n_samples, cond_number=False) + condidion_number = max_val * norm_M_inv + except np.linalg.LinAlgError: + condidion_number = np.inf + + res = (max_val, condidion_number) if cond_number else max_val + return res diff --git a/pyproject.toml b/pyproject.toml index de8156f..f6d3e78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "matrix_calculus" -version = "1.4.0" +version = "1.5.0" description = "Matrix calculus toolkit" requires-python = ">=3.10" dependencies = ["numpy", "seaborn", "matplotlib", "weasyprint", "markdown"] diff --git a/reports/imgs/matrix_p_norm.png b/reports/imgs/matrix_p_norm.png new file mode 100644 index 0000000..ec7a244 Binary files /dev/null and b/reports/imgs/matrix_p_norm.png differ diff --git a/reports/matrix_norms_report.md b/reports/matrix_norms_report.md new file mode 100644 index 0000000..e651ddc --- /dev/null +++ b/reports/matrix_norms_report.md @@ -0,0 +1,88 @@ +# Normy Macierzowe + +*Autorzy: Maja Byrecka, Michał Kowalczyk* + +## Norma 2 + +### Algorytm + +``` +1. Znajdź wartości własne macierzy M +2. Zwróć maksymalną wartość własną +``` + +### Kod + +```python3 +def second_norm(matrix: np.array) -> float: + singular_values = np.linalg.svd(matrix, compute_uv=False) + return np.max(singular_values) +``` + +### Wynik dla Macierzy M + +Dla macierzy M = [[4, 9, 2], [3, 5, 7], [8, 1, 6]], norma macierzowa wyznaczona w powyższy sposób, z parametrem `p=2` wynosiła `15.000000000000002`. + +## Dowolna Norma P + +Dowolną normę *p* można obliczyć jako: + +![Źródło: Wykład - Wartości i wektory własne & Dekompozycja na wartości osobliwe 2026, Maciej Paszyński](imgs/matrix_p_norm.png) +*Źródło: Wykład - Wartości i wektory własne & Dekompozycja na wartości osobliwe 2026, Maciej Paszyński* + +### Pseudokod + +``` +1. max_val <- 0; n <- ilość kolumn macierzy M +2. Powtórz zadaną ilość razy: + 2.1. Utwórz losowy wektor x + 2.2. Oblicz p-normę wektora x + 2.3. Znormalizuj wektor x w zadanej normie + 2.4. matrix_x <- matrix @ x + 2.5. matrix_p <- p-norma wektora matrix_x + 2.6. Jeśli matrix_p>max_val: + 2.6.1. max_val <- matrix_p +3. Zwróć max_val +``` + +### Algorytm + +```python +def p_norm(matrix: np.array, p: int, n_samples: int =1000): + + n = matrix.shape[1] + max_val = 0.0 + + # Search for max value of the norm calculated from matrix multiplied by a randomly generated vector + for _ in range(n_samples): + # create random vector + x = np.random.randn(n) + + # normalize vector in p-norm + norm_x = np.linalg.norm(x, ord=p) + if norm_x == 0: + continue + x = x / norm_x + + # calculate norm from vector equal to matrix * random vector + matrix_x = matrix @ x + val = np.linalg.norm(matrix_x, ord=p) + + if val > max_val: + max_val = val + + return max_val +``` + +### Wynik dla Macierzy M + +Dla macierzy M = [[4, 9, 2], [3, 5, 7], [8, 1, 6]], norma macierzowa wyznaczona w powyższy sposób, z parametrem `p=2` wynosiła `14.99203`. + +--- + +Wykonanie: + +* Język: Python3 +* Podział zadań: + * Norma 2, p - Maja Byrecka + * Norma 1, inf - Michał Kowalczyk diff --git a/reports/resources/matrix-norms/code.txt b/reports/resources/matrix-norms/code.txt new file mode 100644 index 0000000..3f78b2e --- /dev/null +++ b/reports/resources/matrix-norms/code.txt @@ -0,0 +1,77 @@ + +========== matrix_norms_test.py ========== +from __future__ import annotations + +import numpy as np +import pytest # noqa: F401 +from scipy.linalg import norm + +from matrix_calculus.matrix_norms import second_norm, p_norm + + +def test_m_2_norm(): + matrix = np.array([[4, 9, 2], [3, 5, 7], [8, 1, 6]]) + m_2, condition_number = second_norm(matrix) + assert np.allclose(m_2, 15.0) + + matrix = np.array([[1, 2, 3], [3, 2, 1], [10, 15, 20]]) + m_2, condition_number = second_norm(matrix) + assert np.allclose(m_2, 27.343, rtol=0.01) + +def test_p_norm_2(): + matrix = np.array([[4, 9, 2], [3, 5, 7], [8, 1, 6]]) + m_2, condition_number = p_norm(matrix, p=2) + assert np.allclose(m_2, 15.0, rtol=0.01) + + matrix = np.array([[1, 2, 3], [3, 2, 1], [10, 15, 20]]) + m_2, condition_number = p_norm(matrix, p=2) + assert np.allclose(m_2, 27.343, rtol=0.01) + + +========== matrix_norms.py ========== +import numpy as np + + +def second_norm(matrix: np.array) -> float: + singular_values = np.linalg.svd(matrix, compute_uv=False) + s = np.linalg.svd(matrix, compute_uv=False) + m_2 = np.max(singular_values) + condition_number = s.max() / s.min() + + return m_2, condition_number + +def p_norm(matrix: np.array, p: int, n_samples: int =1000, cond_number=True): + + n = matrix.shape[1] + max_val = 0.0 + if cond_number: + condidion_number = np.inf + + # Search for max value of the norm calculated from matrix multiplied by a randomly generated vector + for _ in range(n_samples): + # create random vector + x = np.random.randn(n) + + # normalize vector in p-norm + norm_x = np.linalg.norm(x, ord=p) + if norm_x == 0: + continue + x = x / norm_x + + # calculate norm from vector equal to matrix * random vector + matrix_x = matrix @ x + val = np.linalg.norm(matrix_x, ord=p) + + if val > max_val: + max_val = val + + if cond_number: + try: + matrix_inv = np.linalg.inv(matrix) + norm_M_inv = p_norm(matrix_inv, p, n_samples, cond_number = False) + condidion_number = max_val * norm_M_inv + except np.linalg.LinAlgError: + condidion_number = np.inf + + res = (max_val, condidion_number) if cond_number else max_val + return res diff --git a/reports/resources/matrix-norms/report.pdf b/reports/resources/matrix-norms/report.pdf new file mode 100644 index 0000000..4b9f1dd Binary files /dev/null and b/reports/resources/matrix-norms/report.pdf differ diff --git a/tests/matrix_norms_test.py b/tests/matrix_norms_test.py new file mode 100644 index 0000000..ea0f039 --- /dev/null +++ b/tests/matrix_norms_test.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import numpy as np +import pytest # noqa: F401 + +from matrix_calculus.matrix_norms import p_norm +from matrix_calculus.matrix_norms import second_norm + + +def test_m_2_norm(): + matrix = np.array([[4, 9, 2], [3, 5, 7], [8, 1, 6]]) + m_2, condition_number = second_norm(matrix) + assert np.allclose(m_2, 15.0) + + matrix = np.array([[1, 2, 3], [3, 2, 1], [10, 15, 20]]) + m_2, condition_number = second_norm(matrix) + assert np.allclose(m_2, 27.343, rtol=0.01) + + +def test_p_norm_2(): + matrix = np.array([[4, 9, 2], [3, 5, 7], [8, 1, 6]]) + m_2, condition_number = p_norm(matrix, p=2) + assert np.allclose(m_2, 15.0, rtol=0.01) + + matrix = np.array([[1, 2, 3], [3, 2, 1], [10, 15, 20]]) + m_2, condition_number = p_norm(matrix, p=2) + assert np.allclose(m_2, 27.343, rtol=0.01)