5 Commits

Author SHA1 Message Date
b4fcf6562e lab6 2025-11-21 20:35:50 +03:00
Artem
93ab829cff Use matplotlib for lab6 visuals and expand report 2025-11-21 17:29:02 +03:00
Artem
9f591dadda Refine lab6 assets and report comparison 2025-11-21 17:00:45 +03:00
7394e5b9fb Данные по коммивояжёру 2025-11-21 16:29:45 +03:00
1f80f2f7dc Заготовка lab6 2025-11-21 16:24:30 +03:00
11 changed files with 752 additions and 0 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,8 @@
!*.gitignore
!*.py
!.gitkeep
!lab3/data.txt
!lab4/*
!lab5/report/report.tex
!lab5/README.md
!lab6/README.md

89
lab3/data.txt Normal file
View File

@@ -0,0 +1,89 @@
1 11511.3889 42106.3889
2 11503.0556 42855.2778
3 11438.3333 42057.2222
4 11438.3333 42057.2222
5 11438.3333 42057.2222
6 11785.2778 42884.4444
7 11785.2778 42884.4444
8 11785.2778 42884.4444
9 11785.2778 42884.4444
10 12363.3333 43189.1667
11 11846.9444 42660.5556
12 11503.0556 42855.2778
13 11963.0556 43290.5556
14 11963.0556 43290.5556
15 12300.0000 42433.3333
16 11973.0556 43026.1111
17 11973.0556 43026.1111
18 11461.1111 43252.7778
19 11461.1111 43252.7778
20 11461.1111 43252.7778
21 11461.1111 43252.7778
22 11600.0000 43150.0000
23 12386.6667 43334.7222
24 12386.6667 43334.7222
25 11595.0000 43148.0556
26 11595.0000 43148.0556
27 11569.4444 43136.6667
28 11310.2778 42929.4444
29 11310.2778 42929.4444
30 11310.2778 42929.4444
31 11963.0556 43290.5556
32 11416.6667 42983.3333
33 11416.6667 42983.3333
34 11595.0000 43148.0556
35 12149.4444 42477.5000
36 11595.0000 43148.0556
37 11595.0000 43148.0556
38 11108.6111 42373.8889
39 11108.6111 42373.8889
40 11108.6111 42373.8889
41 11108.6111 42373.8889
42 11183.3333 42933.3333
43 12372.7778 42711.3889
44 11583.3333 43150.0000
45 11583.3333 43150.0000
46 11583.3333 43150.0000
47 11583.3333 43150.0000
48 11583.3333 43150.0000
49 11822.7778 42673.6111
50 11822.7778 42673.6111
51 12058.3333 42195.5556
52 11003.6111 42102.5000
53 11003.6111 42102.5000
54 11003.6111 42102.5000
55 11522.2222 42841.9444
56 12386.6667 43334.7222
57 12386.6667 43334.7222
58 12386.6667 43334.7222
59 11569.4444 43136.6667
60 11569.4444 43136.6667
61 11569.4444 43136.6667
62 11155.8333 42712.5000
63 11155.8333 42712.5000
64 11155.8333 42712.5000
65 11155.8333 42712.5000
66 11133.3333 42885.8333
67 11133.3333 42885.8333
68 11133.3333 42885.8333
69 11133.3333 42885.8333
70 11133.3333 42885.8333
71 11003.6111 42102.5000
72 11770.2778 42651.9444
73 11133.3333 42885.8333
74 11690.5556 42686.6667
75 11690.5556 42686.6667
76 11751.1111 42814.4444
77 12645.0000 42973.3333
78 12421.6667 42895.5556
79 12421.6667 42895.5556
80 11485.5556 43187.2222
81 11423.8889 43000.2778
82 11423.8889 43000.2778
83 11715.8333 41836.1111
84 11297.5000 42853.3333
85 11297.5000 42853.3333
86 11583.3333 43150.0000
87 11569.4444 43136.6667
88 12286.9444 43355.5556
89 12355.8333 43156.3889

3
lab6/README.md Normal file
View File

@@ -0,0 +1,3 @@
# Attention!
lab6 is fully AI generated slop.

160
lab6/aco.py Normal file
View File

@@ -0,0 +1,160 @@
import math
import random
from dataclasses import dataclass
from typing import List, Sequence, Tuple
import matplotlib.pyplot as plt
City = Tuple[float, float]
Tour = List[int]
def euclidean_distance(c1: City, c2: City) -> float:
return math.hypot(c1[0] - c2[0], c1[1] - c2[1])
def build_distance_matrix(cities: Sequence[City]) -> list[list[float]]:
size = len(cities)
matrix = [[0.0 for _ in range(size)] for _ in range(size)]
for i in range(size):
for j in range(i + 1, size):
dist = euclidean_distance(cities[i], cities[j])
matrix[i][j] = matrix[j][i] = dist
return matrix
def plot_tour(cities: Sequence[City], tour: Sequence[int], save_path: str) -> None:
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(x + [x[0]], y + [y[0]], "k-", linewidth=1)
ax.plot(x, y, "ro", markersize=4)
ax.axis("equal")
fig.tight_layout()
fig.savefig(save_path, dpi=220)
plt.close(fig)
def plot_history(best_lengths: Sequence[float], save_path: str) -> None:
if not best_lengths:
return
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)
@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
@dataclass
class ACOResult:
best_tour: Tour
best_length: float
history: List[float]
class AntColonyOptimizer:
def __init__(self, config: ACOConfig):
self.config = config
if config.seed is not None:
random.seed(config.seed)
self.cities = config.cities
self.dist_matrix = build_distance_matrix(config.cities)
n = len(config.cities)
self.pheromone = [[1.0 if i != j else 0.0 for j in range(n)] for i in range(n)]
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)
total = sum(weights)
probs = [w / total for w in weights]
return random.choices(candidates, weights=probs, k=1)[0]
def _build_tour(self, start: int) -> Tour:
n = len(self.cities)
tour = [start]
unvisited = set(range(n))
unvisited.remove(start)
current = start
while unvisited:
nxt = self._choose_next_city(current, unvisited)
tour.append(nxt)
unvisited.remove(nxt)
current = nxt
return tour
def _tour_length(self, tour: Sequence[int]) -> float:
return sum(
self.dist_matrix[tour[i]][tour[(i + 1) % len(tour)]]
for i in range(len(tour))
)
def run(self) -> ACOResult:
best_tour: Tour = []
best_length = float("inf")
best_history: list[float] = []
for _ in range(self.config.n_iterations):
tours: list[Tour] = []
lengths: list[float] = []
for _ in range(self.config.n_ants):
start_city = random.randrange(len(self.cities))
tour = self._build_tour(start_city)
length = self._tour_length(tour)
tours.append(tour)
lengths.append(length)
if length < best_length:
best_length = length
best_tour = tour
for i in range(len(self.pheromone)):
for j in range(len(self.pheromone)):
self.pheromone[i][j] *= 1 - self.config.rho
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
best_history.append(best_length)
return ACOResult(
best_tour=best_tour, best_length=best_length, history=best_history
)
def run_aco(config: ACOConfig) -> ACOResult:
optimizer = AntColonyOptimizer(config)
return optimizer.run()

38
lab6/main.py Normal file
View File

@@ -0,0 +1,38 @@
import os
from aco import ACOConfig, plot_history, plot_tour, run_aco
# В списке из 89 городов только 38 уникальных
cities = set()
with open(os.path.join(os.path.dirname(__file__), "../lab3/data.txt"), "r") as file:
for line in file:
# x и y поменяны местами в визуализациях в методичке
_, y, x = line.split()
cities.add((float(x), float(y)))
cities = list(cities)
config = ACOConfig(
cities=cities,
n_ants=50,
n_iterations=50,
alpha=1.2,
beta=5.0,
rho=0.5,
q=1.0,
seed=7,
)
result = run_aco(config)
print(f"Лучшая длина: {result.best_length:.2f}")
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_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:
f.write(" ".join(map(str, result.best_tour)))

5
lab6/report/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*
!**/
!.gitignore
!report.tex
!img/**/*.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

