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