using Microsoft.VisualBasic;
namespace AntColony.Algorithm;
///
/// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
///
public class AntColonyOptimizer
{
private readonly int numberOfCities;
private readonly double[][] distances;
private readonly double[][] pheromones;
private readonly double alpha; // Важность феромонов
private readonly double beta; // Важность расстояния
private readonly double evaporationRate;
private readonly double Q; // Множитель депозита феромонов
private readonly int numberOfAnts;
///
/// Инициализировать алгоритм
///
/// Матрица расстояний
/// Количество муравьев
/// Значение α (определяет вес феромонов)
/// Значение β (определяет вес расстояния)
/// Скорость испарения
/// Значение Q
public AntColonyOptimizer(double[][] distances, int numberOfAnts = 20, double alpha = 1, double beta = 2,
double evaporationRate = 0.1, double Q = 100)
{
numberOfCities = distances.GetLength(0);
this.distances = distances;
this.numberOfAnts = numberOfAnts;
this.alpha = alpha;
this.beta = beta;
this.evaporationRate = evaporationRate;
this.Q = Q;
// Строим стартовую матрицу феромонов
pheromones = new double[numberOfCities][];
for (int i = 0; i < numberOfCities; i++)
{
var r = new double[numberOfCities];
for (int j = 0; j < numberOfCities; j++)
{
if (i != j)
r[j] = 1.0; // Начальное значение
}
pheromones[i] = r;
}
}
///
/// Решить задачу комивояжера (построить Гамильтонов граф)
///
/// Максимальное число итераций
/// Лучший путь и его расстояние
public (List bestTour, double bestDistance) Solve(int maxIterations)
{
List bestTourEver = [];
double bestDistanceEver = double.MaxValue;
Lock distanceLock = new();
Lock pheromoneLock = new();
// for (int iteration = 0; iteration < maxIterations; iteration++)
Parallel.For(0, maxIterations, i =>
{
List> antTours = [];
List antDistances = [];
// Каждый муравей строит путь
for (int ant = 0; ant < numberOfAnts; ant++)
{
var (tour, distance) = ConstructTour();
antTours.Add(tour);
antDistances.Add(distance);
// Если текущий путь лучше - обновляем
lock (distanceLock)
{
if (distance < bestDistanceEver)
{
bestDistanceEver = distance;
bestTourEver = [.. tour];
}
}
}
// Обновляем феромоны для всех путей
lock (pheromoneLock)
{
UpdatePheromones(antTours, antDistances);
}
// File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n');
// }
});
return (bestTourEver, bestDistanceEver);
}
private (List tour, double distance) ConstructTour()
{
List tour = [];
var visited = new bool[numberOfCities];
var currentCity = Random.Shared.Next(numberOfCities);
tour.Add(currentCity);
visited[currentCity] = true;
// Строим путь
while (tour.Count < numberOfCities)
{
int nextCity = SelectNextCity(currentCity, visited);
tour.Add(nextCity);
visited[nextCity] = true;
currentCity = nextCity;
}
// Добавляем возврат на начальную точку
tour.Add(tour[0]);
return (tour, CalculateTourDistance(tour));
}
///
/// Выбрать следующий город
///
/// В каком городе муравей находится сейчас
/// Какие города муравей уже посетил
/// Номер следующего города
private int SelectNextCity(int currentCity, bool[] visited)
{
var probabilities = new double[numberOfCities];
double probabilitiesSum = 0;
// Вероятности для каждого непосещенного города
for (int i = 0; i < numberOfCities; i++)
{
if (!visited[i])
{
if (distances[currentCity][i] == 0) continue;
probabilities[i] = Math.Pow(pheromones[currentCity][i], alpha) *
Math.Pow(1.0 / distances[currentCity][i], beta);
probabilitiesSum += probabilities[i];
}
}
// Случайно выбираем следующий город (по вероятностям)
double randomValue = Random.Shared.NextDouble() * probabilitiesSum;
double sum = 0;
for (int i = 0; i < numberOfCities; i++)
{
if (!visited[i])
{
sum += probabilities[i];
if (sum >= randomValue)
return i;
}
}
// Иначе возвращаемся к первому непосещенному городу
return Array.FindIndex(visited, v => !v);
}
///
/// Обновить матрицу феромонов
///
/// Пути муравьев
/// Их расстояния
private void UpdatePheromones(List> antTours, List antDistances)
{
// Испарение
for (int i = 0; i < numberOfCities; i++)
{
for (int j = 0; j < numberOfCities; j++)
{
if (i != j)
pheromones[i][j] *= 1.0 - evaporationRate;
}
}
// Добавляем новые феромоны
for (int ant = 0; ant < antTours.Count; ant++)
{
double pheromoneDeposit = Q / antDistances[ant];
var tour = antTours[ant];
for (int i = 0; i < tour.Count - 1; i++)
{
int city1 = tour[i];
int city2 = tour[i + 1];
pheromones[city1][city2] += pheromoneDeposit;
pheromones[city2][city1] += pheromoneDeposit; // Граф ненаправленный
}
}
}
///
/// Рассчитать длину пути
///
/// Путь
/// Длина пути
private double CalculateTourDistance(List tour)
{
double distance = 0;
for (int i = 0; i < tour.Count - 1; i++)
{
distance += distances[tour[i]][tour[i + 1]];
}
return distance;
}
}