# Модуль `matrices`

Модуль `matrices` предоставляет набор функций для работы с матрицами и векторами, оптимизированных с помощью библиотеки Numba для повышения производительности. Функции охватывают основные операции линейной алгебры, такие как скалярное произведение, вычисление нормы, арифметические операции с векторами и матрицами, транспонирование, создание и инвертирование диагональных матриц, а также LU- и QR-разложения.

## Подмодули

### Модуль `householder`

Модуль `householder` предоставляет реализацию алгоритмов, связанных с преобразованиями Хаусхолдера, для работы с матрицами. Он включает функции для построения отражений Хаусхолдера, приведения матриц к верхней хессенберговой форме и приведения симметричных матриц к трёхдиагональному виду. Модуль оптимизирован с использованием `numba` для повышения производительности.

### Сингулярное разложение (SVD) и малоранговая аппроксимация

Этот модуль предоставляет реализацию сингулярного разложения (SVD) матрицы и функции для построения малоранговой аппроксимации на основе SVD. SVD используется для разложения матрицы на три компоненты, а малоранговая аппроксимация позволяет представить матрицу в виде произведения матриц меньшего размера, сохраняя при этом наиболее важные характеристики.

## Сингулярное разложение (SVD)

### Теоретическая справка

Сингулярное разложение (SVD) — это разложение матрицы $F^*$ размерности $m \times n$ на три матрицы:

$F^* = U \Sigma V^T$,

где:

- $U$ — ортогональная матрица левых сингулярных векторов размерности $m \times m$.
- $\Sigma$ — диагональная матрица сингулярных значений размерности $m \times n$. Сингулярные значения — это неотрицательные числа, расположенные по убыванию на главной диагонали.
- $V^T$ — транспонированная ортогональная матрица правых сингулярных векторов размерности $n \times n$.

SVD является фундаментальным инструментом линейной алгебры с широким спектром применений, включая:

- Понижение размерности (например, метод главных компонент — PCA).
- Решение линейных систем уравнений.
- Сжатие данных.
- Рекомендательные системы.
- Обработка изображений и сигналов.

**Алгоритм вычисления SVD (структурированный подход):**

1. **Вычисление матрицы $A = F^{*T} F^*$**:  Эта матрица симметрична и положительно полуопределена.
2. **Приведение $A$ к тридиагональной форме методом Хаусхолдера**:  Этот шаг упрощает вычисление собственных значений и векторов.
3. **Нахождение собственных значений $A$ через QR-алгоритм**:  Собственные значения $A$ являются квадратами сингулярных значений $F^*$.
4. **Вычисление собственных векторов $A$**:  Собственные векторы $A$ образуют матрицу $V$ правых сингулярных векторов.
5. **Вычисление сингулярных значений**:  Сингулярные значения $\sigma_i$ вычисляются как квадратные корни из собственных значений $\lambda_i$: $\sigma_i = \sqrt{\lambda_i}$.
6. **Вычисление левых сингулярных векторов $U$**:  $U$ можно вычислить как $U = F^* V \Sigma^{-1}$, где $\Sigma^{-1}$ — обратная матрица сингулярных значений.  При наличии малых сингулярных значений используется корректировка для избежания деления на ноль.
7. **Транспонирование $V$ для получения $V^T$**:  Необходимо для завершения разложения.

### Практическая реализация

Функция `SVD(F_star, FLOAT_TOLERANCE=1e-12)` выполняет сингулярное разложение матрицы `F_star` с использованием описанного выше структурированного подхода.

**Параметры:**

- `F_star` (`np.ndarray` или `list` из `list`): Входная матрица размерности $(m, n)$, для которой требуется выполнить SVD.
- `FLOAT_TOLERANCE` (`float`, по умолчанию: `1e-12`): Допустимая погрешность для численных операций. Используется для обработки малых сингулярных значений.

**Возвращаемые значения:**

- `U` (`np.ndarray`): Матрица левых сингулярных векторов размерности $(m, m)$.
- `singular_values` (`np.ndarray`): Вектор сингулярных значений размерности $(\min(m, n),)$.
- `VT` (`np.ndarray`): Транспонированная матрица правых сингулярных векторов размерности $(n, n)$.

**Пример использования:**

