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 int numberOfAnts;
private readonly List xants = [];
private IAnt[] ants;
///
/// Инициализировать алгоритм
///
/// Матрица расстояний
/// Количество муравьев
/// Скорость испарения
/// Значение Q
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) where T : IAnt
{
for (int i = 0; i < count; i++)
{
xants.Add(Activator.CreateInstance()!);
}
return this;
}
public AntColonyOptimizer ShuffleAnts()
{
ants = [.. xants];
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)
{
// for (int iteration = 0; iteration < maxIterations; iteration++)
for (int i = 0; i < maxIterations; i++)
{
ConcurrentBag> antTours = [];
ConcurrentBag antDistances = [];
Console.Write($"{i}/{maxIterations} - {bestDistanceEver}\r");
// Каждый муравей строит путь
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);
// Console.Write($"\r|- {i}/{numberOfAnts} - {distance}");
return (tour, distance);
});
for (int j = 0; j < antTours.Count; j++)
{
ants[j].UpdatePheromones(ref pheromones);
}
// Console.Write(new string(' ', 20));
// Console.CursorTop--;
// Console.CursorLeft = 0;
// Обновляем феромоны для всех путей
EvaporatePheromones();
// File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n');
// }
(List Tour, double Distance) bestIterationTour = iterationTours.MinBy(x => x.distance);
if (bestIterationTour.Distance < bestDistanceEver)
{
bestDistanceEver = bestIterationTour.Distance;
bestTourEver = bestIterationTour.Tour;
Console.WriteLine();
}
};
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;
}
}
}
}