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; } } } }