|
@@ -1,5 +1,4 @@
|
|
|
using System.Collections.Concurrent;
|
|
|
-using System.Reflection.Metadata.Ecma335;
|
|
|
|
|
|
namespace AntColony.Algorithm;
|
|
|
|
|
@@ -9,33 +8,26 @@ namespace AntColony.Algorithm;
|
|
|
public class AntColonyOptimizer
|
|
|
{
|
|
|
private readonly int numberOfCities;
|
|
|
- private readonly double[][] distances;
|
|
|
- private readonly double[][] pheromones;
|
|
|
- private readonly double alpha; // Важность феромонов
|
|
|
- private readonly double beta; // Важность расстояния
|
|
|
+ private double[][] distances;
|
|
|
+ private double[][] pheromones;
|
|
|
private readonly double evaporationRate;
|
|
|
- private readonly double Q; // Множитель депозита феромонов
|
|
|
private readonly int numberOfAnts;
|
|
|
|
|
|
+ private readonly List<IAnt> xants = [];
|
|
|
+ private IAnt[] ants;
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Инициализировать алгоритм
|
|
|
/// </summary>
|
|
|
/// <param name="distances">Матрица расстояний</param>
|
|
|
/// <param name="numberOfAnts">Количество муравьев</param>
|
|
|
- /// <param name="alpha">Значение α (определяет вес феромонов)</param>
|
|
|
- /// <param name="beta">Значение β (определяет вес расстояния)</param>
|
|
|
/// <param name="evaporationRate">Скорость испарения</param>
|
|
|
/// <param name="Q">Значение Q</param>
|
|
|
- public AntColonyOptimizer(double[][] distances, int numberOfAnts = 20, double alpha = 1, double beta = 2,
|
|
|
- double evaporationRate = 0.1, double Q = 100)
|
|
|
+ public AntColonyOptimizer(double[][] distances, double evaporationRate = 0.1)
|
|
|
{
|
|
|
numberOfCities = distances.GetLength(0);
|
|
|
this.distances = distances;
|
|
|
- this.numberOfAnts = numberOfAnts;
|
|
|
- this.alpha = alpha;
|
|
|
- this.beta = beta;
|
|
|
this.evaporationRate = evaporationRate;
|
|
|
- this.Q = Q;
|
|
|
|
|
|
// Строим стартовую матрицу феромонов
|
|
|
pheromones = new double[numberOfCities][];
|
|
@@ -51,6 +43,24 @@ public class AntColonyOptimizer
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public AntColonyOptimizer AddAnts<T>(int count) where T : IAnt
|
|
|
+ {
|
|
|
+ for (int i = 0; i < count; i++)
|
|
|
+ {
|
|
|
+ xants.Add(Activator.CreateInstance<T>()!);
|
|
|
+ }
|
|
|
+
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public AntColonyOptimizer ShuffleAnts()
|
|
|
+ {
|
|
|
+ ants = [.. xants];
|
|
|
+ Random.Shared.Shuffle(ants);
|
|
|
+
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Обработчик ранней остановки работы алгоритма
|
|
|
/// </summary>
|
|
@@ -61,7 +71,7 @@ public class AntColonyOptimizer
|
|
|
private double bestDistanceEver = double.PositiveInfinity;
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Решить задачу комивояжера (построить Гамильтонов граф)
|
|
|
+ /// Решить задачу комивояжера (найти Гамильтонов цикл)
|
|
|
/// </summary>
|
|
|
/// <param name="maxIterations">Максимальное число итераций</param>
|
|
|
/// <returns>Лучший путь и его расстояние</returns>
|
|
@@ -76,10 +86,9 @@ public class AntColonyOptimizer
|
|
|
Console.Write($"{i}/{maxIterations} - {bestDistanceEver}\r");
|
|
|
|
|
|
// Каждый муравей строит путь
|
|
|
- var iterationTours = Enumerable.Range(0, numberOfAnts).AsParallel().Select(i =>
|
|
|
+ var iterationTours = ants.AsParallel().Select(ant =>
|
|
|
{
|
|
|
- var ct = ConstructTour();
|
|
|
- // while (!ct.HasValue) ct = ConstructTour();
|
|
|
+ var ct = ant.ConstructTour(ref distances, ref pheromones);
|
|
|
if (!ct.HasValue) return ([], double.MaxValue);
|
|
|
var (tour, distance) = ct.Value;
|
|
|
antTours.Add(tour);
|
|
@@ -89,13 +98,18 @@ public class AntColonyOptimizer
|
|
|
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;
|
|
|
|
|
|
// Обновляем феромоны для всех путей
|
|
|
- UpdatePheromones([.. antTours], [.. antDistances]);
|
|
|
+ EvaporatePheromones();
|
|
|
// File.AppendAllText("./iteration-results", bestDistanceEver.ToString() + '\n');
|
|
|
// }
|
|
|
|
|
@@ -111,81 +125,10 @@ public class AntColonyOptimizer
|
|
|
return (bestTourEver, bestDistanceEver);
|
|
|
}
|
|
|
|
|
|
- private (List<int> Tour, double Distance)? ConstructTour()
|
|
|
- {
|
|
|
- List<int> tour = [];
|
|
|
- var visited = new bool[numberOfCities];
|
|
|
- var currentCity = Random.Shared.Next(numberOfCities);
|
|
|
-
|
|
|
- tour.Add(currentCity);
|
|
|
- visited[currentCity] = true;
|
|
|
-
|
|
|
- // Строим путь
|
|
|
- while (tour.Count < numberOfCities)
|
|
|
- {
|
|
|
- var sel = SelectNextCity(currentCity, visited);
|
|
|
- while (!sel.HasValue) sel = SelectNextCity(currentCity, visited);
|
|
|
- if (sel is not { } nextCity) return null;
|
|
|
- tour.Add(nextCity);
|
|
|
- visited[nextCity] = true;
|
|
|
- currentCity = nextCity;
|
|
|
- }
|
|
|
-
|
|
|
- if (distances[currentCity][tour[0]] == 0) return null;
|
|
|
- // Добавляем возврат на начальную точку
|
|
|
- tour.Add(tour[0]);
|
|
|
-
|
|
|
- return (tour, CalculateTourDistance(tour));
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Выбрать следующий город
|
|
|
- /// </summary>
|
|
|
- /// <param name="currentCity">В каком городе муравей находится сейчас</param>
|
|
|
- /// <param name="visited">Какие города муравей уже посетил</param>
|
|
|
- /// <returns>Номер следующего города</returns>
|
|
|
- private int? SelectNextCity(int currentCity, bool[] visited)
|
|
|
- {
|
|
|
- var probabilities = new double[numberOfCities];
|
|
|
- double probabilitiesSum = 0;
|
|
|
-
|
|
|
- // Вероятности для каждого непосещенного города
|
|
|
- for (int i = 0; i < numberOfCities; i++)
|
|
|
- {
|
|
|
- if (!visited[i])
|
|
|
- {
|
|
|
- if (distances[currentCity][i] == 0) continue;
|
|
|
- probabilities[i] = Math.Pow(pheromones[currentCity][i], alpha) *
|
|
|
- Math.Pow(1.0 / distances[currentCity][i], beta);
|
|
|
- probabilitiesSum += probabilities[i];
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Случайно выбираем следующий город (по вероятностям)
|
|
|
- double randomValue = Random.Shared.NextDouble() * probabilitiesSum;
|
|
|
- //double sum = 0;
|
|
|
-
|
|
|
- for (int i = 0; i < numberOfCities; i++)
|
|
|
- {
|
|
|
- if (!visited[i])
|
|
|
- {
|
|
|
- //sum += probabilities[i];
|
|
|
- if (probabilities[i] >= randomValue)
|
|
|
- {
|
|
|
- return i;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// Обновить матрицу феромонов
|
|
|
/// </summary>
|
|
|
- /// <param name="antTours">Пути муравьев</param>
|
|
|
- /// <param name="antDistances">Их расстояния</param>
|
|
|
- private void UpdatePheromones(List<List<int>> antTours, List<double> antDistances)
|
|
|
+ private void EvaporatePheromones()
|
|
|
{
|
|
|
// Испарение
|
|
|
for (int i = 0; i < numberOfCities; i++)
|
|
@@ -196,35 +139,5 @@ public class AntColonyOptimizer
|
|
|
pheromones[i][j] *= 1.0 - evaporationRate;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- // Добавляем новые феромоны
|
|
|
- for (int ant = 0; ant < antTours.Count; ant++)
|
|
|
- {
|
|
|
- double pheromoneDeposit = Q / antDistances[ant];
|
|
|
- var tour = antTours[ant];
|
|
|
-
|
|
|
- for (int i = 0; i < tour.Count - 1; i++)
|
|
|
- {
|
|
|
- int city1 = tour[i];
|
|
|
- int city2 = tour[i + 1];
|
|
|
- pheromones[city1][city2] += pheromoneDeposit;
|
|
|
- pheromones[city2][city1] += pheromoneDeposit; // Граф ненаправленный
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Рассчитать длину пути
|
|
|
- /// </summary>
|
|
|
- /// <param name="tour">Путь</param>
|
|
|
- /// <returns>Длина пути</returns>
|
|
|
- private double CalculateTourDistance(List<int> tour)
|
|
|
- {
|
|
|
- double distance = 0;
|
|
|
- for (int i = 0; i < tour.Count - 1; i++)
|
|
|
- {
|
|
|
- distance += distances[tour[i]][tour[i + 1]];
|
|
|
- }
|
|
|
- return distance;
|
|
|
}
|
|
|
}
|