ACO.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. using System.Collections.Concurrent;
  2. namespace AntColony.Algorithm;
  3. /// <summary>
  4. /// Алгоритм построения кратчайшего пути с помощью симуляции муравьиной колонии
  5. /// </summary>
  6. public class AntColonyOptimizer
  7. {
  8. private readonly int numberOfCities;
  9. private double[][] distances;
  10. private double[][] pheromones;
  11. private readonly double evaporationRate;
  12. private readonly List<IAnt> xants = [];
  13. private IAnt[]? ants;
  14. /// <summary>
  15. /// Инициализировать алгоритм
  16. /// </summary>
  17. /// <param name="distances">Матрица расстояний</param>
  18. /// <param name="evaporationRate">Скорость испарения</param>
  19. public AntColonyOptimizer(double[][] distances, double evaporationRate = 0.1)
  20. {
  21. numberOfCities = distances.GetLength(0);
  22. this.distances = distances;
  23. this.evaporationRate = evaporationRate;
  24. // Строим стартовую матрицу феромонов
  25. pheromones = new double[numberOfCities][];
  26. for (int i = 0; i < numberOfCities; i++)
  27. {
  28. var r = new double[numberOfCities];
  29. for (int j = 0; j < numberOfCities; j++)
  30. {
  31. if (i != j)
  32. r[j] = 1.0; // Начальное значение
  33. }
  34. pheromones[i] = r;
  35. }
  36. }
  37. /// <summary>
  38. /// Добавить определенное количество муравьев заданного типа
  39. /// </summary>
  40. /// <typeparam name="T">Тип муравьев</typeparam>
  41. /// <param name="count">Количество муравьев</param>
  42. /// <param name="args">Аргументы конструктора муравья</param>
  43. public AntColonyOptimizer AddAnts<T>(int count, params object[] args) where T : IAnt
  44. {
  45. for (int i = 0; i < count; i++)
  46. {
  47. // Создаем нового муравья заданного типа
  48. xants.Add((T)Activator.CreateInstance(typeof(T), args)!);
  49. }
  50. return this;
  51. }
  52. /// <summary>
  53. /// Расположить всех муравьев в случайном порядке
  54. /// </summary>
  55. /// <param name="shuffle">Расположить муравьев в случайном порядке</param>
  56. public AntColonyOptimizer CompileAnts(bool shuffle = false)
  57. {
  58. ants = [.. xants];
  59. // Если нужно, располагаем муравьев в случайном порядке
  60. if (shuffle) Random.Shared.Shuffle(ants);
  61. return this;
  62. }
  63. /// <summary>
  64. /// Обработчик ранней остановки работы алгоритма
  65. /// </summary>
  66. /// <returns>Лучший путь и его длину на текущий момент</returns>
  67. public (List<int> tour, double distance) EarlyExit() => (bestTourEver, bestDistanceEver);
  68. private List<int> bestTourEver = [];
  69. private double bestDistanceEver = double.PositiveInfinity;
  70. /// <summary>
  71. /// Решить задачу комивояжера (найти Гамильтонов цикл)
  72. /// </summary>
  73. /// <param name="maxIterations">Максимальное число итераций</param>
  74. /// <returns>Лучший путь и его расстояние</returns>
  75. public (List<int> bestTour, double bestDistance) Solve(int maxIterations)
  76. {
  77. // Если колония муравьев пуста
  78. if (ants is null) throw new MissingMemberException("Колония пуста. Забыли вызвать AntColonyOptimizer.CompileAnts()?");
  79. Console.WriteLine($"Поиск кратчайшего Гамильтонова цикла с {ants.Length} муравьев и {maxIterations} итераций");
  80. for (int i = 0; i < maxIterations; i++)
  81. {
  82. ConcurrentBag<List<int>> antTours = [];
  83. ConcurrentBag<double> antDistances = [];
  84. // Каждый муравей строит путь
  85. var iterationTours = ants.AsParallel().Select(ant =>
  86. {
  87. var ct = ant.ConstructTour(ref distances, ref pheromones);
  88. // Если муравей зашел в тупик - убиваем его
  89. if (!ct.HasValue) return ([], double.MaxValue);
  90. var (tour, distance) = ct.Value;
  91. antTours.Add(tour);
  92. antDistances.Add(distance);
  93. return (tour, distance);
  94. });
  95. // После того, как все муравьи построили маршруты, они последовательно (не параллельно) обновляют феромоны
  96. for (int j = 0; j < antTours.Count; j++)
  97. {
  98. ants[j].UpdatePheromones(ref pheromones);
  99. }
  100. // Испаряем феромоны после итерации
  101. EvaporatePheromones();
  102. // Находим лучший путь
  103. (List<int> Tour, double Distance) bestIterationTour = iterationTours.MinBy(x => x.distance);
  104. if (bestIterationTour.Distance < bestDistanceEver) // Если лучше текущего - обновляем текущий
  105. {
  106. bestDistanceEver = bestIterationTour.Distance;
  107. bestTourEver = bestIterationTour.Tour;
  108. Console.WriteLine($"{i} - {bestDistanceEver,20}");
  109. }
  110. Console.Write($"{i}/{maxIterations} - {bestDistanceEver}\r");
  111. }
  112. return (bestTourEver, bestDistanceEver);
  113. }
  114. /// <summary>
  115. /// Испарить феромоны
  116. /// </summary>
  117. private void EvaporatePheromones()
  118. {
  119. for (int i = 0; i < numberOfCities; i++)
  120. {
  121. for (int j = 0; j < numberOfCities; j++)
  122. {
  123. if (i != j)
  124. pheromones[i][j] *= 1.0 - evaporationRate;
  125. }
  126. }
  127. }
  128. }