From ce33380a170fed76c9c7d42d12c0c9a171933a73 Mon Sep 17 00:00:00 2001 From: MajaByr Date: Wed, 15 Apr 2026 19:34:51 +0200 Subject: [PATCH] :sparkles: Add 2nd and p matrix norms --- .pre-commit-config.yaml | 4 - CHANGELOG.md | 10 +++ README.md | 6 ++ matrix_calculus/matrix_norms.py | 49 ++++++++++++ pyproject.toml | 2 +- reports/imgs/matrix_p_norm.png | Bin 0 -> 9771 bytes reports/matrix_norms_report.md | 88 ++++++++++++++++++++++ reports/resources/matrix-norms/code.txt | 77 +++++++++++++++++++ reports/resources/matrix-norms/report.pdf | Bin 0 -> 35038 bytes tests/matrix_norms_test.py | 27 +++++++ 10 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 matrix_calculus/matrix_norms.py create mode 100644 reports/imgs/matrix_p_norm.png create mode 100644 reports/matrix_norms_report.md create mode 100644 reports/resources/matrix-norms/code.txt create mode 100644 reports/resources/matrix-norms/report.pdf create mode 100644 tests/matrix_norms_test.py 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 0000000000000000000000000000000000000000..ec7a244c8a05d02d6ad117ef26f05e2634f6e51b GIT binary patch literal 9771 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 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 0000000000000000000000000000000000000000..4b9f1dddbfe264443103990506fd99aabe5e6ed0 GIT binary patch literal 35038 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 YcXYKkGKObjW@Dm