medvedkov_andrey_lab_6 is ready
This commit is contained in:
210
medvedkov_andrey_lab_6/lab.py
Normal file
210
medvedkov_andrey_lab_6/lab.py
Normal file
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Лабораторная работа:
|
||||
Вычисление детерминанта квадратной матрицы (последовательный и параллельный алгоритмы).
|
||||
|
||||
При запуске файла:
|
||||
- автоматически выполняется бенчмарк для матриц 100x100, 300x300, 500x500,
|
||||
- используется последовательный и параллельный алгоритмы,
|
||||
- для параллельного алгоритма тестируются числа потоков: 1, 2, 4, 8.
|
||||
"""
|
||||
|
||||
import random
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
Matrix = List[List[float]]
|
||||
|
||||
|
||||
# ==============================
|
||||
# Вспомогательные функции
|
||||
# ==============================
|
||||
|
||||
def generate_matrix(n: int,
|
||||
seed: Optional[int] = None,
|
||||
low: float = -10.0,
|
||||
high: float = 10.0) -> Matrix:
|
||||
"""
|
||||
Генерация случайной квадратной матрицы n x n.
|
||||
Элементы — вещественные числа в диапазоне [low, high].
|
||||
"""
|
||||
if seed is not None:
|
||||
random.seed(seed)
|
||||
return [[random.uniform(low, high) for _ in range(n)] for _ in range(n)]
|
||||
|
||||
|
||||
def upper_triangular_and_sign(matrix: Matrix) -> Tuple[Matrix, int]:
|
||||
"""
|
||||
Приведение матрицы к верхнетреугольному виду методом Гаусса
|
||||
с частичным выбором главного элемента.
|
||||
Возвращает:
|
||||
- новую матрицу (верхнетреугольную),
|
||||
- sign: +1 или -1 в зависимости от числа перестановок строк,
|
||||
либо 0, если найден нулевой столбец (det == 0).
|
||||
"""
|
||||
n = len(matrix)
|
||||
a = [row[:] for row in matrix] # копия, исходную матрицу не портим
|
||||
sign = 1
|
||||
eps = 1e-12
|
||||
|
||||
for i in range(n):
|
||||
# Поиск ведущего элемента (pivot) в столбце i
|
||||
pivot = i
|
||||
for j in range(i + 1, n):
|
||||
if abs(a[j][i]) > abs(a[pivot][i]):
|
||||
pivot = j
|
||||
|
||||
# Если весь столбец почти нулевой — детерминант = 0
|
||||
if abs(a[pivot][i]) < eps:
|
||||
return a, 0
|
||||
|
||||
# Если нужно — меняем строки местами
|
||||
if pivot != i:
|
||||
a[i], a[pivot] = a[pivot], a[i]
|
||||
sign *= -1
|
||||
|
||||
pivot_val = a[i][i]
|
||||
|
||||
# Обнуляем элементы ниже главной диагонали
|
||||
for j in range(i + 1, n):
|
||||
factor = a[j][i] / pivot_val
|
||||
row_j = a[j]
|
||||
row_i = a[i]
|
||||
for k in range(i, n):
|
||||
row_j[k] -= factor * row_i[k]
|
||||
|
||||
return a, sign
|
||||
|
||||
|
||||
# ==============================
|
||||
# Последовательный алгоритм
|
||||
# ==============================
|
||||
|
||||
def determinant_sequential(matrix: Matrix) -> float:
|
||||
"""
|
||||
Последовательное вычисление детерминанта:
|
||||
1) приводим к верхнетреугольной матрице,
|
||||
2) det = sign * произведение диагональных элементов.
|
||||
"""
|
||||
a, sign = upper_triangular_and_sign(matrix)
|
||||
if sign == 0:
|
||||
return 0.0
|
||||
|
||||
det = float(sign)
|
||||
n = len(a)
|
||||
for i in range(n):
|
||||
det *= a[i][i]
|
||||
return det
|
||||
|
||||
|
||||
# ==============================
|
||||
# Параллельный алгоритм
|
||||
# ==============================
|
||||
|
||||
def _product_chunk(diag: List[float], start: int, end: int) -> float:
|
||||
"""Произведение части диагонали diag[start:end]."""
|
||||
p = 1.0
|
||||
for i in range(start, end):
|
||||
p *= diag[i]
|
||||
return p
|
||||
|
||||
|
||||
def determinant_parallel(matrix: Matrix, num_threads: int = 4) -> float:
|
||||
"""
|
||||
"Параллельный" алгоритм:
|
||||
- так же приводим матрицу к верхнетреугольной (последовательно),
|
||||
- произведение диагональных элементов считаем в нескольких потоках.
|
||||
|
||||
При num_threads == 1 ведёт себя как последовательный алгоритм
|
||||
(задание со звёздочкой: один и тот же алгоритм для 1 и N потоков).
|
||||
"""
|
||||
a, sign = upper_triangular_and_sign(matrix)
|
||||
if sign == 0:
|
||||
return 0.0
|
||||
|
||||
n = len(a)
|
||||
diag = [a[i][i] for i in range(n)]
|
||||
|
||||
# Если пользователь указал 1 поток — выполняем произведение последовательно
|
||||
if num_threads <= 1 or n == 0:
|
||||
det = float(sign)
|
||||
for x in diag:
|
||||
det *= x
|
||||
return det
|
||||
|
||||
# Делим диагональ на блоки по числу потоков
|
||||
chunk_size = (n + num_threads - 1) // num_threads
|
||||
|
||||
with ThreadPoolExecutor(max_workers=num_threads) as executor:
|
||||
futures = []
|
||||
for start in range(0, n, chunk_size):
|
||||
end = min(start + chunk_size, n)
|
||||
futures.append(executor.submit(_product_chunk, diag, start, end))
|
||||
|
||||
det = float(sign)
|
||||
for f in futures:
|
||||
det *= f.result()
|
||||
|
||||
return det
|
||||
|
||||
|
||||
# ==============================
|
||||
# Бенчмарк
|
||||
# ==============================
|
||||
|
||||
def benchmark() -> None:
|
||||
"""
|
||||
Прогон бенчмарка для матриц 100x100, 300x300, 500x500.
|
||||
Число потоков для параллельного алгоритма: 1, 2, 4, 8.
|
||||
"""
|
||||
sizes = [100, 300, 500]
|
||||
thread_counts = [1, 2, 4, 8]
|
||||
|
||||
print("Бенчмарк детерминанта случайных матриц\n")
|
||||
|
||||
for n in sizes:
|
||||
print(f"=== Размер матрицы: {n}x{n} ===")
|
||||
# фиксируем seed, чтобы последовательный и параллельный считали одну и ту же матрицу
|
||||
matrix = generate_matrix(n, seed=42)
|
||||
|
||||
# Последовательный алгоритм
|
||||
t0 = time.perf_counter()
|
||||
det_seq = determinant_sequential(matrix)
|
||||
t1 = time.perf_counter()
|
||||
seq_time = t1 - t0
|
||||
|
||||
print(f"Последовательный: det = {det_seq:.3e}, "
|
||||
f"время = {seq_time:.4f} с")
|
||||
|
||||
# Параллельный алгоритм для разных чисел потоков
|
||||
for threads in thread_counts:
|
||||
t0 = time.perf_counter()
|
||||
det_par = determinant_parallel(matrix, num_threads=threads)
|
||||
t1 = time.perf_counter()
|
||||
par_time = t1 - t0
|
||||
|
||||
# Разница нужна только для проверки корректности (когда нет переполнения)
|
||||
diff = abs(det_par - det_seq) if (
|
||||
abs(det_seq) != float("inf")
|
||||
and abs(det_par) != float("inf")
|
||||
) else float("nan")
|
||||
|
||||
print(
|
||||
f"Параллельный (threads={threads}): det = {det_par:.3e}, "
|
||||
f"разность = {diff:.3e}, время = {par_time:.4f} с"
|
||||
)
|
||||
|
||||
print()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Точка входа: сразу запускаем бенчмарк.
|
||||
При проверке достаточно выполнить: python determinant_lab.py
|
||||
"""
|
||||
benchmark()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
146
medvedkov_andrey_lab_6/readme.md
Normal file
146
medvedkov_andrey_lab_6/readme.md
Normal file
@@ -0,0 +1,146 @@
|
||||
## Используемые технологии
|
||||
|
||||
- Язык программирования: **Python 3**
|
||||
- Стандартная библиотека Python:
|
||||
- `concurrent.futures.ThreadPoolExecutor` — создание пула потоков и ручное управление их количеством;
|
||||
- `time` — измерение времени выполнения;
|
||||
- `random` — генерация случайных матриц.
|
||||
|
||||
Дополнительные внешние библиотеки **не используются**, достаточно установленного Python 3.
|
||||
|
||||
## Как запустить лабораторную работу
|
||||
|
||||
### Требования
|
||||
|
||||
- Установленный **Python 3** (3.8+ достаточно).
|
||||
- Файлы `determinant_lab.py` и `README.md` лежат в одной директории.
|
||||
|
||||
### Запуск
|
||||
|
||||
Просто выполнить:
|
||||
|
||||
```bash
|
||||
python determinant_lab.py
|
||||
```
|
||||
|
||||
При запуске автоматически:
|
||||
|
||||
- генерируются случайные матрицы размером 100×100, 300×300 и 500×500;
|
||||
- вычисляется детерминант:
|
||||
- последовательным алгоритмом;
|
||||
- параллельным алгоритмом для числа потоков 1, 2, 4, 8;
|
||||
- на экран выводятся:
|
||||
- значение детерминанта (в научной нотации),
|
||||
- время работы для каждого варианта,
|
||||
- (при отсутствии переполнения) разница между детерминантами последовательного и параллельного вычислений.
|
||||
|
||||
Если нужно изменить набор размеров или числа потоков,
|
||||
это можно сделать, отредактировав списки `sizes` и `thread_counts`
|
||||
в функции `benchmark()`.
|
||||
|
||||
---
|
||||
|
||||
## Описание реализованных алгоритмов
|
||||
|
||||
### Последовательный алгоритм
|
||||
|
||||
Последовательная функция: `determinant_sequential(matrix: Matrix) -> float`
|
||||
|
||||
Идея:
|
||||
|
||||
1. Используется метод **Гаусса** с частичным выбором главного элемента:
|
||||
- матрица копируется во внутренний массив;
|
||||
- последовательно обрабатываются столбцы, выбирается максимальный по модулю элемент в текущем столбце — pivot;
|
||||
- при необходимости строки меняются местами, счётчик перестановок учитывается через знак `sign = ±1`;
|
||||
- ниже главной диагонали элементы зануляются.
|
||||
2. После приведения к **верхнетреугольному виду**:
|
||||
- детерминант равен произведению диагональных элементов,
|
||||
- умноженному на знак `sign` (зависящий от числа перестановок строк).
|
||||
|
||||
Формула:
|
||||
\[
|
||||
\det(A) = \mathrm{sign} \cdot \prod_{i=1}^{n} u_{ii},
|
||||
\]
|
||||
где \(u_{ii}\) — элементы на диагонали верхнетреугольной матрицы.
|
||||
|
||||
Алгоритмическая сложность: **O(n³)**.
|
||||
|
||||
---
|
||||
|
||||
### Параллельный алгоритм
|
||||
|
||||
Параллельная функция: `determinant_parallel(matrix: Matrix, num_threads: int) -> float`
|
||||
|
||||
Особенности:
|
||||
|
||||
1. Шаг приведения к верхнетреугольной матрице выполняется **так же, как в последовательной версии** —
|
||||
он остаётся последовательным (это основная трудоёмкость O(n³)).
|
||||
2. Отличие в том, как считается произведение диагональных элементов:
|
||||
- собирается список диагональных элементов `diag`;
|
||||
- он делится на `num_threads` примерно равных блоков;
|
||||
- каждый блок обрабатывается в отдельном потоке (`ThreadPoolExecutor`);
|
||||
- результаты (частичные произведения) перемножаются в один итоговый детерминант.
|
||||
|
||||
3. При `num_threads = 1`:
|
||||
- используется тот же код, но без распараллеливания;
|
||||
- таким образом **один и тот же алгоритм** реализует и последовательный, и параллельный режим
|
||||
(соответствие варианту задания со звёздочкой).
|
||||
|
||||
---
|
||||
|
||||
## Результаты бенчмарка
|
||||
|
||||
Бенчмарк выполнялся функцией `benchmark()` при запуске `determinant_lab.py`.
|
||||
Использовались случайные матрицы с элементами в диапазоне [-10, 10] и фиксированным `seed = 42`
|
||||
(для воспроизводимости результатов).
|
||||
|
||||
Ниже приведены **фактические** времена выполнения (в секундах), полученные при запуске:
|
||||
|
||||
### Время работы
|
||||
|
||||
| Размер матрицы | Алгоритм | Число потоков | Время, с |
|
||||
|----------------|-----------------|---------------|------------:|
|
||||
| 100×100 | последовательный| 1 | 0.0183 |
|
||||
| 100×100 | параллельный | 1 | 0.0179 |
|
||||
| 100×100 | параллельный | 2 | 0.0189 |
|
||||
| 100×100 | параллельный | 4 | 0.0195 |
|
||||
| 100×100 | параллельный | 8 | 0.0196 |
|
||||
| 300×300 | последовательный| 1 | 0.5635 |
|
||||
| 300×300 | параллельный | 1 | 0.5327 |
|
||||
| 300×300 | параллельный | 2 | 0.5677 |
|
||||
| 300×300 | параллельный | 4 | 0.5204 |
|
||||
| 300×300 | параллельный | 8 | 0.5358 |
|
||||
| 500×500 | последовательный| 1 | 2.6207 |
|
||||
| 500×500 | параллельный | 1 | 2.4511 |
|
||||
| 500×500 | параллельный | 2 | 2.4260 |
|
||||
| 500×500 | параллельный | 4 | 2.6239 |
|
||||
| 500×500 | параллельный | 8 | 2.3660 |
|
||||
|
||||
Для сравнения корректности можно отметить:
|
||||
|
||||
- Для матрицы 100×100 детерминант по модулю имеет разумный порядок (`≈ 1.8 · 10^153`),
|
||||
и последовательный и параллельный алгоритмы дают очень близкие значения
|
||||
(разность либо ноль, либо на уровне погрешности представления `float`).
|
||||
- Для матриц 300×300 и 500×500 значение детерминанта по модулю становится настолько большим,
|
||||
что во время эксперимента оно выходило за диапазон представимых значений типа `float`,
|
||||
и в выводе отображалось как `inf` или `-inf`. Это ожидаемое поведение
|
||||
для случайных матриц с элементами порядка 10 и не является ошибкой алгоритма.
|
||||
|
||||
---
|
||||
|
||||
## Анализ результатов
|
||||
|
||||
1. **Основное время** работы алгоритма тратится на приведение матрицы к верхнетреугольному виду,
|
||||
которое выполняется последовательно и имеет сложность O(n³).
|
||||
|
||||
2. Параллелизация реализована на этапе **перемножения диагональных элементов**, который по трудоёмкости
|
||||
значительно проще (O(n)). Поэтому существенного ускорения при увеличении числа потоков не наблюдается:
|
||||
накладные расходы на создание потоков и их синхронизацию сопоставимы с выигрышем.
|
||||
|
||||
3. Для размера 100×100 время параллельного алгоритма с 1–8 потоками практически совпадает
|
||||
с последовательным вариантом (отличия в пределах нескольких миллисекунд).
|
||||
Для размеров 300×300 и 500×500 также наблюдаются лишь небольшие колебания времени
|
||||
без устойчивого существенного ускорения.
|
||||
|
||||
## ссылка на видео
|
||||
https://vkvideo.ru/video-234070899_456239022?list=ln-XP9xzFbV2KUljy9ewx
|
||||
Reference in New Issue
Block a user