Merge pull request 'malafeev_leonid_lab_6' (#398) from malafeev_leonid_lab_6 into main

Reviewed-on: #398
This commit was merged in pull request #398.
This commit is contained in:
2025-12-08 23:00:07 +04:00
3 changed files with 215 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# Лабораторная работа по параллельному вычислению детерминанта матрицы (LU-разложение)
Сделал, как просили — два алгоритма вычисления детерминанта квадратной матрицы: последовательный и параллельный. Использовал LU-разложение (метод Гаусса). Сделал бенчмарки. Работает, считает быстро.
## Что делает
Программа реализует:
1. **Последовательное вычисление** детерминанта через LU-разложение (метод Гаусса с выбором ведущего элемента).
2. **Параллельное вычисление** детерминанта тем же методом, где **внутри каждой итерации** внешнего цикла (когда обновляются строки под ведущим элементом) вычисления для **разных строк** распределяются между потоками.
3. **Задание со ***: параллельный алгоритм с 1 потоком работает как обычный последовательный (внутри итерации распределяется 1 строка, обработка которой происходит последовательно).
4. **Бенчмарки**: измеряет время выполнения для матриц размером 100x100, 300x300, 500x500 с разным количеством потоков.
5. **Сравнение**: выводит таблицу результатов (время, ускорение) и сохраняет её в файл `benchmark_determinant_lu_results.csv`.
## Технологии
1. Python 3
2. Библиотека `numpy` для работы с матрицами
3. Библиотека `pandas` для форматирования таблицы результатов
4. Модуль `concurrent.futures` для управления потоками
## Как запустить
1. Убедиться, что установлен Python 3.
2. Установить зависимости:
`pip install numpy pandas`
3. В терминале перейти в папку с файлом `main_determinant.py`.
4. Выполнить команду:
`python main_determinant.py`
## Тесты
Программа запускает серию тестов:
1. Генерирует "устойчивые" случайные матрицы заданных размеров (100x100, 300x300, 500x500).
2. Вычисляет детерминант с помощью последовательного алгоритма (LU-разложение).
3. Вычисляет детерминант с помощью параллельного алгоритма (LU-разложение с распределением строк между потоками внутри итерации).
4. Сравнивает значения детерминантов (могут отличаться из-за погрешностей вычислений с плавающей точкой).
5. Замеряет и выводит время выполнения для каждого теста.
6. Вычисляет и выводит ускорение (`sequential_time / parallel_time`).
7. Собирает все результаты (размер, алгоритм, потоки, время, детерминант, ускорение) в `pandas.DataFrame` и выводит в консоль в виде таблицы.
8. Сохраняет таблицу с результатами в файл `benchmark_determinant_lu_results.csv`.
## Видео
https://vkvideo.ru/video-233205700_456239023

View File

@@ -0,0 +1,13 @@
Размер,Алгоритм,Потоки,Время (с),Детерминант,Ускорение
100,Последовательный,1,0.02842402458190918,25297894.350354116,
100,Параллельный,2,0.1195516586303711,25297894.350354116,0.2377551671599167
100,Параллельный,4,0.16419363021850586,25297894.350354116,0.17311283357389085
100,Параллельный,8,0.21109366416931152,25297894.350354116,0.13465124447842816
300,Последовательный,1,0.2608449459075928,9.962891199441524e+21,
300,Параллельный,2,0.5623910427093506,9.962891199441524e+21,0.463814189946834
300,Параллельный,4,0.6823325157165527,9.962891199441524e+21,0.38228420879762115
300,Параллельный,8,0.8545553684234619,9.962891199441524e+21,0.30524054443519105
500,Последовательный,1,0.7417495250701904,8.547565819508747e+37,
500,Параллельный,2,1.2983334064483643,8.547565819508747e+37,0.571308973015854
500,Параллельный,4,1.52494215965271,8.547565819508747e+37,0.4864115798589478
500,Параллельный,8,1.7622523307800293,8.547565819508747e+37,0.42090994128058173
1 Размер Алгоритм Потоки Время (с) Детерминант Ускорение
2 100 Последовательный 1 0.02842402458190918 25297894.350354116
3 100 Параллельный 2 0.1195516586303711 25297894.350354116 0.2377551671599167
4 100 Параллельный 4 0.16419363021850586 25297894.350354116 0.17311283357389085
5 100 Параллельный 8 0.21109366416931152 25297894.350354116 0.13465124447842816
6 300 Последовательный 1 0.2608449459075928 9.962891199441524e+21
7 300 Параллельный 2 0.5623910427093506 9.962891199441524e+21 0.463814189946834
8 300 Параллельный 4 0.6823325157165527 9.962891199441524e+21 0.38228420879762115
9 300 Параллельный 8 0.8545553684234619 9.962891199441524e+21 0.30524054443519105
10 500 Последовательный 1 0.7417495250701904 8.547565819508747e+37
11 500 Параллельный 2 1.2983334064483643 8.547565819508747e+37 0.571308973015854
12 500 Параллельный 4 1.52494215965271 8.547565819508747e+37 0.4864115798589478
13 500 Параллельный 8 1.7622523307800293 8.547565819508747e+37 0.42090994128058173

View File

@@ -0,0 +1,163 @@
import numpy as np
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
import pandas as pd
import threading
def sequential_lu_determinant(matrix):
"""
Последовательный алгоритм вычисления детерминанта через LU-разложение
(метод Гаусса с выбором главного элемента по столбцу).
"""
n = matrix.shape[0]
A = matrix.astype(np.float64).copy()
sign = 1
for i in range(n):
# Поиск максимального элемента в столбце i, начиная с диагонали
max_row = i + np.argmax(np.abs(A[i:n, i]))
if max_row != i:
A[[i, max_row]] = A[[max_row, i]]
sign *= -1 # Смена строк меняет знак детерминанта
# Если ведущий элемент близок к нулю, детерминант = 0
if abs(A[i, i]) < 1e-15:
return 0.0
# Исключение столбца i
for k in range(i + 1, n):
factor = A[k, i] / A[i, i]
A[k, i:] -= factor * A[i, i:]
# Детерминант = произведение диагональных элементов
diag_product = np.prod(np.diag(A))
return sign * diag_product
def worker_update_rows(args):
A_full, i, start_k, end_k, pivot_row, pivot_elem = args
# Извлекаем срезы для удобства
A_rows_to_update = A_full[start_k:end_k, :]
A_pivot_row = pivot_row # Уже передаём нужную строку
for idx, k in enumerate(range(start_k, end_k)):
factor = A_rows_to_update[idx, i] / pivot_elem
A_rows_to_update[idx, i:] -= factor * A_pivot_row[i:]
def parallel_lu_determinant(matrix, num_threads=4):
"""
Параллельный алгоритм вычисления детерминанта через LU-разложение.
Параллелизация происходит внутри каждой итерации внешнего цикла (i):
строки, подлежащие обновлению (k от i+1 до n-1), распределяются между потоками.
"""
n = matrix.shape[0]
A = matrix.astype(np.float64).copy()
sign = 1
for i in range(n):
# Поиск максимального элемента в столбце i
max_row = i + np.argmax(np.abs(A[i:n, i]))
if max_row != i:
A[[i, max_row]] = A[[max_row, i]]
sign *= -1
if abs(A[i, i]) < 1e-15:
return 0.0
rows_to_update = list(range(i + 1, n))
if not rows_to_update:
continue
# Распределяем строки rows_to_update между потоками
chunk_size = len(rows_to_update) // num_threads
remainder = len(rows_to_update) % num_threads
futures = []
start_idx = 0
with ThreadPoolExecutor(max_workers=num_threads) as executor:
for t in range(num_threads):
# Определяем, какие строки обработает поток t
chunk_size_t = chunk_size + (1 if t < remainder else 0)
end_idx = start_idx + chunk_size_t
if chunk_size_t > 0:
start_k = rows_to_update[start_idx]
end_k = rows_to_update[end_idx - 1] + 1
# Подготовка аргументов для задачи
task_args = (A, i, start_k, end_k, A[i, :], A[i, i])
future = executor.submit(worker_update_rows, task_args)
futures.append(future)
start_idx = end_idx
# Ждём завершения всех задач для текущей итерации i
for future in futures:
future.result()
diag_product = np.prod(np.diag(A))
return sign * diag_product
def generate_stable_matrix(size, scale=1.0):
"""
Генерирует устойчивую (по определителю) случайную матрицу.
"""
np.random.seed(13)
random_matrix = np.random.randn(size, size)
Q, R = np.linalg.qr(random_matrix)
eigenvalues = np.random.uniform(0.5, 2.0, size) * scale
D = np.diag(eigenvalues)
A = Q @ D @ Q.T
return A
def benchmark_determinant():
"""Функция для проведения бенчмарков детерминанта с LU-разложением."""
sizes = [100, 300, 500]
thread_counts = [1, 2, 4, 8]
results = []
print("=" * 80)
print("БЕНЧМАРК ВЫЧИСЛЕНИЯ ДЕТЕРМИНАНТА (LU-РАЗЛОЖЕНИЕ)")
print("=" * 80)
for size in sizes:
print(f"\n--- Тестирование для матриц размером {size}x{size} ---")
matrix = generate_stable_matrix(size)
# --- Последовательный алгоритм (1 поток = обычный алгоритм) ---
start_time = time.time()
det_seq = sequential_lu_determinant(matrix.copy()) # Копируем, чтобы не изменять исходную
seq_time = time.time() - start_time
print(f"Последовательный: {seq_time:.4f} секунд, детерминант = {det_seq:.6e}")
results.append({'Размер': size, 'Алгоритм': 'Последовательный', 'Потоки': 1, 'Время (с)': seq_time, 'Детерминант': det_seq})
# --- Параллельный алгоритм для разных чисел потоков ---
for num_threads in thread_counts:
if num_threads == 1:
continue # Пропускаем 1 поток, так как это последовательный (уже посчитано)
start_time = time.time()
det_par = parallel_lu_determinant(matrix.copy(), num_threads)
par_time = time.time() - start_time
print(f"Параллельный ({num_threads} потоков): {par_time:.4f} секунд, детерминант = {det_par:.6e}")
speedup = seq_time / par_time if par_time > 0 else 0
results.append({'Размер': size, 'Алгоритм': 'Параллельный', 'Потоки': num_threads, 'Время (с)': par_time, 'Детерминант': det_par, 'Ускорение': speedup})
print(f" Ускорение: {speedup:.2f}x")
# Создание и вывод таблицы результатов
df = pd.DataFrame(results)
print("\n--- Результаты бенчмарков ---")
print(df.to_string(index=False))
# Сохранение таблицы в файл
df.to_csv('benchmark_determinant_lu_results.csv', index=False)
print("\nРезультаты сохранены в файл 'benchmark_determinant_lu_results.csv'")
if __name__ == "__main__":
benchmark_determinant()