import numpy as np
import scipy.sparse as sps
import timeit
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
42)
np.random.seed(
= np.linspace(200, 2000, dtype=int)
n_values = 50
density
# Заранее создаём и храним матрицы и векторы
= []
sparse_matrices = []
dense_matrices = []
vectors
for n in n_values:
= sps.random(n, n, density=density/n**2, format='csr', random_state=42)
A_sparse = A_sparse.toarray()
A_dense = np.random.rand(n)
x
sparse_matrices.append(A_sparse)
dense_matrices.append(A_dense)
vectors.append(x)
= []
sparse_times = []
dense_times
= tqdm(range(len(n_values)))
pbar
for i in pbar:
f"Размер матрицы {n_values[i]}x{n_values[i]}")
pbar.set_description(= n_values[i]
n = sparse_matrices[i]
A_sparse = dense_matrices[i]
A_dense = vectors[i]
x
# Прогрев (не меряем это время, просто вызываем 1 раз)
A_sparse.dot(x)
A_dense.dot(x)
# Параметры timeit
= 200 # В каждом "повторе" делаем 200 умножений
number_runs = 5 # Сколько серий замеров делаем
repeat_runs
= """
setup_code import numpy as np
import scipy.sparse as sps
from __main__ import A_sparse, A_dense, x
"""
# Собираем многократные замеры и берём медиану
= timeit.repeat(stmt="A_sparse.dot(x)",
times_sparse =setup_code,
setup=repeat_runs,
repeat=number_runs)
number= timeit.repeat(stmt="A_dense.dot(x)",
times_dense =setup_code,
setup=repeat_runs,
repeat=number_runs)
number
= np.median(times_sparse) / number_runs
median_sparse = np.median(times_dense) / number_runs
median_dense
sparse_times.append(median_sparse) dense_times.append(median_dense)
# Построение графика
=(6, 4))
plt.figure(figsize='o', label='CSR')
plt.plot(n_values, sparse_times, marker='x', label='Dense')
plt.plot(n_values, dense_times, marker'log')
plt.yscale(=':')
plt.grid(linestyle'Размер матрицы (N)')
plt.xlabel('Медианное время матвека (с)')
plt.ylabel(f'Сравнение времени матвека, nnz={density}')
plt.title(2e-6,1e-3)
plt.ylim(
plt.legend()'sparse_fixed_nnz.pdf')
plt.savefig( plt.show()
import numpy as np
import scipy.sparse as sps
import timeit
import matplotlib.pyplot as plt
from tqdm.auto import tqdm
42)
np.random.seed(
= np.linspace(200, 2000, dtype=int)
n_values = 0.01
density
# Заранее создаём и храним матрицы и векторы
= []
sparse_matrices = []
dense_matrices = []
vectors
for n in n_values:
= sps.random(n, n, density=density, format='csr', random_state=42)
A_sparse = A_sparse.toarray()
A_dense = np.random.rand(n)
x
sparse_matrices.append(A_sparse)
dense_matrices.append(A_dense)
vectors.append(x)
= []
sparse_times = []
dense_times
= tqdm(range(len(n_values)))
pbar
for i in pbar:
f"Размер матрицы {n_values[i]}x{n_values[i]}")
pbar.set_description(= n_values[i]
n = sparse_matrices[i]
A_sparse = dense_matrices[i]
A_dense = vectors[i]
x
# Прогрев (не меряем это время, просто вызываем 1 раз)
A_sparse.dot(x)
A_dense.dot(x)
# Параметры timeit
= 200 # В каждом "повторе" делаем 200 умножений
number_runs = 5 # Сколько серий замеров делаем
repeat_runs
= """
setup_code import numpy as np
import scipy.sparse as sps
from __main__ import A_sparse, A_dense, x
"""
# Собираем многократные замеры и берём медиану
= timeit.repeat(stmt="A_sparse.dot(x)",
times_sparse =setup_code,
setup=repeat_runs,
repeat=number_runs)
number= timeit.repeat(stmt="A_dense.dot(x)",
times_dense =setup_code,
setup=repeat_runs,
repeat=number_runs)
number
= np.median(times_sparse) / number_runs
median_sparse = np.median(times_dense) / number_runs
median_dense
sparse_times.append(median_sparse) dense_times.append(median_dense)
# Построение графика
=(6, 4))
plt.figure(figsize='o', label='CSR')
plt.plot(n_values, sparse_times, marker='x', label='Dense')
plt.plot(n_values, dense_times, marker'log')
plt.yscale(=':')
plt.grid(linestyle'Размер матрицы (N)')
plt.xlabel('Медианное время матвека (с)')
plt.ylabel(f'Сравнение времени матвека, плотность={density}')
plt.title(2e-6,1e-3)
plt.ylim(
plt.legend()'sparse_fixed_density.pdf')
plt.savefig( plt.show()
Потеря разреженности
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as spla
import matplotlib.pyplot as plt
# 1. Создание разреженной матрицы
# Создадим матрицу, где LU разложение без перестановок приведет к заполнению.
# Пример: "стреловидная" матрица (arrowhead matrix)
= 10
n = np.diag(np.random.rand(n) + 1) # Диагональ
A_dense 0, 1:] = np.random.rand(n - 1) * 0.5
A_dense[1:, 0] = np.random.rand(n - 1) * 0.5
A_dense[= sp.csc_matrix(A_dense) # Преобразуем в разреженный формат CSC (Compressed Sparse Column)
A
print("Исходная разреженная матрица A (ненулевые элементы):")
print(A)
# 2. LU разложение без перестановок (естественный порядок)
# Используем splu (SuperLU) из scipy.sparse.linalg
# permc_spec="NATURAL" означает отсутствие перестановок столбцов
= spla.splu(A, permc_spec="NATURAL", diag_pivot_thresh=0) # diag_pivot_thresh=0 отключает частичный пивотинг
lu_natural
= lu_natural.L
L_natural = lu_natural.U
U_natural
# 3. LU разложение с перестановками для минимизации заполнения
# Используем COLAMD (Column Approximate Minimum Degree) - популярный алгоритм
# splu по умолчанию использует перестановки для уменьшения заполнения
= spla.splu(A, diag_pivot_thresh=0) # По умолчанию permc_spec='COLAMD'
lu_permuted
= lu_permuted.L
L_permuted = lu_permuted.U
U_permuted # Важно: Факторы L и U соответствуют матрице P @ A @ Q, где P и Q - матрицы перестановок
# lu_permuted.perm_r содержит индексы перестановки строк (P)
# lu_permuted.perm_c содержит индексы перестановки столбцов (Q)
# 4. Визуализация разреженности
= plt.subplots(2, 3, figsize=(12, 8))
fig, axs "Влияние перестановок на заполненность при LU-разложении", fontsize=16)
fig.suptitle(
# Исходная матрица
0, 0].spy(A, markersize=5)
axs[0, 0].set_title(f"Исходная A ({A.nnz} ненулевых)")
axs[0, 0].set_xticks([])
axs[0, 0].set_yticks([])
axs[
# Факторы без перестановок
0, 1].spy(L_natural, markersize=5)
axs[0, 1].set_title(f"L (без перестановок) ({L_natural.nnz} ненулевых)")
axs[0, 1].set_xticks([])
axs[0, 1].set_yticks([])
axs[
0, 2].spy(U_natural, markersize=5)
axs[0, 2].set_title(f"U (без перестановок) ({U_natural.nnz} ненулевых)")
axs[0, 2].set_xticks([])
axs[0, 2].set_yticks([])
axs[
# Переставленная матрица (для наглядности, хотя splu работает с ней неявно)
= sp.csc_matrix((np.ones(n), (lu_permuted.perm_r, np.arange(n))))
P = sp.csc_matrix((np.ones(n), (np.arange(n), lu_permuted.perm_c)))
Q = P @ A @ Q
A_permuted
1, 0].spy(A_permuted, markersize=5)
axs[1, 0].set_title(f"P @ A @ Q ({A_permuted.nnz} ненулевых)")
axs[1, 0].set_xticks([])
axs[1, 0].set_yticks([])
axs[
# Факторы с перестановками
1, 1].spy(L_permuted, markersize=5)
axs[1, 1].set_title(f"L (с COLAMD) ({L_permuted.nnz} ненулевых)")
axs[1, 1].set_xticks([])
axs[1, 1].set_yticks([])
axs[
1, 2].spy(U_permuted, markersize=5)
axs[1, 2].set_title(f"U (с COLAMD) ({U_permuted.nnz} ненулевых)")
axs[1, 2].set_xticks([])
axs[1, 2].set_yticks([])
axs[
=[0, 0.03, 1, 0.95]) # Adjust layout to prevent title overlap
plt.tight_layout(rect'sparse_permutation.pdf')
plt.savefig(
plt.show()
print("\nСравнение количества ненулевых элементов:")
print(f"Исходная матрица A: {A.nnz}")
print(f"L (без перестановок): {L_natural.nnz}")
print(f"U (без перестановок): {U_natural.nnz}")
print(f"Сумма L+U (без перестановок): {L_natural.nnz + U_natural.nnz - n}") # Вычитаем диагональ L, которая вся единицы
print("-" * 20)
print(f"L (с COLAMD): {L_permuted.nnz}")
print(f"U (с COLAMD): {U_permuted.nnz}")
print(f"Сумма L+U (с COLAMD): {L_permuted.nnz + U_permuted.nnz - n}")
Исходная разреженная матрица A (ненулевые элементы):
(0, 0) 1.8776782503720555
(1, 0) 0.28666311355271434
(2, 0) 0.06125074516763307
(3, 0) 0.20815901357243471
(4, 0) 0.14911718572511817
(5, 0) 0.4377389771128421
(6, 0) 0.2536100905730013
(7, 0) 0.28875627305162155
(8, 0) 0.1176452024641696
(9, 0) 0.3995317737956705
(0, 1) 0.16470247073023336
(1, 1) 1.535850677660174
(0, 2) 0.4023472696302903
(2, 2) 1.4785209479554453
(0, 3) 0.4455542569562675
(3, 3) 1.7728187724647264
(0, 4) 0.3091274855628439
(4, 4) 1.7410960035518377
(0, 5) 0.2777895963540577
(5, 5) 1.7042765323899776
(0, 6) 0.33594337379731914
(6, 6) 1.6899009560005396
(0, 7) 0.08153032873935356
(7, 7) 1.5153959971781843
(0, 8) 0.04367183810471542
(8, 8) 1.9264755359754915
(0, 9) 0.41007513748355745
(9, 9) 1.8906414097273678
Сравнение количества ненулевых элементов:
Исходная матрица A: 28
L (без перестановок): 55
U (без перестановок): 55
Сумма L+U (без перестановок): 100
--------------------
L (с COLAMD): 19
U (с COLAMD): 19
Сумма L+U (с COLAMD): 28