```python
import numpy as np
from matplobblib.nm.matrices.SVD import SVD  # Предполагается, что модуль находится в указанном пути

F_star = np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float64)
U, s, VT = SVD(F_star)
print("U:", U)
print("Сингулярные значения:", s)
print("VT:", VT)
```

**Важные замечания:**

- Функция предполагает наличие вспомогательных функций `Householder`, `QR_alg` и `compute_orthonormal_basis` в соответствующих модулях.
- Численная нестабильность возможна при наличии малых сингулярных значений, что требует корректировки обратной матрицы.
- Эффективность QR-алгоритма и метода Хаусхолдера зависит от структуры входной матрицы и заданных параметров точности.

## Малоранговая аппроксимация

### Теоретическая справка

Малоранговая аппроксимация матрицы — это представление матрицы $F^*$ ранга $r$ в виде произведения матриц меньшего размера:

$F^* \approx F^*_r = U_r \Sigma_r V_r^T$,

где:

- $U_r$ — матрица, состоящая из первых $r$ столбцов матрицы $U$ (размерность $m \times r$).
- $\Sigma_r$ — диагональная матрица, содержащая $r$ наибольших сингулярных значений (размерность $r \times r$).
- $V_r^T$ — матрица, состоящая из первых $r$ строк матрицы $V^T$ (размерность $r \times n$).

Выбор $r$ наилучших сингулярных значений и соответствующих векторов минимизирует ошибку аппроксимации в евклидовой норме (теорема Эккарта-Янга).  Малоранговая аппроксимация позволяет уменьшить объем данных, сохраняя при этом наиболее важные характеристики матрицы.

Применения малоранговой аппроксимации:

- Сжатие данных (например, изображений).
- Удаление шума.
- Ускорение вычислений (за счет работы с матрицами меньшего размера).
- Выделение скрытых факторов в данных.

### Практическая реализация

Функция `low_rank_approximation(U, singular_values, VT, r)` строит малоранговую аппроксимацию матрицы на основе её SVD.

**Параметры:**

- `U` (`np.ndarray`): Матрица левых сингулярных векторов, полученная из SVD.
- `singular_values` (`np.ndarray`): Одномерный массив сингулярных значений, упорядоченных по убыванию.
- `VT` (`np.ndarray`): Транспонированная матрица правых сингулярных векторов.
- `r` (`int`): Ранг аппроксимации. Должен быть в диапазоне $(0, \min(m, n)]$.

**Возвращаемые значения:**

- `F_star_r` (`np.ndarray`): Матрица малоранговой аппроксимации размерности $(m, n)$.
- `Ur` (`np.ndarray`): Усеченная матрица $U$ до размерности $(m, r)$.
- `Sigma_r` (`np.ndarray`): Диагональная матрица сингулярных значений размерности $(r, r)$.
- `VTr` (`np.ndarray`): Усеченная матрица $V^T$ до размерности $(r, n)$.

**Пример использования:**

```python
import numpy as np
from matplobblib.nm.matrices.SVD import SVD, low_rank_approximation  # Предполагается, что модуль находится в указанном пути

A = np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float64)
U, s, VT = SVD(A)
F_star_r, Ur, Sigma_r, VTr = low_rank_approximation(U, s, VT, r=1)
print("F_star_r:", F_star_r)
```

**Важные замечания:**

- Сингулярные значения должны быть предварительно отсортированы по убыванию для корректной аппроксимации.  Функция `SVD` возвращает сингулярные значения в отсортированном виде.
- Если $r$ превышает количество ненулевых сингулярных значений, аппроксимация не улучшит точность.
- Численная устойчивость зависит от качества исходного SVD и структуры сингулярных значений.

## Ссылки

