From 4ce7536bbf77f9920044d7853f9cfcdf3ed5bba6 Mon Sep 17 00:00:00 2001 From: pogodinda Date: Sun, 24 May 2026 11:30:30 +0300 Subject: [PATCH] =?UTF-8?q?[2]=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=81=D1=80=D0=B0=D0=B2=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pogodinda/lab2/src/experiments.py | 240 ++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 pogodinda/lab2/src/experiments.py diff --git a/pogodinda/lab2/src/experiments.py b/pogodinda/lab2/src/experiments.py new file mode 100644 index 0000000..a63d902 --- /dev/null +++ b/pogodinda/lab2/src/experiments.py @@ -0,0 +1,240 @@ +import os +import random +import time +import csv +from typing import Dict, List + +import matplotlib.pyplot as plt +import numpy as np + +from maze import Maze, Cell +from maze_builder import RandomMazeBuilder +from pathfinding import BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy +from maze_solver import MazeSolver, SearchStats + + +def create_test_mazes() -> Dict[str, Maze]: + """Создаёт тестовые лабиринты разной сложности.""" + mazes = {} + + # 1. Маленький 10×10 с простым путём + small = Maze(10, 10) + for y in range(10): + for x in range(10): + is_wall = (x == 0 or x == 9 or y == 0 or y == 9 or + (x == 3 and y < 7) or (x == 6 and y > 2)) + is_start = (x == 1 and y == 1) + is_exit = (x == 8 and y == 8) + small.set_cell(x, y, Cell(x, y, is_wall=is_wall, + is_start=is_start, is_exit=is_exit)) + mazes['small_10x10'] = small + + # 2. Средний 50×50 — случайный + mazes['medium_50x50'] = RandomMazeBuilder(51, 51).build_from_file() + + # 3. Большой 100×100 — случайный + mazes['large_100x100'] = RandomMazeBuilder(101, 101).build_from_file() + + # 4. Пустой 20×20 — без стен + empty = Maze(20, 20) + for y in range(20): + for x in range(20): + empty.set_cell(x, y, Cell(x, y, is_wall=False, + is_start=(x==1 and y==1), + is_exit=(x==18 and y==18))) + mazes['empty_20x20'] = empty + + # 5. Без выхода — проверка обработки + no_exit = Maze(10, 10) + for y in range(10): + for x in range(10): + is_wall = (x == 0 or x == 9 or y == 0 or y == 9 or x == 5) + no_exit.set_cell(x, y, Cell(x, y, is_wall=is_wall, + is_start=(x==1 and y==1))) + mazes['no_exit_10x10'] = no_exit + + # 6. Взвешенный 15×15 + weighted = Maze(15, 15) + for y in range(15): + for x in range(15): + is_wall = (x == 0 or x == 14 or y == 0 or y == 14 or + (x == 5 and y != 7) or (x == 10 and y != 3)) + weight = 1 + if 3 <= x <= 7 and 3 <= y <= 7: + weight = 2 # песок + elif 8 <= x <= 12 and 8 <= y <= 12: + weight = 3 # болото + + weighted.set_cell(x, y, Cell(x, y, + is_wall=is_wall, + is_start=(x==1 and y==1), + is_exit=(x==13 and y==13), + weight=weight)) + mazes['weighted_15x15'] = weighted + + return mazes + + +def run_experiments(mazes: Dict[str, Maze], + strategies: List, + runs_per_test: int = 5) -> List[SearchStats]: + """Запускает эксперименты, возвращает список статистик.""" + results = [] + + for maze_name, maze in mazes.items(): + print(f"\n{'='*60}") + print(f"Лабиринт: {maze_name} ({maze.width}x{maze.height})") + print(f"{'='*60}") + + for strategy in strategies: + print(f"\nАлгоритм: {strategy.get_name()}") + + times = [] + visited_list = [] + path_lens = [] + + solver = MazeSolver(maze, strategy) + + for run in range(runs_per_test): + stats = solver.solve(maze_name) + times.append(stats.time_ms) + visited_list.append(stats.visited_cells) + path_lens.append(stats.path_length) + + print(f" Запуск {run+1}: {stats.time_ms:.4f} мс, " + f"посещено: {stats.visited_cells}, " + f"длина: {stats.path_length}") + + # Средние значения + avg = SearchStats( + time_ms=np.mean(times), + visited_cells=int(np.mean(visited_list)), + path_length=int(np.mean(path_lens)), + algorithm_name=strategy.get_name(), + maze_name=maze_name + ) + results.append(avg) + + print(f" СРЕДНЕЕ: {avg.time_ms:.4f} мс, " + f"посещено: {avg.visited_cells}, " + f"длина: {avg.path_length}") + + return results + + +def save_csv(results: List[SearchStats], filename: str): + """Сохраняет результаты в CSV.""" + with open(filename, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(['Лабиринт', 'Алгоритм', 'Время_мс', + 'Посещено_клеток', 'Длина_пути']) + for r in results: + writer.writerow([ + r.maze_name, r.algorithm_name, + f"{r.time_ms:.6f}", r.visited_cells, r.path_length + ]) + print(f"\nCSV сохранён: {filename}") + + +def create_plots(results: List[SearchStats], output_dir: str = "."): + """Создаёт графики сравнения.""" + os.makedirs(output_dir, exist_ok=True) + + maze_names = sorted(set(r.maze_name for r in results)) + algorithms = sorted(set(r.algorithm_name for r in results)) + + # 1. Общее сравнение по лабиринтам + fig, axes = plt.subplots(2, 3, figsize=(18, 12)) + fig.suptitle('Сравнение алгоритмов поиска пути', fontsize=16) + + for idx, maze_name in enumerate(maze_names): + ax = axes[idx // 3, idx % 3] + maze_res = [r for r in results if r.maze_name == maze_name] + + names = [r.algorithm_name for r in maze_res] + times = [r.time_ms for r in maze_res] + visited = [r.visited_cells for r in maze_res] + paths = [r.path_length for r in maze_res] + + x = np.arange(len(names)) + w = 0.25 + + ax.bar(x - w, times, w, label='Время (мс)', color='skyblue') + ax.bar(x, [v/10 for v in visited], w, + label='Посещено (÷10)', color='lightcoral') + ax.bar(x + w, paths, w, label='Длина пути', color='lightgreen') + + ax.set_title(maze_name) + ax.set_xticks(x) + ax.set_xticklabels(names, rotation=45) + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig(f'{output_dir}/maze_comparison.png', dpi=150) + plt.close() + + # 2. Посещённые клетки + fig, ax = plt.subplots(figsize=(14, 8)) + for algo in algorithms: + algo_res = [r for r in results if r.algorithm_name == algo] + names = [r.maze_name for r in algo_res] + vals = [r.visited_cells for r in algo_res] + ax.plot(names, vals, marker='o', label=algo, linewidth=2) + + ax.set_title('Посещённые клетки по лабиринтам', fontsize=14) + ax.set_xlabel('Лабиринт') + ax.set_ylabel('Клетки') + ax.legend() + ax.grid(True, alpha=0.3) + plt.xticks(rotation=45) + plt.tight_layout() + plt.savefig(f'{output_dir}/visited_cells.png', dpi=150) + plt.close() + + # 3. Время выполнения (логарифмическая шкала) + fig, ax = plt.subplots(figsize=(14, 8)) + for algo in algorithms: + algo_res = [r for r in results if r.algorithm_name == algo] + names = [r.maze_name for r in algo_res] + vals = [max(r.time_ms, 0.001) for r in algo_res] + ax.plot(names, vals, marker='s', label=algo, linewidth=2) + + ax.set_title('Время выполнения (лог. шкала)', fontsize=14) + ax.set_xlabel('Лабиринт') + ax.set_ylabel('Время (мс)') + ax.set_yscale('log') + ax.legend() + ax.grid(True, alpha=0.3, which='both') + plt.xticks(rotation=45) + plt.tight_layout() + plt.savefig(f'{output_dir}/time_comparison.png', dpi=150) + plt.close() + + print(f"Графики сохранены в {output_dir}/") + + +if __name__ == "__main__": + print("=" * 70) + print("ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ") + print("=" * 70) + + # Создаём лабиринты + mazes = create_test_mazes() + + # Алгоритмы для сравнения + strategies = [BFSStrategy(), DFSStrategy(), + AStarStrategy(), DijkstraStrategy()] + + # Запускаем эксперименты + results = run_experiments(mazes, strategies, runs_per_test=5) + + # Сохраняем CSV + save_csv(results, "experiment_results.csv") + + # Строим графики + create_plots(results) + + print("\n" + "=" * 70) + print("ГОТОВО!") + print("=" * 70) \ No newline at end of file