using System.Collections.Concurrent;
namespace AntColony.Algorithm;
///
/// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
///
public class AntColonyOptimizer
{
private readonly int numberOfCities;
private double[][] distances;
private double[][] pheromones;
private readonly double evaporationRate;
private readonly List xants = [];
private IAnt[]? ants;
///
/// Инициализировать алгоритм
///
/// Матрица расстояний
/// Скорость испарения
public AntColonyOptimizer(double[][] distances, double evaporationRate = 0.1)
{
numberOfCities = distances.GetLength(0);
this.distances = distances;
this.evaporationRate = evaporationRate;
// Строим стартовую матрицу феромонов
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 AntColonyOptimizer AddAnts(int count, params object[] args) where T : IAnt
{
for (int i = 0; i < count; i++)
{
// Создаем нового муравья заданного типа
xants.Add((T)Activator.CreateInstance(typeof(T), args)!);
}
return this;
}
///
/// Расположить всех муравьев в случайном порядке
///
/// Расположить муравьев в случайном порядке
public AntColonyOptimizer CompileAnts(bool shuffle = false)
{
ants = [.. xants];
// Если нужно, располагаем муравьев в случайном порядке
if (shuffle) Random.Shared.Shuffle(ants);
return this;
}
///
/// Обработчик ранней остановки работы алгоритма
///
/// Лучший путь и его длину на текущий момент
public (List tour, double distance) EarlyExit() => (bestTourEver, bestDistanceEver);
private List bestTourEver = [];
private double bestDistanceEver = double.PositiveInfinity;
///
/// Решить задачу комивояжера (найти Гамильтонов цикл)
///
/// Максимальное число итераций
/// Лучший путь и его расстояние
public (List bestTour, double bestDistance) Solve(int maxIterations)
{
// Если колония муравьев пуста
if (ants is null) throw new MissingMemberException("Колония пуста. Забыли вызвать AntColonyOptimizer.CompileAnts()?");
Console.WriteLine($"Поиск кратчайшего Гамильтонова цикла с {ants.Length} муравьев и {maxIterations} итераций");
for (int i = 0; i < maxIterations; i++)
{
ConcurrentBag> antTours = [];
ConcurrentBag antDistances = [];
// Каждый муравей строит путь
var iterationTours = ants.AsParallel().Select(ant =>
{
var ct = ant.ConstructTour(ref distances, ref pheromones);
// Если муравей зашел в тупик - убиваем его
if (!ct.HasValue) return ([], double.MaxValue);
var (tour, distance) = ct.Value;
antTours.Add(tour);
antDistances.Add(distance);
return (tour, distance);
});
// После того, как все муравьи построили маршруты, они последовательно (не параллельно) обновляют феромоны
for (int j = 0; j < antTours.Count; j++)
{
ants[j].UpdatePheromones(ref pheromones);
}
// Испаряем феромоны после итерации
EvaporatePheromones();
// Находим лучший путь
(List Tour, double Distance) bestIterationTour = iterationTours.MinBy(x => x.distance);
if (bestIterationTour.Distance < bestDistanceEver) // Если лучше текущего - обновляем текущий
{
bestDistanceEver = bestIterationTour.Distance;
bestTourEver = bestIterationTour.Tour;
Console.WriteLine($"{i} - {bestDistanceEver,20}");
}
Console.Write($"{i}/{maxIterations} - {bestDistanceEver}\r");
}
return (bestTourEver, bestDistanceEver);
}
///
/// Испарить феромоны
///
private void EvaporatePheromones()
{
for (int i = 0; i < numberOfCities; i++)
{
for (int j = 0; j < numberOfCities; j++)
{
if (i != j)
pheromones[i][j] *= 1.0 - evaporationRate;
}
}
}
}