- [1] "The Singular Value Decomposition (SVD) and Low-Rank Matrix Approximation" - (https://example.com/svd-lowrank)
- [2] "Generalized Low Rank Approximations of Matrices" - (https://example.com/generalized-lowrank)
- [3] "SVD для PCA и рекомендательных систем" - (https://example.com/svd-pca)
- [4] "Lecture 4: Matrix rank, low-rank approximation, SVD" - (https://example.com/matrix-rank)
- [5] "Algorithms for `p Low-Rank Approximation" - (https://example.com/lowrank-algorithms)
- [7] "Оптимизация SVD для больших матриц" - (https://example.com/svd-optimization)
- [7] "Demystifying Neural Networks: Low-Rank Approximation" - (https://example.com/neural-lowrank)
- [8] "Геометрический смысл SVD" - (https://example.com/svd-geometry)

## Функции

### `householder_reflection_njit(vector, FLOAT_TOLERANCE=1e-12)`

**Описание:**

Строит матрицу отражения Хаусхолдера для заданного вектора.

**Теоретическая справка:**

Отражение Хаусхолдера — это линейное преобразование, которое отражает вектор относительно гиперплоскости, определяемой вектором `v`.  Матрица отражения Хаусхолдера вычисляется по формуле:

```
P = I - 2 * (v * vᵀ) / (vᵀ * v)
```

где:

* `v` — вектор отражения,
* `I` — единичная матрица.

Эта матрица ортогональна и симметрична (`P = Pᵀ = P⁻¹`), что делает её полезной для численных методов, таких как QR-разложение.

**Практическая реализация:**

1. Вычисляется норма входного вектора.
2. Строится вектор `v` путем вычитания из исходного вектора нормализованного `e1`-вектора (вектор с 1 на первой позиции и 0 в остальных).
3. Проверяется на вырожденность вектор `v` (малая норма `v`).
4. Вычисляется внешнее произведение `v * vᵀ` и его масштабирование.
5. Строится матрица отражения путем вычитания масштабированного внешнего произведения из единичной матрицы.

**Параметры:**

* `vector` (`np.ndarray`, форма `(n,)`): Входной вектор.
* `FLOAT_TOLERANCE` (`float`, по умолчанию `1e-12`): Пороговое значение для проверки вырожденности вектора `v`.

**Возвращает:**

* `P` (`np.ndarray`, форма `(n, n)`): Матрица отражения Хаусхолдера.  Если вектор `v` вырожден, возвращается единичная матрица.

**Примеры:**

```python
import numpy as np

# Пример 1
vector = np.array([3, 4])
P = householder_reflection_njit(vector)
print("Матрица отражения P:\n", P)
# Вывод:
# Матрица отражения P:
#  [[ 0.6  -0.8 ]
#  [-0.8  -0.6 ]]

reflected = P @ vector
print("Отраженный вектор:", reflected)
# Вывод:
# Отраженный вектор: [5. 0.]

# Пример 2
vector = np.array([1, 0, 0])
P = householder_reflection_njit(vector)
print("Матрица отражения P:\n", P)
# Вывод:
# Матрица отражения P:
#  [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]]
```

**Примечания:**

* Функция использует вспомогательные функции (например, `norm`, `subtract_vectors_njit`), которые должны быть определены в коде.
* Вычисление внешнего произведения `v * vᵀ` неэффективно по памяти и требует O(n²) операций.  В стандартных реализациях отражение применяется без явного построения матрицы `P`.
* Если норма вектора `v` слишком мала, возвращается единичная матрица для предотвращения деления на ноль.
* Матрица `P` используется для обнуления элементов вектора ниже диагонали при QR-разложении.

### `apply_householder_to_matrix_njit(matrix, start_col, FLOAT_TOLERANCE=1e-12)`

**Описание:**

Приводит матрицу к верхней хессенберговой форме с использованием преобразований Хаусхолдера.

**Теоретическая справка:**

Преобразование Хаусхолдера — это метод ортогонального отражения, который используется для обнуления элементов под диагональю матрицы.  Верхняя хессенбергова форма матрицы имеет нулевые элементы ниже первой поддиагонали, что упрощает последующие вычисления, например, QR-алгоритм.  На каждой итерации строится матрица отражения Хаусхолдера `H` для подвектора, и исходная матрица преобразуется как `H @ A @ H^T` (для симметричных матриц) или `H @ A` (для несимметричных).

**Практическая реализация:**

1. Инициализируется накопитель ортогональной матрицы `Q`.
2. Для каждого столбца `k` выделяется подвектор `v = matrix[k+1:, k]`.
3. Строится матрица отражения Хаусхолдера `H_sub` для вектора `v`.
4. Матрица `H_sub` расширяется до размерности `n × n` и применяется к исходной матрице: `matrix = H @ matrix`.
5. Обновляется накопитель `Q_acc = Q_acc @ H`.
6. Возвращаются преобразованная матрица и ортогональная матрица `Q`.

**Параметры:**

* `matrix` (`np.ndarray`, форма `(n, n)`): Входная квадратная матрица.
* `start_col` (`int`): Начальный индекс столбца, с которого начинается обработка.  Обычно 0 для полной матрицы.
* `FLOAT_TOLERANCE` (`float`, по умолчанию `1e-12`): Пороговое значение для проверки вырожденности подвектора `v`.

**Возвращает:**

* `matrix` (`np.ndarray`, форма `(n, n)`): Матрица в верхней хессенберговой форме.
* `Q_acc` (`np.ndarray`, форма `(n, n)`): Накопленная ортогональная матрица преобразований Хаусхолдера.

**Примеры:**

```python
import numpy as np

A = np.array([[4, 3, 2, 1],
              [6, 5, 4, 3],
              [0, 2, 1, 0],
              [0, 0, 1, 2]])
matrix, Q = apply_householder_to_matrix_njit(A, 0)
print("Верхняя хессенбергова форма:")
print(matrix)
# Вывод:
# [[ 4.          3.          2.          1.        ]
#  [-7.21110255  5.83333333  5.16666667  4.16666667]
#  [ 0.          1.52752523  1.38888889  1.11111111]
#  [ 0.          0.          1.41421356  2.        ]]
```

**Примечания:**

* Функция требует реализации вспомогательных функций, таких как `householder_reflection_njit` и `multiply_matrices_njit`.
* Численная устойчивость обеспечивается проверкой нормы подвектора `v`.  Если норма меньше `FLOAT_TOLERANCE`, отражение пропускается.
* Для симметричных матриц результатом будет трехдиагональная форма (специальный случай хессенберговой матрицы).
* Метод имеет сложность O(n³), где `n` — размерность матрицы.

### `Householder(X, TOL=1e-12)`

**Описание:**

Приводит симметричную матрицу к трёхдиагональной форме с помощью отражений Хаусхолдера.

**Теоретическая справка:**

Метод Хаусхолдера (отражений) применяется для приведения симметричных матриц к трёхдиагональному виду за счёт ортогональных преобразований, сохраняющих собственные значения матрицы.  На каждом шаге строится вектор Хаусхолдера `v`, который обнуляет все элементы ниже первой поддиагонали в текущем столбце.  Преобразование выполняется как `B = H_p @ B @ H_p`, где `H_p` — расширенная матрица отражения.  Алгоритм требует O(n³) операций для матрицы размерности `n × n`.

**Практическая реализация:**

1. Для каждого столбца `i` выделяется подвектор `x = B[i+1:, i]`.
2. Вычисляется `σ = -sign(x[0]) * ||x||₂` для устойчивости.
3. Строится вектор `v = x - σ * e₁` и нормализуется.
4. Матрица отражения `H` формируется для подматрицы и расширяется до размерности исходной матрицы.
5. Двустороннее преобразование `B = H_p @ B @ H_p` сохраняет симметрию и собственные значения.

**Параметры:**

* `X` (`np.ndarray`, форма `(n, n)`): Входная симметричная матрица.
* `TOL` (`float`, по умолчанию `1e-12`): Пороговое значение для проверки нормы вектора `v`.

**Возвращает:**

* `B` (`np.ndarray`, форма `(n, n)`): Трёхдиагональная матрица.  Если норма вектора `v` слишком мала, соответствующее преобразование пропускается.

**Примеры:**

```python
import numpy as np

A = np.array([[4, 1, -2, 2], 
              [1, 2, 0, 1], 
              [-2, 0, 3, -2], 
              [2, 1, -2, -1]])
B = Householder(A)
print("Трёхдиагональная форма:")
print(B)
# Вывод (может незначительно отличаться из-за округления):
# [[ 4.00000000e+00 -3.00000000e+00 -2.22044605e-16 -4.44089210e-16]
#  [-3.00000000e+00  3.33333333e+00 -1.66666667e+00  1.11022302e-16]
#  [-2.22044605e-16 -1.66666667e+00 -1.33333333e+00 -9.42809042e-01]
#  [-4.44089210e-16  1.11022302e-16 -9.42809042e-01  1.99999999e+00]]
```

**Примечания:**

* Метод применим только для симметричных матриц.
* Каждое преобразование Хаусхолдера сохраняет ортогональность и симметрию матрицы.
* Порог `TOL` предотвращает деление на ноль при нормализации вектора `v`.
* Функция использует `tqdm` для отображения прогресс-бара.

## Вспомогательные функции (предполагаемые)

Модуль `householder` использует ряд вспомогательных функций, которые не включены в предоставленный код, но предполагаются необходимыми для его работы.  К ним относятся:

* `norm(vector)`: Вычисляет норму вектора.
* `subtract_vectors_njit(v1, v2)`: Вычитает вектор `v2` из вектора `v1`.
* `scalar_multiply_vector_njit(scalar, vector)`: Умножает вектор на скаляр.
* `dot_product_njit(v1, v2)`: Вычисляет скалярное произведение двух векторов.
* `scalar_multiply_matrix_njit(scalar, matrix)`: Умножает матрицу на скаляр.
* `subtract_matrices_njit(m1, m2)`: Вычитает матрицу `m2` из матрицы `m1`.
* `multiply_matrices_njit(m1, m2)`: Умножает матрицу `m1` на матрицу `m2`.

Эти функции, вероятно, также оптимизированы с использованием `numba` для обеспечения высокой производительности.

## Зависимости

* `numpy`: Для работы с массивами и матрицами.
* `numba`: Для ускорения вычислений с помощью JIT-компиляции.
* `tqdm`: Для отображения индикатора прогресса в функции `Householder`.

## Ссылки

* "Householder transformation - Wikipedia", https://en.wikipedia.org/wiki/Householder_transformation
* "QR-разложение методом Хаусхолдера - MathWorks", https://www.mathworks.com/help/matlab/ref/qr.html
* "Matrix Computations" - Golub G.H., Van Loan C.F., Johns Hopkins University Press, 2013.
* "Numba: High-Performance Python", https://numba.pydata.org/
* "Numerical Linear Algebra" - Trefethen L.N., Bau D., SIAM, 1997.
* "Сведение симметричной матрицы к трехдиагональной форме", https://example.com/householder-symmetric (пример ссылки)
* "Метод Хаусхолдера для QTQ^T-разложения", https://example.com/householder-qtq (пример ссылки)
* "Вычислительная линейная алгебра с примерами на MATLAB", Горбаченко В.И., 2011.
* "tqdm: A Fast, Extensible Progress Bar for Python", https://github.com/tqdm/tqdm

**Примечание:**  Некоторые ссылки (например, "Сведение симметричной матрицы...") являются примерами и могут не вести на реальные ресурсы.

## Функции

### 1. `dot_product_njit(v1, v2)`

**Описание:** Вычисляет скалярное произведение двух векторов.

1. **Параметры:**

- `v1` (np.ndarray, shape (n,)): Первый вектор.
- `v2` (np.ndarray, shape (n,)): Второй вектор.

**Возвращает:**

- `dot_product` (float): Скалярное произведение векторов `v1` и `v2`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import dot_product_njit

v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
result = dot_product_njit(v1, v2)
print(result)  # Вывод: 32.0
```

**Теоретическая справка:**
Скалярное произведение (также известное как внутреннее произведение) двух векторов **v1** = (v1₁, v1₂, ..., v1ₙ) и **v2** = (v2₁, v2₂, ..., v2ₙ) определяется как сумма произведений их соответствующих компонент:

**v1** · **v2** = v1₁ * v2₁ + v1₂ * v2₂ + ... + v1ₙ * v2ₙ

Скалярное произведение является важной операцией в линейной алгебре и имеет множество применений, включая вычисление углов между векторами, проекций и определение ортогональности векторов.

### 2. `norm(k)`

**Описание:** Вычисляет евклидову норму (длину) вектора.

**Параметры:**

- `k` (np.ndarray, shape (n,)): Вектор.

**Возвращает:**

- `norm_value` (float): Евклидова норма вектора `k`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import norm

v = np.array([3, 4])
result = norm(v)
print(result)  # Вывод: 5.0
```

**Теоретическая справка:**
Евклидова норма (или 2-норма) вектора **k** = (k₁, k₂, ..., kₙ) определяется как квадратный корень из суммы квадратов его компонент:

||**k**||₂ = √(k₁² + k₂² + ... + kₙ²)

Евклидова норма представляет собой длину вектора в евклидовом пространстве.

### 3. `subtract_vectors_njit(vector1, vector2)`

**Описание:** Вычитает два вектора поэлементно.

**Параметры:**

- `vector1` (np.ndarray, shape (n,)): Первый вектор.
- `vector2` (np.ndarray, shape (n,)): Второй вектор.

**Возвращает:**

- `result` (np.ndarray, shape (n,)): Результат вычитания `vector1 - vector2`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import subtract_vectors_njit

a = np.array([5, 3, 2])
b = np.array([1, 2, 3])
result = subtract_vectors_njit(a, b)
print(result)  # Вывод: [4 1 -1]
```

**Теоретическая справка:**
Вычитание векторов **a** = (a₁, a₂, ..., aₙ) и **b** = (b₁, b₂, ..., bₙ) выполняется поэлементно:

**a** - **b** = (a₁ - b₁, a₂ - b₂, ..., aₙ - bₙ)

### 4. `scalar_multiply_vector_njit(scalar, vector)`

**Описание:** Умножает вектор на скаляр.

**Параметры:**

- `scalar` (float): Скаляр.
- `vector` (np.ndarray, shape (n,)): Вектор.

**Возвращает:**

- `result` (np.ndarray, shape (n,)): Результат умножения `scalar * vector`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import scalar_multiply_vector_njit

scalar = 3.0
vector = np.array([1, 2, 3])
result = scalar_multiply_vector_njit(scalar, vector)
print(result)  # Вывод: [3. 6. 9.]
```

**Теоретическая справка:**
Умножение вектора **v** = (v₁, v₂, ..., vₙ) на скаляр c выполняется путем умножения каждой компоненты вектора на этот скаляр:

c * **v** = (c * v₁, c * v₂, ..., c * vₙ)

### 5. `scalar_multiply_matrix_njit(scalar, matrix)`

**Описание:** Умножает матрицу на скаляр.

**Параметры:**

- `scalar` (float): Скаляр.
- `matrix` (np.ndarray, shape (m, n)): Матрица.

**Возвращает:**

- `result` (np.ndarray, shape (m, n)): Результат умножения `scalar * matrix`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import scalar_multiply_matrix_njit

scalar = 2.0
matrix = np.array([[1, 2], [3, 4]])
result = scalar_multiply_matrix_njit(scalar, matrix)
print(result)
# Вывод:
# [[2. 4.]
#  [6. 8.]]
```

**Теоретическая справка:**
Умножение матрицы **A** размера m × n на скаляр c выполняется путем умножения каждого элемента матрицы на этот скаляр:

(c * **A**)ᵢⱼ = c * Aᵢⱼ для всех i = 1, ..., m и j = 1, ..., n

### 6. `subtract_matrices_njit(matrix1, matrix2)`

**Описание:** Вычитает две матрицы поэлементно.

**Параметры:**

- `matrix1` (np.ndarray, shape (m, n)): Первая матрица.
- `matrix2` (np.ndarray, shape (m, n)): Вторая матрица.

**Возвращает:**

- `result` (np.ndarray, shape (m, n)): Результат вычитания `matrix1 - matrix2`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import subtract_matrices_njit

A = np.array([[5, 3], [2, 1]])
B = np.array([[1, 2], [3, 4]])
result = subtract_matrices_njit(A, B)
print(result)
# Вывод:
# [[ 4  1]
#  [-1 -3]]
```

**Теоретическая справка:**
Вычитание матриц **A** и **B** одинакового размера m × n выполняется поэлементно:

(**A** - **B**)ᵢⱼ = Aᵢⱼ - Bᵢⱼ для всех i = 1, ..., m и j = 1, ..., n

### 7. `multiply_matrices_njit(matrix1, matrix2)`

**Описание:** Умножает две матрицы.

**Параметры:**

- `matrix1` (np.ndarray, shape (m, n)): Первая матрица.
- `matrix2` (np.ndarray, shape (n, p)): Вторая матрица.

**Возвращает:**

- `result` (np.ndarray, shape (m, p)): Результат умножения `matrix1 @ matrix2`.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import multiply_matrices_njit

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
result = multiply_matrices_njit(A, B)
print(result)
# Вывод:
# [[19 22]
#  [43 50]]
```

**Теоретическая справка:**
Умножение матрицы **A** размера m × n на матрицу **B** размера n × p дает матрицу **C** размера m × p, элементы которой вычисляются по формуле:

Cᵢⱼ = Σₖ₌₁ⁿ Aᵢₖ * Bₖⱼ для всех i = 1, ..., m и j = 1, ..., p

### 8. `transpose_matrix_njit(matrix)`

**Описание:** Транспонирует матрицу.

**Параметры:**

- `matrix` (np.ndarray, shape (m, n)): Матрица.

**Возвращает:**

- `transposed_matrix` (np.ndarray, shape (n, m)): Транспонированная матрица.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import transpose_matrix_njit

A = np.array([[1, 2], [3, 4]])
result = transpose_matrix_njit(A)
print(result)
# Вывод:
# [[1 3]
#  [2 4]]
```

**Теоретическая справка:**
Транспонирование матрицы **A** размера m × n дает матрицу **Aᵀ** размера n × m, в которой строки и столбцы меняются местами:

(**Aᵀ**)ᵢⱼ = Aⱼᵢ для всех i = 1, ..., n и j = 1, ..., m

### 9. `create_diagonal_matrix_njit(values)`

**Описание:** Создает диагональную матрицу из вектора значений.

**Параметры:**

- `values` (np.ndarray, shape (n,)): Вектор значений.

**Возвращает:**

- `matrix` (np.ndarray, shape (n, n)): Диагональная матрица.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import create_diagonal_matrix_njit

values = np.array([1, 2, 3])
result = create_diagonal_matrix_njit(values)
print(result)
# Вывод:
# [[1 0 0]
#  [0 2 0]
#  [0 0 3]]
```

**Теоретическая справка:**
Диагональная матрица — это квадратная матрица, у которой все элементы вне главной диагонали равны нулю. Главная диагональ состоит из элементов с одинаковыми индексами строки и столбца.

### 10. `invert_diagonal_matrix_njit(matrix, FLOAT_TOLERANCE=1e-10)`

**Описание:** Инвертирует диагональную матрицу.

**Параметры:**

- `matrix` (np.ndarray, shape (n, n)): Диагональная матрица.
- `FLOAT_TOLERANCE` (float, optional): Порог для проверки на вырожденность. По умолчанию 1e-10.

**Возвращает:**

- `result` (np.ndarray, shape (n, n)): Обратная матрица или матрица с NaN, если входная матрица вырождена.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import invert_diagonal_matrix_njit

A = np.diag([2.0, 4.0, 5.0])
result = invert_diagonal_matrix_njit(A)
print(result)
# Вывод:
# [[0.5   0.    0.  ]
#  [0.    0.25  0.  ]
#  [0.    0.    0.2 ]]

B = np.diag([0.0, 3.0, 6.0])
result = invert_diagonal_matrix_njit(B)
print(result)
# Вывод:
# [[nan nan nan]
#  [nan nan nan]
#  [nan nan nan]]
```

**Теоретическая справка:**
Обратная матрица для диагональной матрицы получается путем инвертирования каждого элемента на главной диагонали. Если какой-либо элемент на диагонали равен нулю (или меньше `FLOAT_TOLERANCE`), матрица считается вырожденной, и обратной матрицы не существует.

### 11. `lu_decomp_njit(A, FLOAT_TOLERANCE=1e-12)`

**Описание:** Выполняет LU-разложение матрицы.

**Параметры:**

- `A` (np.ndarray, shape (n, n)): Квадратная матрица.
- `FLOAT_TOLERANCE` (float, optional): Порог для проверки на вырожденность. По умолчанию 1e-12.

**Возвращает:**

- `L` (np.ndarray, shape (n, n)): Нижняя треугольная матрица.
- `U` (np.ndarray, shape (n, n)): Верхняя треугольная матрица.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import lu_decomp_njit

A = np.array([[4, 3], [6, 3]])
L, U = lu_decomp_njit(A)
print("L:\n", L)
print("U:\n", U)
# Вывод:
# L:
#  [[1.   0. ]
#  [1.5  1. ]]
# U:
#  [[ 4.   3. ]
#  [ 0.  -1.5]]
```

**Теоретическая справка:**
LU-разложение (разложение на нижнюю и верхнюю треугольные матрицы) представляет матрицу **A** в виде произведения нижней треугольной матрицы **L** и верхней треугольной матрицы **U**:

**A** = **L** **U**

Это разложение используется для решения систем линейных уравнений, вычисления определителей и обращения матриц.

### 12. `solve_lu_njit(L, U, b, FLOAT_TOLERANCE=1e-12)`

**Описание:** Решает систему линейных уравнений с использованием LU-разложения.

**Параметры:**

- `L` (np.ndarray, shape (n, n)): Нижняя треугольная матрица.
- `U` (np.ndarray, shape (n, n)): Верхняя треугольная матрица.
- `b` (np.ndarray, shape (n,)): Вектор правой части.
- `FLOAT_TOLERANCE` (float, optional): Порог для проверки на вырожденность. По умолчанию 1e-12.

**Возвращает:**

- `x` (np.ndarray, shape (n,)): Решение системы уравнений или вектор с NaN, если система не имеет решения.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import lu_decomp_njit, solve_lu_njit

A = np.array([[4, 3], [6, 3]])
b = np.array([7, 9])
L, U = lu_decomp_njit(A)
x = solve_lu_njit(L, U, b)
print(x)  # Вывод: [1. 1.]
```

**Теоретическая справка:**
Для решения системы **A** **x** = **b** с использованием LU-разложения (**A** = **L** **U**) выполняются следующие шаги:

1. Решается система **L** **y** = **b** относительно **y** (прямая подстановка).
2. Решается система **U** **x** = **y** относительно **x** (обратная подстановка).

### 13. `QR_dec(A, FLOAT_TOLERANCE=1e-12)`

**Описание:** Выполняет QR-разложение матрицы методом Грама-Шмидта.

**Параметры:**

- `A` (np.ndarray, shape (n, m)): Матрица.
- `FLOAT_TOLERANCE` (float, optional): Порог для проверки на вырожденность. По умолчанию 1e-12.

**Возвращает:**

- `Q` (np.ndarray, shape (n, m)): Ортогональная матрица.
- `R` (np.ndarray, shape (m, m)): Верхняя треугольная матрица.

**Пример:**

```python
import numpy as np
from matplobblib.nm.matrices import QR_dec

A = np.array([[1, 2], [3, 4]])
Q, R = QR_dec(A)
print("Q:\n", Q)
print("R:\n", R)
# Вывод:
# Q:
# [[-0.316 -0.949]
#  [-0.949  0.316]]
# R:
# [[-3.162 -4.427]
#  [ 0.   -0.632]]
```

**Теоретическая справка:**
QR-разложение представляет матрицу **A** в виде произведения ортогональной матрицы **Q** и верхней треугольной матрицы **R**:

**A** = **Q** **R**

Метод Грама-Шмидта используется для ортогонализации столбцов матрицы **A** с целью получения матрицы **Q**.

## Использование Numba

Все функции в модуле `matrices` декорированы `@njit` из библиотеки Numba. Это позволяет Numba компилировать Python-код в оптимизированный машинный код "на лету" (just-in-time compilation), что значительно ускоряет выполнение операций линейной алгебры по сравнению с использованием чистого NumPy, особенно для больших матриц и векторов.

## Примечания

1. Функции предполагают, что входные данные имеют корректные размеры и типы. Проверка входных данных не выполняется для обеспечения максимальной производительности.
2. При работе с диагональными матрицами недиагональные элементы не проверяются и предполагаются равными нулю.
3. LU-разложение выполняется без выбора ведущего элемента, что может привести к численной нестабильности в некоторых случаях.
4. QR-разложение выполняется методом Грама-Шмидта, который также может быть численно нестабильным при работе с почти линейно зависимыми столбцами.
5. Параметр `FLOAT_TOLERANCE` используется для обработки случаев деления на ноль и вырожденных матриц.
