From 59410050ca8061181f8ec71d8e1e11c0fee88acb Mon Sep 17 00:00:00 2001 From: lomakinae Date: Mon, 25 May 2026 02:41:47 +0300 Subject: [PATCH] feat: add experiment benchmarks and Facade entry point --- lomakinae/docs/data/02/src/__init__.py | 0 lomakinae/docs/data/02/src/experiment.py | 69 ++++++++++++++++++ lomakinae/docs/data/02/src/facade.py | 13 ++++ lomakinae/docs/data/02/src/plots.py | 92 ++++++++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 lomakinae/docs/data/02/src/__init__.py create mode 100644 lomakinae/docs/data/02/src/experiment.py create mode 100644 lomakinae/docs/data/02/src/facade.py create mode 100644 lomakinae/docs/data/02/src/plots.py diff --git a/lomakinae/docs/data/02/src/__init__.py b/lomakinae/docs/data/02/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lomakinae/docs/data/02/src/experiment.py b/lomakinae/docs/data/02/src/experiment.py new file mode 100644 index 0000000..f59c406 --- /dev/null +++ b/lomakinae/docs/data/02/src/experiment.py @@ -0,0 +1,69 @@ +import csv +import sys +from pathlib import Path + +# Adjust sys.path to allow imports from src when run directly +BASE_DIR = Path(__file__).resolve().parent +sys.path.append(str(BASE_DIR.parent)) + +from src.builder import TextFileMazeBuilder +from src.solver import MazeSolver +from src.strategies import AStarStrategy, BFSStrategy, DFSStrategy + +MAZE_DIR = BASE_DIR / "mazes" + + +def run_benchmarks(): + builder = TextFileMazeBuilder() + strategies = [ + ("BFS", BFSStrategy()), + ("DFS", DFSStrategy()), + ("A*", AStarStrategy()), + ] + + results = [] + + for maze_path in sorted(MAZE_DIR.glob("*.txt")): + maze_name = maze_path.stem + try: + maze = builder.build_from_file(maze_path) + except Exception as e: + continue + + solver = MazeSolver(maze) + + for strat_name, strat in strategies: + solver.set_strategy(strat) + + times = [] + stats = None + for _ in range(10): + stats = solver.solve() + times.append(stats.time_ms) + + avg_time = sum(times) / len(times) + + results.append( + { + "maze": maze_name, + "strategy": strat_name, + "time_ms": avg_time, + "visited_cells": stats.visited_cells, + "path_length": stats.path_length, + } + ) + + csv_path = BASE_DIR.parent / "results.csv" + with open(csv_path, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter( + f, + fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"], + ) + writer.writeheader() + writer.writerows(results) + + print(f"Benchmark results stored in {csv_path}") + + +if __name__ == "__main__": + run_benchmarks() diff --git a/lomakinae/docs/data/02/src/facade.py b/lomakinae/docs/data/02/src/facade.py new file mode 100644 index 0000000..826f586 --- /dev/null +++ b/lomakinae/docs/data/02/src/facade.py @@ -0,0 +1,13 @@ +from .experiment import run_benchmarks +from .plots import generate_plots + + +class MazeTestingFacade: + def run_full_diagnostic(self): + print("Запуск экспериментов...") + run_benchmarks() + + print("\nГенерация графиков...") + generate_plots() + + print("\nГотово!") diff --git a/lomakinae/docs/data/02/src/plots.py b/lomakinae/docs/data/02/src/plots.py new file mode 100644 index 0000000..3e26114 --- /dev/null +++ b/lomakinae/docs/data/02/src/plots.py @@ -0,0 +1,92 @@ +import csv +import re +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + +BASE_DIR = Path(__file__).resolve().parent + + +def generate_plots(): + csv_path = BASE_DIR.parent / "results.csv" + if not csv_path.exists(): + print(f"Error: {csv_path} not found. Run experiment.py first.") + return + + results = [] + with open(csv_path, "r", encoding="utf-8") as f: + reader = csv.DictReader(f) + for row in reader: + results.append( + { + "maze": row["maze"], + "strategy": row["strategy"], + "time_ms": float(row["time_ms"]), + "visited_cells": int(row["visited_cells"]), + "path_length": int(row["path_length"]), + } + ) + + # Sort mazes by requested logical order: no_exit, empty, then by size (NxN) + unique_mazes = list(dict.fromkeys(r["maze"] for r in results)) + + def get_sort_key(m_name): + name = m_name.lower() + if "no_exit" in name or "noexit" in name: + return 0 + if "empty" in name: + return 1 + + match = re.search(r"(\d+)x\d+", name) + if match: + return 100 + int(match.group(1)) + + return 999 + + maze_files_keys = sorted(unique_mazes, key=get_sort_key) + + fig, axes = plt.subplots( + len(maze_files_keys), 3, figsize=(18, 3 * len(maze_files_keys)) + ) + + for idx, maze_name in enumerate(maze_files_keys): + maze_res = [r for r in results if r["maze"] == maze_name] + if not maze_res: + continue + + strats = [r["strategy"] for r in maze_res] + times = [r["time_ms"] for r in maze_res] + visited = [r["visited_cells"] for r in maze_res] + path_lens = [r["path_length"] for r in maze_res] + + x = np.arange(len(strats)) + + # Check if axes is 1D or 2D depending on number of mazes + ax_time = axes[0] if len(maze_files_keys) == 1 else axes[idx, 0] + ax_visited = axes[1] if len(maze_files_keys) == 1 else axes[idx, 1] + ax_path = axes[2] if len(maze_files_keys) == 1 else axes[idx, 2] + + ax_time.bar(x, times, color=["red", "green", "blue"]) + ax_time.set_xticks(x) + ax_time.set_xticklabels(strats) + ax_time.set_title(f"{maze_name}: Execution Time (ms)") + + ax_visited.bar(x, visited, color=["red", "green", "blue"]) + ax_visited.set_xticks(x) + ax_visited.set_xticklabels(strats) + ax_visited.set_title(f"{maze_name}: Visited Cells") + + ax_path.bar(x, path_lens, color=["red", "green", "blue"]) + ax_path.set_xticks(x) + ax_path.set_xticklabels(strats) + ax_path.set_title(f"{maze_name}: Path Length") + + plt.tight_layout() + chart_path = BASE_DIR.parent / "benchmark_charts.png" + plt.savefig(chart_path) + print(f"Charts exported to {chart_path}") + + +if __name__ == "__main__": + generate_plots()