123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- using System.Collections.Concurrent;
- namespace AntColony.Algorithm;
- /// <summary>
- /// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
- /// </summary>
- public class AntColonyOptimizer
- {
- private readonly int numberOfCities;
- private double[][] distances;
- private double[][] pheromones;
- private readonly double evaporationRate;
- private readonly List<IAnt> xants = [];
- private IAnt[]? ants;
- /// <summary>
- /// Инициализировать алгоритм
- /// </summary>
- /// <param name="distances">Матрица расстояний</param>
- /// <param name="evaporationRate">Скорость испарения</param>
- 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;
- }
- }
- /// <summary>
- /// Добавить определенное количество муравьев заданного типа
- /// </summary>
- /// <typeparam name="T">Тип муравьев</typeparam>
- /// <param name="count">Количество муравьев</param>
- /// <param name="args">Аргументы конструктора муравья</param>
- public AntColonyOptimizer AddAnts<T>(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;
- }
- /// <summary>
- /// Расположить всех муравьев в случайном порядке
- /// </summary>
- /// <param name="shuffle">Расположить муравьев в случайном порядке</param>
- public AntColonyOptimizer CompileAnts(bool shuffle = false)
- {
- ants = [.. xants];
- // Если нужно, располагаем муравьев в случайном порядке
- if (shuffle) Random.Shared.Shuffle(ants);
- return this;
- }
- /// <summary>
- /// Обработчик ранней остановки работы алгоритма
- /// </summary>
- /// <returns>Лучший путь и его длину на текущий момент</returns>
- public (List<int> tour, double distance) EarlyExit() => (bestTourEver, bestDistanceEver);
- private List<int> bestTourEver = [];
- private double bestDistanceEver = double.PositiveInfinity;
- /// <summary>
- /// Решить задачу комивояжера (найти Гамильтонов цикл)
- /// </summary>
- /// <param name="maxIterations">Максимальное число итераций</param>
- /// <returns>Лучший путь и его расстояние</returns>
- public (List<int> 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<List<int>> antTours = [];
- ConcurrentBag<double> 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<int> 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);
- }
- /// <summary>
- /// Испарить феромоны
- /// </summary>
- 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;
- }
- }
- }
- }
|