455
lab6/report/report.tex Normal file
View File

@@ -0,0 +1,455 @@
\documentclass[a4paper, final]{article}
%\usepackage{literat} % Нормальные шрифты
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
\usepackage{tabularx}
\usepackage{booktabs}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{amsmath}
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
\usepackage{ragged2e} %для растягивания по ширине
\usepackage{setspace} %для межстрочно го интервала
\usepackage{moreverb} %для работы с листингами
\usepackage{indentfirst} % для абзацного отступа
\usepackage{moreverb} %для печати в листинге исходного кода программ
\usepackage{pdfpages} %для вставки других pdf файлов
\usepackage{tikz}
\usepackage{graphicx}
\usepackage{afterpage}
\usepackage{longtable}
\usepackage{float}
\usepackage{xcolor}
% \usepackage[paper=A4,DIV=12]{typearea}
\usepackage{pdflscape}
% \usepackage{lscape}
\usepackage{array}
\usepackage{multirow}
\renewcommand\verbatimtabsize{4\relax}
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
\usepackage{listings} %листинги
\usepackage{xcolor} % цвета
\usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
\hypersetup{colorlinks,
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
\lstloadlanguages{ SQL}
% включаем кириллицу и добавляем кое−какие опции
\lstset{tabsize=2,
breaklines,
basicstyle=\footnotesize,
columns=fullflexible,
flexiblecolumns,
numbers=left,
numberstyle={\footnotesize},
keywordstyle=\color{blue},
inputencoding=cp1251,
extendedchars=true
}
\lstdefinelanguage{MyC}{
language=SQL,
% ndkeywordstyle=\color{darkgray}\bfseries,
% identifierstyle=\color{black},
% morecomment=[n]{/**}{*/},
% commentstyle=\color{blue}\ttfamily,
% stringstyle=\color{red}\ttfamily,
% morestring=[b]",
% showstringspaces=false,
% morecomment=[l][\color{gray}]{//},
keepspaces=true,
escapechar=\%,
texcl=true
}
\textheight=24cm % высота текста
\textwidth=16cm % ширина текста
\oddsidemargin=0pt % отступ от левого края
\topmargin=-1.5cm % отступ от верхнего края
\parindent=24pt % абзацный отступ
\parskip=5pt % интервал между абзацами
\tolerance=2000 % терпимость к "жидким" строкам
\flushbottom % выравнивание высоты страниц
% Настройка листингов
\lstset{
language=python,
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
% captionpos=b, % подписи листингов снизу
}
\begin{document} % начало документа
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
\begin{center}
\hfill \break
\hfill \break
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
\hfill \break
\hfill \break
\hfill \break
\hfill \break
\large{Лабораторная работа №6}\\
\large{по дисциплине}\\
\large{<<Генетические алгоритмы>>}\\
\large{Вариант 18}\\
% \hfill \break
\hfill \break
\end{center}
\small{
\begin{tabular}{lrrl}
\!\!\!Студент, & \hspace{2cm} & & \\
\!\!\!группы 5130201/20101 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Большаков А. А. \\\\
&&\hspace{4cm}
\end{tabular}
\begin{flushright}
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г.
\end{flushright}
}
\hfill \break
% \hfill \break
\begin{center} \small{Санкт-Петербург, 2025} \end{center}
\thispagestyle{empty} % выключаем отображение номера для этой страницы
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
\newpage
\tableofcontents
\newpage
\section {Постановка задачи}
В данной работе были поставлены следующие задачи:
\begin{itemize}
\item Реализовать с использованием муравьиных алгоритмов решение задачи коммивояжера по индивидуальному заданию согласно номеру варианта.
\item Представить графически найденное решение
\item Сравнить найденное решение с представленным в условии задачи оптимальным решением и результатами, полученными в лабораторной работе №3.
\end{itemize}
\textbf{Индивидуальное задание вариант 18:}
\textbf{Дано:} Эвклидовы координаты городов 38 городов в Джибути (см.~Приложение~А). Оптимальный тур представлен на Рис.~\ref{fig:optimal_tour}, его длина равна 6659.
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/optimal_tour.png}
\caption{Оптимальный тур для заданного набора данных}
\label{fig:optimal_tour}
\end{figure}
\newpage
\section{Теоретические сведения}
\subsection{Общие сведения о муравьиных алгоритмах}
Муравьиные алгоритмы (МА) относятся к метаэвристическим методам оптимизации и предназначены преимущественно для решения задач комбинаторной оптимизации, в частности задачи поиска оптимальных путей на графах. Основная идея таких алгоритмов основана на моделировании коллективного поведения реальных муравьёв, использующих феромонные следы для обмена информацией.
Каждый агент, называемый \textit{искусственным муравьём}, поэтапно строит решение задачи, перемещаясь по графу и выбирая следующую вершину на основе вероятностного правила, учитывающего концентрацию феромона на дугах графа. Феромон отражает привлекательность соответствующих маршрутов: чем выше его концентрация на дуге, тем вероятнее выбор этой дуги муравьём.
\subsection{Простой муравьиный алгоритм (SACO)}
Для иллюстрации рассмотрим простой муравьиный алгоритм SACO (Simple Ant Colony Optimization). Пусть задан граф
\[
G = (V, E),
\]
где $V$ — множество вершин, $E$ — множество рёбер. Каждой дуге $(i,j)$ сопоставлена величина феромона $\tau_{ij}$.
В начальный момент концентрация феромона обычно принимается нулевой, однако для предотвращения зацикливания каждому ребру присваивается малое случайное начальное значение $\tau_{ij}^{(0)}$.
Каждый муравей $k=1,\ldots,n_k$ помещается в стартовую вершину и начинает построение пути. Если муравей находится в вершине $i$, он выбирает следующую вершину $j \in N_i^k$ на основе вероятностного правила
\[
p_{ij}^k(t) = \frac{\tau_{ij}^\alpha(t)}{\sum\limits_{l \in N_i^k} \tau_{il}^\alpha(t)},
\]
где $\alpha$ — параметр, определяющий степень влияния феромона.
При отсутствии допустимых переходов допускается возврат в предыдущую вершину, что приводит к появлению петель, которые впоследствии удаляются.
После завершения построения полного пути $x_k(t)$ выполняется его оценка. Длина пути обозначается как $L_k(t)$ и равна числу пройденных дуг.
\subsection{Обновление феромона}
Каждый муравей откладывает феромон на рёбрах своего пути согласно правилу
\[
\Delta \tau_{ij}^k(t) =
\begin{cases}
\frac{1}{L_k(t)}, &\text{если дуга } (i,j) \in x_k(t), \\
0, &\text{иначе}.
\end{cases}
\]
Общее обновление феромона на дуге $(i,j)$:
\[
\tau_{ij}(t+1) = \tau_{ij}(t) + \sum_{k=1}^{n_k} \Delta\tau_{ij}^k(t).
\]
Чем короче путь, тем больше феромона откладывается на его рёбрах, что повышает вероятность выбора коротких маршрутов в последующих итерациях.
\subsection{Испарение феромона}
Чтобы предотвратить преждевременную сходимость алгоритма к локальным минимумам, применяется механизм \textit{искусственного испарения феромона}. На каждом шаге выполняется:
\[
\tau_{ij}(t) = (1 - \rho)\,\tau_{ij}(t),
\]
где $\rho \in [0,1]$ — коэффициент испарения. Большие значения $\rho$ усиливают случайность поиска, малые — повышают устойчивость к изменениям.
\subsection{Критерии остановки алгоритма}
Муравьиные алгоритмы могут завершаться при выполнении одного из условий:
\begin{itemize}
\item достигнуто максимальное число итераций;
\item найдено решение приемлемого качества $f(x_k(t)) \leq \varepsilon$;
\item все муравьи начинают строить одинаковые маршруты, что говорит о стабилизации процесса.
\end{itemize}
\subsection{Описание общего алгоритма}
Алгоритм SACO можно представить в следующем виде:
\begin{enumerate}
\item Инициализация феромона малыми случайными значениями $\tau_{ij}^{(0)}$.
\item Размещение всех муравьёв в начальной вершине.
\item Для каждой итерации:
\begin{enumerate}
\item Каждый муравей строит путь согласно вероятностному правилу выбора вершины.
\item Выполняется удаление петель.
\item Вычисляется длина пути $L_k(t)$.
\end{enumerate}
\item Выполняется испарение феромона.
\item Каждый муравей откладывает феромон на рёбрах своего пути.
\item Итерация продолжается до выполнения критерия остановки.
\end{enumerate}
Муравьиные алгоритмы позволяют эффективно находить приближённые решения задач комбинаторной оптимизации, таких как задача коммивояжёра, что и является целью данной лабораторной работы.
\newpage
\section{Особенности реализации}
Код решения собран в модуле \texttt{lab6/aco.py}. Реализация использует объектно-ориентированный подход с явной типизацией через современные аннотации типов Python (PEP 604). Ниже приведены ключевые элементы реализации с сигнатурами функций и пояснениями.
\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 # влияние эвристики (1/расстояние)
rho: float = 0.5 # коэффициент испарения
q: float = 1.0 # константа для отложения феромона
seed: int | None = None # зерно ГСЧ (воспроизводимость)
\end{lstlisting}
Результат работы алгоритма представлен структурой:
\begin{lstlisting}[language=Python]
@dataclass
class ACOResult:
best_tour: Tour # индексы городов в порядке обхода
best_length: float # длина лучшего маршрута
history: List[float] # история длин по итерациям
\end{lstlisting}
\subsection{Класс AntColonyOptimizer и инициализация}
Основная логика инкапсулирована в классе \texttt{AntColonyOptimizer}, который принимает конфигурацию при создании:
\begin{lstlisting}[language=Python]
class AntColonyOptimizer:
def __init__(self, config: ACOConfig)
\end{lstlisting}
В конструкторе выполняются следующие действия:
\begin{itemize}
\item инициализация генератора случайных чисел через \texttt{random.seed(config.seed)} для обеспечения воспроизводимости экспериментов;
\item вычисление матрицы расстояний между всеми городами с помощью \texttt{build\_distance\_matrix};
\item создание матрицы феромона размером $n \times n$, где все недиагональные элементы инициализируются единицами, а диагональные — нулями (для предотвращения самопереходов).
\end{itemize}
\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 муравьёв, 50 итераций, $\alpha = 1{,}2$, $\beta = 5$, $\rho = 0{,}5$, $q = 1$. Лучший найденный тур имеет длину $6662{,}35$, что на $0{,}05\%$ отличается от оптимального значения 6659.
\begin{figure}[h!]
\centering
\begin{minipage}{0.48\linewidth}
\centering
\includegraphics[width=0.95\linewidth]{img/optimal_tour.png}
\caption{Оптимальный маршрут длиной 6659}
\label{fig:optimal_result}
\end{minipage}\hfill
\begin{minipage}{0.48\linewidth}
\centering
\includegraphics[width=0.95\linewidth]{img/aco_best_tour.png}
\caption{Лучший маршрут, найденный муравьиным алгоритмом (6662{,}35)}
\label{fig:aco_tour}
\end{minipage}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/aco_history.png}
\caption{Сходимость длины лучшего тура по итерациям}
\label{fig:aco_history}
\end{figure}
\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\%).
\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*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе шестой лабораторной работы выполнена реализация простого муравьиного алгоритма для задачи коммивояжёра:
\begin{enumerate}
\item Разработан модуль \texttt{aco.py} с конфигурацией алгоритма, построением туров, обновлением феромона и визуализацией результатов с помощью \texttt{matplotlib}.
\item Проведён численный эксперимент на данных из варианта 18 (38 городов Джибути); подобраны параметры $\alpha=1{,}2$, $\beta=5$, $\rho=0{,}5$, 50 муравьёв, 400 итераций.
\item Получено приближённое решение длиной 6662{,}35, что всего на 0{,}05\% хуже известного оптимума 6659 и лучше результата, достигнутого генетическим алгоритмом из лабораторной работы №3.
\end{enumerate}
\newpage
\section*{Список литературы}
\addcontentsline{toc}{section}{Список литературы}
\vspace{-1.5cm}
\begin{thebibliography}{0}
\bibitem{vostrov}
Методические указания по выполнению лабораторных работ к курсу «Генетические алгоритмы», 119 стр.
\end{thebibliography}
\end{document}