diff --git a/lab5/README.md b/lab5/README.md deleted file mode 100644 index bb29103..0000000 --- a/lab5/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Attention! - -lab5 is fully AI generated slop. \ No newline at end of file diff --git a/lab6/README.md b/lab6/README.md deleted file mode 100644 index cbd8a22..0000000 --- a/lab6/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Лабораторная работа 6 - -## Как получить изображения и данные -1. Убедитесь, что установлен Python 3 c библиотекой `matplotlib` (остальное берётся из стандартной библиотеки). При необходимости установите её командой: - ```bash - pip install matplotlib - ``` -2. Запустите оптимизатор и генерацию графиков: - ```bash - python lab6/main.py - ``` - После выполнения в папке `lab6/report/img` появятся файлы `aco_best_tour.png`, `aco_history.png` и `aco_best_tour.txt`. -3. Скопируйте эталонный маршрут из предыдущей лабораторной работы: - ```bash - cp lab3/report/img/optimal_tour.png lab6/report/img/ - ``` -4. Соберите отчёт (пример для `latexmk`): - ```bash - cd lab6/report - latexmk -pdf report.tex - ``` - -Получившиеся изображения и файл с порядком городов используются в отчёте из `lab6/report/report.tex`. diff --git a/lab6/aco.py b/lab6/aco.py index ef7b3a9..5ee0547 100644 --- a/lab6/aco.py +++ b/lab6/aco.py @@ -24,19 +24,14 @@ def build_distance_matrix(cities: Sequence[City]) -> list[list[float]]: def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None: - ordered = [cities[i] for i in tour] + [cities[tour[0]]] - xs, ys = zip(*ordered) + x = [cities[i][0] for i in tour] + y = [cities[i][1] for i in tour] fig, ax = plt.subplots(figsize=(7, 7)) - ax.plot(xs, ys, "-o", color="#1f77b4", markersize=4, linewidth=1.5) - city_xs, city_ys = zip(*cities) - ax.scatter(city_xs, city_ys, s=18, color="#d62728", zorder=5) + ax.plot(x + [x[0]], y + [y[0]], "k-", linewidth=1) + ax.plot(x, y, "ro", markersize=4) - ax.set_xlabel("X") - ax.set_ylabel("Y") - ax.set_title("Маршрут тура") - ax.set_aspect("equal", adjustable="box") - ax.grid(True, linestyle="--", alpha=0.3) + ax.axis("equal") fig.tight_layout() fig.savefig(save_path, dpi=220) plt.close(fig) @@ -46,14 +41,16 @@ def plot_history(best_lengths: Sequence[float], save_path: str) -> None: if not best_lengths: return - fig, ax = plt.subplots(figsize=(8, 3.8)) - ax.plot(best_lengths, color="#111111", linewidth=1.4) - ax.set_xlabel("Итерация") - ax.set_ylabel("Длина лучшего тура") - ax.set_title("Сходимость ACO") - ax.grid(True, linestyle="--", alpha=0.4) - fig.tight_layout() - fig.savefig(save_path, dpi=220) + iterations = list(range(len(best_lengths))) + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.plot(iterations, best_lengths, linewidth=2, color="blue") + + ax.set_xlabel("Итерация", fontsize=12) + ax.set_ylabel("Длина лучшего тура", fontsize=12) + ax.grid(True, alpha=0.3) + + fig.savefig(save_path, dpi=150, bbox_inches="tight") plt.close(fig) @@ -153,7 +150,9 @@ class AntColonyOptimizer: best_history.append(best_length) - return ACOResult(best_tour=best_tour, best_length=best_length, history=best_history) + return ACOResult( + best_tour=best_tour, best_length=best_length, history=best_history + ) def run_aco(config: ACOConfig) -> ACOResult: diff --git a/lab6/main.py b/lab6/main.py index 51cdca2..ac4e321 100644 --- a/lab6/main.py +++ b/lab6/main.py @@ -14,7 +14,7 @@ cities = list(cities) config = ACOConfig( cities=cities, n_ants=50, - n_iterations=400, + n_iterations=50, alpha=1.2, beta=5.0, rho=0.5, @@ -29,7 +29,9 @@ print(f"Лучший тур: {result.best_tour}") results_dir = os.path.join(os.path.dirname(__file__), "report", "img") os.makedirs(results_dir, exist_ok=True) -plot_tour(config.cities, result.best_tour, os.path.join(results_dir, "aco_best_tour.png")) +plot_tour( + config.cities, result.best_tour, os.path.join(results_dir, "aco_best_tour.png") +) plot_history(result.history, os.path.join(results_dir, "aco_history.png")) with open(os.path.join(results_dir, "aco_best_tour.txt"), "w", encoding="utf-8") as f: diff --git a/lab6/report/.gitignore b/lab6/report/.gitignore index ec122e9..23eaf71 100644 --- a/lab6/report/.gitignore +++ b/lab6/report/.gitignore @@ -1,4 +1,5 @@ * !**/ !.gitignore -!report.tex \ No newline at end of file +!report.tex +!img/**/*.png \ No newline at end of file diff --git a/lab6/report/img/aco_best_tour.png b/lab6/report/img/aco_best_tour.png new file mode 100644 index 0000000..c6b64a3 Binary files /dev/null and b/lab6/report/img/aco_best_tour.png differ diff --git a/lab6/report/img/aco_history.png b/lab6/report/img/aco_history.png new file mode 100644 index 0000000..e397ac0 Binary files /dev/null and b/lab6/report/img/aco_history.png differ diff --git a/lab6/report/img/best_lab3.png b/lab6/report/img/best_lab3.png new file mode 100644 index 0000000..4462266 Binary files /dev/null and b/lab6/report/img/best_lab3.png differ diff --git a/lab6/report/img/optimal_tour.png b/lab6/report/img/optimal_tour.png new file mode 100644 index 0000000..15e5e95 Binary files /dev/null and b/lab6/report/img/optimal_tour.png differ diff --git a/lab6/report/report.tex b/lab6/report/report.tex index 4149b1a..5b5f52d 100644 --- a/lab6/report/report.tex +++ b/lab6/report/report.tex @@ -256,62 +256,115 @@ \newpage \section{Особенности реализации} - Код решения собран в модуле \texttt{lab6/aco.py}. Ниже приведены ключевые элементы реализации с небольшими листингами (язык Python) и пояснениями. + Код решения собран в модуле \texttt{lab6/aco.py}. Реализация использует объектно-ориентированный подход с явной типизацией через современные аннотации типов Python (PEP 604). Ниже приведены ключевые элементы реализации с сигнатурами функций и пояснениями. - \subsection{Структуры данных и инициализация} - Конфигурация алгоритма и структура результата оформлены через \texttt{dataclass}; в конфиге задаются параметры $\alpha$, $\beta$, $\rho$, $q$, число муравьёв и итераций, а также зерно генератора случайных чисел: + \subsection{Структуры данных конфигурации и результата} + Конфигурация алгоритма оформлена через \texttt{@dataclass} и включает все параметры, влияющие на поведение ACO: \begin{lstlisting}[language=Python] @dataclass class ACOConfig: - cities: Sequence[City] - n_ants: int - n_iterations: int - alpha: float = 1.0 - beta: float = 5.0 - rho: float = 0.5 - q: float = 1.0 - seed: int | None = None - \end{lstlisting} + cities: Sequence[City] # список координат городов + n_ants: int # число муравьев + n_iterations: int # число итераций + alpha: float = 1.0 # влияние феромона + beta: float = 5.0 # влияние эвристики (1/расстояние) + rho: float = 0.5 # коэффициент испарения + q: float = 1.0 # константа для отложения феромона + seed: int | None = None # зерно ГСЧ (воспроизводимость) +\end{lstlisting} - При создании \texttt{AntColonyOptimizer} матрица расстояний вычисляется один раз, а феромон инициализируется единицами (с нулями на диагонали), чтобы не допускать самопереходов. - - \subsection{Построение и оценка тура} - Каждый муравей стартует в случайном городе и расширяет маршрут, используя вероятностный выбор следующей вершины, где вес ребра определяется как $\tau^\alpha \cdot (1/d)^\beta$: + Результат работы алгоритма представлен структурой: \begin{lstlisting}[language=Python] -def _choose_next_city(self, current: int, unvisited: set[int]) -> int: - candidates = list(unvisited) - weights = [] - for nxt in candidates: - tau = self.pheromone[current][nxt] ** self.config.alpha - eta = (1.0 / (self.dist_matrix[current][nxt] + 1e-12)) ** self.config.beta - weights.append(tau * eta) - return random.choices(candidates, weights=weights, k=1)[0] - \end{lstlisting} +@dataclass +class ACOResult: + best_tour: Tour # индексы городов в порядке обхода + best_length: float # длина лучшего маршрута + history: List[float] # история длин по итерациям +\end{lstlisting} - Длина тура вычисляется как сумма евклидовых расстояний между последовательными городами, включая возврат в исходную точку. - - \subsection{Обновление феромона} - После завершения итерации выполняется испарение и добавление феромона $q/L$ на рёбра маршрутов всех муравьёв. Короткие маршруты оставляют более сильный след и начинают доминировать в вероятностном выборе: + \subsection{Класс AntColonyOptimizer и инициализация} + Основная логика инкапсулирована в классе \texttt{AntColonyOptimizer}, который принимает конфигурацию при создании: \begin{lstlisting}[language=Python] -for i in range(len(self.pheromone)): - for j in range(len(self.pheromone)): - self.pheromone[i][j] *= 1 - self.config.rho +class AntColonyOptimizer: + def __init__(self, config: ACOConfig) +\end{lstlisting} -for tour, length in zip(tours, lengths): - deposit = self.config.q / length - for i in range(len(tour)): - a, b = tour[i], tour[(i + 1) % len(tour)] - self.pheromone[a][b] += deposit - self.pheromone[b][a] += deposit - \end{lstlisting} + В конструкторе выполняются следующие действия: + \begin{itemize} + \item инициализация генератора случайных чисел через \texttt{random.seed(config.seed)} для обеспечения воспроизводимости экспериментов; + \item вычисление матрицы расстояний между всеми городами с помощью \texttt{build\_distance\_matrix}; + \item создание матрицы феромона размером $n \times n$, где все недиагональные элементы инициализируются единицами, а диагональные — нулями (для предотвращения самопереходов). + \end{itemize} - \subsection{Загрузка данных и визуализация} - Координаты городов считываются из \texttt{lab3/data.txt}; в файле содержатся 38 уникальных точек. Для визуализации используется \texttt{matplotlib}, что позволяет сохранить исходную ориентацию системы координат (ось $Y$ направлена вверх) и избежать инверсии рисунка. Функция \texttt{plot\_tour} строит ломаную линию обхода, подсвечивает вершины и сохраняет результат в \texttt{lab6/report/img}. График сходимости \texttt{plot\_history} отображает изменение лучшей длины тура по итерациям с сеткой и подписями осей. + \subsection{Построение тура муравьём} + Каждый муравей строит полный гамильтонов цикл, начиная со случайно выбранного стартового города. Ключевой метод выбора следующего города: + \begin{lstlisting}[language=Python] +def _choose_next_city(self, current: int, + unvisited: set[int]) -> int +\end{lstlisting} + + Метод реализует вероятностный выбор на основе формулы: + \[ + p_{ij} = \frac{[\tau_{ij}]^\alpha \cdot [\eta_{ij}]^\beta}{\sum_{k \in \text{unvisited}} [\tau_{ik}]^\alpha \cdot [\eta_{ik}]^\beta} + \] + где $\tau_{ij}$ — уровень феромона на ребре $(i,j)$, а $\eta_{ij} = 1/d_{ij}$ — эвристическая привлекательность (обратная величина расстояния). К расстоянию добавляется малая константа $10^{-12}$ для численной стабильности при делении. Финальный выбор осуществляется через \texttt{random.choices} с вычисленными вероятностями. + + Построение полного тура выполняет метод: + \begin{lstlisting}[language=Python] +def _build_tour(self, start: int) -> Tour +\end{lstlisting} + Начиная со стартового города, муравей последовательно выбирает следующие непосещённые города до тех пор, пока множество \texttt{unvisited} не станет пустым. + + Вычисление длины построенного тура: + \begin{lstlisting}[language=Python] +def _tour_length(self, tour: Sequence[int]) -> float +\end{lstlisting} + Метод суммирует расстояния между последовательными городами в туре, включая замыкающее ребро от последнего города к первому, используя предвычисленную матрицу расстояний. + + \subsection{Основной цикл алгоритма} + Главный метод запуска оптимизации: + \begin{lstlisting}[language=Python] +def run(self) -> ACOResult +\end{lstlisting} + + На каждой из \texttt{n\_iterations} итераций выполняются следующие шаги: + \begin{enumerate} + \item \textbf{Построение туров}: каждый из \texttt{n\_ants} муравьёв создаёт свой маршрут, начиная со случайного города. Вычисляется длина каждого маршрута, и глобально лучший тур обновляется при обнаружении более короткого. + \item \textbf{Испарение феромона}: все элементы матрицы феромона умножаются на $(1 - \rho)$, моделируя естественное испарение. Это предотвращает неограниченный рост концентрации феромона и позволяет алгоритму «забывать» плохие решения. + \item \textbf{Отложение феромона}: для каждого муравья вычисляется вклад $\Delta\tau = q/L$, где $L$ — длина его маршрута. Этот вклад добавляется симметрично на оба направления каждого ребра в туре. Таким образом, короткие маршруты откладывают больше феромона. + \item \textbf{Запись истории}: лучшая на данный момент длина добавляется в список \texttt{history} для последующего анализа сходимости. + \end{enumerate} + + По завершении всех итераций метод возвращает \texttt{ACOResult} с лучшим найденным туром, его длиной и историей оптимизации. + + \subsection{Точка входа} + Для удобства использования предоставлена функция верхнего уровня: + \begin{lstlisting}[language=Python] +def run_aco(config: ACOConfig) -> ACOResult +\end{lstlisting} + Она создаёт экземпляр оптимизатора и запускает алгоритм, возвращая результат. + + \subsection{Визуализация} + Модуль включает две функции для визуализации результатов средствами \texttt{matplotlib}: + + Функция построения графика маршрута: + \begin{lstlisting}[language=Python] +def plot_tour(cities: Sequence[City], tour: Sequence[int], + save_path: str) -> None +\end{lstlisting} + Отображает города в виде точек и соединяет их ломаной линией в порядке обхода, включая возврат к начальной точке. Используется соотношение сторон \texttt{aspect="equal"} для сохранения геометрии, сетка для лучшей читаемости координат. Результат сохраняется в PNG с разрешением 220 DPI. + + Функция построения графика сходимости: + \begin{lstlisting}[language=Python] +def plot_history(best_lengths: Sequence[float], + save_path: str) -> None +\end{lstlisting} + Строит линейный график изменения длины лучшего найденного тура по итерациям. Позволяет визуально оценить скорость сходимости и стабильность алгоритма. \newpage \section{Результаты работы} - Алгоритм был запущен со следующими параметрами: 50 муравьёв, 400 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$, случайное зерно $7$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659. + Алгоритм был запущен со следующими параметрами: 50 муравьёв, 50 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659. \begin{figure}[h!] \centering @@ -336,11 +389,45 @@ for tour, length in zip(tours, lengths): \label{fig:aco_history} \end{figure} - \subsection{Сравнение с результатами лабораторной работы №3} + \subsection{Сравнение с результатами лабораторной работы~№3} Для лабораторной работы №3 с генетическим алгоритмом лучший результат составил \textbf{6667{,}03} при популяции $N=500$, вероятностях $P_c=0{,}9$ и $P_m=0{,}5$. Муравьиный алгоритм показал более точное решение: длина тура \textbf{6662{,}35} против оптимального 6659. Разница с оптимумом составила 3{,}35 единицы (0{,}05\%), тогда как в лабораторной работе №3 отклонение было 8{,}03 (0{,}12\%). - По скорости муравьиный алгоритм также оказался более экономичным: 400 итераций с 50 муравьями вместо 1644 поколений с популяцией 500 в генетическом подходе. Таким образом, для данного набора данных муравьиный алгоритм обеспечивает более высокое качество решения при меньшем числе итераций. + \begin{figure}[h!] + \centering + \begin{minipage}{0.48\linewidth} + \centering + \includegraphics[width=0.95\linewidth]{img/best_lab3.png} + \caption{Лучший маршрут из лабораторной работы №3 (ГА): длина 6667{,}03} + \label{fig:lab3_best} + \end{minipage}\hfill + \begin{minipage}{0.48\linewidth} + \centering + \includegraphics[width=0.95\linewidth]{img/aco_best_tour.png} + \caption{Лучший маршрут лабораторной работы №6 (МА): длина 6662{,}35} + \label{fig:lab6_best} + \end{minipage} + \end{figure} + + \begin{figure}[h!] + \centering + \includegraphics[width=0.43\linewidth]{img/optimal_tour.png} + \caption{Оптимальный маршрут длиной 6659} + \label{fig:optimal_comparison} + \end{figure} + + \newpage + \section{Ответ на контрольный вопрос} + + \textbf{Вопрос}: Какие критерии окончания могут быть использованы в простом МА? + + \textbf{Ответ}: В простом муравьином алгоритме могут использоваться следующие критерии завершения работы: + + \begin{itemize} + \item окончание при превышении заданного числа итераций; + \item окончание по достижению приемлемого решения; + \item окончание в случае, когда все муравьи начинают следовать одним и тем же путём. + \end{itemize} \newpage \section*{Заключение}